Repository: prometheus/procfs
Pull request #156 (2019-05-07 16:38:41)

update readme with some basic usage info


Patch:
diff --git a/Makefile b/Makefile
index 314d1ba..616a0d2 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@
 include Makefile.common
 
 %/.unpacked: %.ttar
+	@echo ">> extracting fixtures"
 	./ttar -C $(dir $*) -x -f $*.ttar
 	touch $@
 
diff --git a/Makefile.common b/Makefile.common
index 4f18ea5..c7f9ea6 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -69,7 +69,7 @@ else
 	GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
 endif
 
-PROMU_VERSION ?= 0.3.0
+PROMU_VERSION ?= 0.4.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
 GOLANGCI_LINT :=
diff --git a/README.md b/README.md
index 2095494..7066e11 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # procfs
 
 This procfs package provides functions to retrieve system, kernel and process
-metrics from the pseudo-filesystem proc.
+metrics from the pseudo-filesystems /proc and /sys.
 
 *WARNING*: This package is a work in progress. Its API may still break in
 backwards-incompatible ways without warnings. Use it at your own risk.
@@ -9,3 +9,38 @@ backwards-incompatible ways without warnings. Use it at your own risk.
 [![GoDoc](https://godoc.org/github.com/prometheus/procfs?status.png)](https://godoc.org/github.com/prometheus/procfs)
 [![Build Status](https://travis-ci.org/prometheus/procfs.svg?branch=master)](https://travis-ci.org/prometheus/procfs)
 [![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/procfs)](https://goreportcard.com/report/github.com/prometheus/procfs)
+
+## Usage
+
+The procfs library is organized by packages based on whether the gathered data is coming from
+/proc, /sys, or both.  Each package contains an `FS` type which represents the path to either /proc, /sys, or both.  For example, current cpu statistics are gathered from
+`/proc/stat` and are available via the root procfs package.  First, the proc filesystem mount 
+point is initialized, and then the stat information is read.
+
+```go
+	fs, err := procfs.NewFS("/proc")
+    stats, err := fs.NewStat()
+```
+
+## Building and Testing
+
+The procfs library is normally built as part of another application.  However, when making
+changes to the library, the `make test` command can be used to run the API test suite.
+
+### Updating Test Fixtures
+
+The procfs library includes a set of test fixtures which include many example files from
+the `/proc` and `/sys` filesystems.  These fixtures are included as a ttar (text tar) file
+which is extracted automatically during testing.  To add/update the test fixtures, first
+ensure the `fixtures` directory is up to date by removing the existing directory and then
+extracting the ttar file using `make fixtures/.unpacked` or just `make test`.
+
+```bash
+    rm -rf fixtures
+    make test
+```
+
+Next, make the required changes to the extracted files in the `fixtures` directory.  When 
+the changes are complete, run `make update-fixtures` to create a new `fixtures.ttar` file
+based on the updated `fixtures` directory.  And finally, verify the changes using 
+`git diff fixtures.ttar`.
diff --git a/blockdevice/stats.go b/blockdevice/stats.go
index efaeb81..2e8e124 100644
--- a/blockdevice/stats.go
+++ b/blockdevice/stats.go
@@ -95,8 +95,8 @@ type FS struct {
 	sys  *fs.FS
 }
 
-// NewFS returns a new XFS mounted under the given mountPoint. It will error
-// if the mount point can't be read.
+// NewFS returns a new blockdevice fs using the given mountPoints for proc and sys.
+// It will error if either of these mount points can't be read.
 func NewFS(procMountPoint string, sysMountPoint string) (FS, error) {
 	if strings.TrimSpace(procMountPoint) == "" {
 		procMountPoint = fs.DefaultProcMountPoint
diff --git a/fixtures.ttar b/fixtures.ttar
index 1242447..0ada8ae 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -81,7 +81,7 @@ device sysfs mounted on /sys with fstype sysfs
 device proc mounted on /proc with fstype proc
 device /dev/sda1 mounted on / with fstype ext4
 device 192.168.1.1:/srv/test mounted on /mnt/nfs/test with fstype nfs4 statvers=1.1
-	opts:	rw,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.1.5,local_lock=none
+	opts:	rw,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.1,clientaddr=192.168.1.5,local_lock=none
 	age:	13968
 	caps:	caps=0xfff7,wtmult=512,dtsize=32768,bsize=0,namlen=255
 	nfsv4:	bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x0,acl=0x0,pnfs=not configured
@@ -1164,6 +1164,22 @@ Mode: 644
 Directory: fixtures/sys/devices/system
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/clocksource
+Mode: 775
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/clocksource/clocksource0
+Mode: 775
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/clocksource/clocksource0/available_clocksource
+Lines: 1
+tsc hpet acpi_pm 
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/clocksource/clocksource0/current_clocksource
+Lines: 1
+tsc
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/cpu
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/mountstats.go b/mountstats.go
index fc385af..7e8f044 100644
--- a/mountstats.go
+++ b/mountstats.go
@@ -69,8 +69,8 @@ type MountStats interface {
 type MountStatsNFS struct {
 	// The version of statistics provided.
 	StatVersion string
-	// The optional mountaddr of the NFS mount.
-	MountAddress string
+	// The mount options of the NFS mount.
+	Opts map[string]string
 	// The age of the NFS mount.
 	Age time.Duration
 	// Statistics related to byte counters for various operations.
@@ -342,10 +342,15 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 
 		switch ss[0] {
 		case fieldOpts:
+			if stats.Opts == nil {
+				stats.Opts = map[string]string{}
+			}
 			for _, opt := range strings.Split(ss[1], ",") {
 				split := strings.Split(opt, "=")
-				if len(split) == 2 && split[0] == "mountaddr" {
-					stats.MountAddress = split[1]
+				if len(split) == 2 {
+					stats.Opts[split[0]] = split[1]
+				} else {
+					stats.Opts[opt] = ""
 				}
 			}
 		case fieldAge:
diff --git a/net_dev.go b/net_dev.go
index 0063594..8249c98 100644
--- a/net_dev.go
+++ b/net_dev.go
@@ -75,7 +75,7 @@ func newNetDev(file string) (NetDev, error) {
 	}
 	defer f.Close()
 
-	nd := NetDev{}
+	netDev := NetDev{}
 	s := bufio.NewScanner(f)
 	for n := 0; s.Scan(); n++ {
 		// Skip the 2 header lines.
@@ -83,20 +83,20 @@ func newNetDev(file string) (NetDev, error) {
 			continue
 		}
 
-		line, err := nd.parseLine(s.Text())
+		line, err := netDev.parseLine(s.Text())
 		if err != nil {
-			return nd, err
+			return netDev, err
 		}
 
-		nd[line.Name] = *line
+		netDev[line.Name] = *line
 	}
 
-	return nd, s.Err()
+	return netDev, s.Err()
 }
 
 // parseLine parses a single line from the /proc/net/dev file. Header lines
 // must be filtered prior to calling this method.
-func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
+func (netDev NetDev) parseLine(rawLine string) (*NetDevLine, error) {
 	parts := strings.SplitN(rawLine, ":", 2)
 	if len(parts) != 2 {
 		return nil, errors.New("invalid net/dev line, missing colon")
@@ -185,11 +185,11 @@ func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
 
 // Total aggregates the values across interfaces and returns a new NetDevLine.
 // The Name field will be a sorted comma separated list of interface names.
-func (nd NetDev) Total() NetDevLine {
+func (netDev NetDev) Total() NetDevLine {
 	total := NetDevLine{}
 
-	names := make([]string, 0, len(nd))
-	for _, ifc := range nd {
+	names := make([]string, 0, len(netDev))
+	for _, ifc := range netDev {
 		names = append(names, ifc.Name)
 		total.RxBytes += ifc.RxBytes
 		total.RxPackets += ifc.RxPackets
diff --git a/proc_ns.go b/proc_ns.go
index d06c26e..3f8d6d6 100644
--- a/proc_ns.go
+++ b/proc_ns.go
@@ -29,7 +29,7 @@ type Namespace struct {
 // Namespaces contains all of the namespaces that the process is contained in.
 type Namespaces map[string]Namespace
 
-// NewNamespaces reads from /proc/[pid/ns/* to get the namespaces of which the
+// NewNamespaces reads from /proc/<pid>/ns/* to get the namespaces of which the
 // process is a member.
 func (p Proc) NewNamespaces() (Namespaces, error) {
 	d, err := os.Open(p.path("ns"))
diff --git a/sysfs/clocksource.go b/sysfs/clocksource.go
new file mode 100644
index 0000000..d96c389
--- /dev/null
+++ b/sysfs/clocksource.go
@@ -0,0 +1,76 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !windows
+
+package sysfs
+
+import (
+	"path/filepath"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// ClockSource contains metrics related to the clock source
+type ClockSource struct {
+	Name      string
+	Available []string
+	Current   string
+}
+
+// ClockSources returns clocksource information including current and available clocksources
+// read from '/sys/devices/system/clocksource'
+func (fs FS) ClockSources() ([]ClockSource, error) {
+
+	clocksourcePaths, err := filepath.Glob(fs.sys.Path("devices/system/clocksource/clocksource[0-9]*"))
+	if err != nil {
+		return nil, err
+	}
+
+	clocksources := make([]ClockSource, len(clocksourcePaths))
+	for i, clocksourcePath := range clocksourcePaths {
+		clocksourceName := strings.TrimPrefix(filepath.Base(clocksourcePath), "clocksource")
+
+		clocksource, err := parseClocksource(clocksourcePath)
+		if err != nil {
+			return nil, err
+		}
+		clocksource.Name = clocksourceName
+		clocksources[i] = *clocksource
+	}
+
+	return clocksources, nil
+}
+
+func parseClocksource(clocksourcePath string) (*ClockSource, error) {
+
+	stringFiles := []string{
+		"available_clocksource",
+		"current_clocksource",
+	}
+	stringOut := make([]string, len(stringFiles))
+	var err error
+
+	for i, f := range stringFiles {
+		stringOut[i], err = util.SysReadFile(filepath.Join(clocksourcePath, f))
+		if err != nil {
+			return &ClockSource{}, err
+		}
+	}
+
+	return &ClockSource{
+		Available: strings.Fields(stringOut[0]),
+		Current:   stringOut[1],
+	}, nil
+}
diff --git a/ttar b/ttar
index b0171a1..19ef02b 100755
--- a/ttar
+++ b/ttar
@@ -86,8 +86,10 @@ Usage:   $bname [-C <DIR>] -c -f <ARCHIVE> <FILE...> (create archive)
          $bname [-C <DIR>] -x -f <ARCHIVE>           (extract archive)
 
 Options:
-         -C <DIR>                                    (change directory)
-         -v                                          (verbose)
+         -C <DIR>           (change directory)
+         -v                 (verbose)
+         --recursive-unlink (recursively delete existing directory if path
+                             collides with file or directory to extract)
 
 Example: Change to sysfs directory, create ttar file from fixtures directory
          $bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/
@@ -111,8 +113,9 @@ function set_cmd {
 }
 
 unset VERBOSE
+unset RECURSIVE_UNLINK
 
-while getopts :cf:htxvC: opt; do
+while getopts :cf:-:htxvC: opt; do
     case $opt in
         c)
             set_cmd "create"
@@ -136,6 +139,18 @@ while getopts :cf:htxvC: opt; do
         C)
             CDIR=$OPTARG
             ;;
+        -)
+            case $OPTARG in
+                recursive-unlink)
+                    RECURSIVE_UNLINK="yes"
+                    ;;
+                *)
+                    echo -e "Error: invalid option -$OPTARG"
+                    echo
+                    usage 1
+                    ;;
+            esac
+            ;;
         *)
             echo >&2 "ERROR: invalid option -$OPTARG"
             echo
@@ -212,16 +227,16 @@ function extract {
         local eof_without_newline
         if [ "$size" -gt 0 ]; then
             if [[ "$line" =~ [^\\]EOF ]]; then
-                # An EOF not preceeded by a backslash indicates that the line
+                # An EOF not preceded by a backslash indicates that the line
                 # does not end with a newline
                 eof_without_newline=1
             else
                 eof_without_newline=0
             fi
             # Replace NULLBYTE with null byte if at beginning of line
-            # Replace NULLBYTE with null byte unless preceeded by backslash
+            # Replace NULLBYTE with null byte unless preceded by backslash
             # Remove one backslash in front of NULLBYTE (if any)
-            # Remove EOF unless preceeded by backslash
+            # Remove EOF unless preceded by backslash
             # Remove one backslash in front of EOF
             if [ $USE_PYTHON -eq 1 ]; then
                 echo -n "$line" | python -c "$PYTHON_EXTRACT_FILTER" >> "$path"
@@ -245,7 +260,16 @@ function extract {
         fi
         if [[ $line =~ ^Path:\ (.*)$ ]]; then
             path=${BASH_REMATCH[1]}
-            if [ -e "$path" ] || [ -L "$path" ]; then
+            if [ -L "$path" ]; then
+                rm "$path"
+            elif [ -d "$path" ]; then
+                if [ "${RECURSIVE_UNLINK:-}" == "yes" ]; then
+                    rm -r "$path"
+                else
+                    # Safe because symlinks to directories are dealt with above
+                    rmdir "$path"
+                fi
+            elif [ -e "$path" ]; then
                 rm "$path"
             fi
         elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
@@ -338,8 +362,8 @@ function _create {
             else
                 < "$file" \
                     sed 's/EOF/\\EOF/g;
-                         s/NULLBYTE/\\NULLBYTE/g;
-                         s/\x0/NULLBYTE/g;
+                            s/NULLBYTE/\\NULLBYTE/g;
+                            s/\x0/NULLBYTE/g;
                     '
             fi
             if [[ "$eof_without_newline" -eq 1 ]]; then


Test patch:
diff --git a/mountstats_test.go b/mountstats_test.go
index 727af07..b25ca8f 100644
--- a/mountstats_test.go
+++ b/mountstats_test.go
@@ -227,8 +227,8 @@ func TestMountStats(t *testing.T) {
 				Mount:  "/mnt/nfs",
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
-					StatVersion:  "1.1",
-					MountAddress: "192.168.1.1",
+					StatVersion: "1.1",
+					Opts:        map[string]string{"rw": "", "vers": "3", "mountaddr": "192.168.1.1", "proto": "udp"},
 				},
 			}},
 		},
@@ -282,7 +282,14 @@ func TestMountStats(t *testing.T) {
 					Type:   "nfs4",
 					Stats: &MountStatsNFS{
 						StatVersion: "1.1",
-						Age:         13968 * time.Second,
+						Opts: map[string]string{"rw": "", "vers": "4.0",
+							"rsize": "1048576", "wsize": "1048576", "namlen": "255", "acregmin": "3",
+							"acregmax": "60", "acdirmin": "30", "acdirmax": "60", "hard": "",
+							"proto": "tcp", "port": "0", "timeo": "600", "retrans": "2",
+							"sec": "sys", "mountaddr": "192.168.1.1", "clientaddr": "192.168.1.5",
+							"local_lock": "none",
+						},
+						Age: 13968 * time.Second,
 						Bytes: NFSBytesStats{
 							Read:      1207640230,
 							ReadTotal: 1210214218,
@@ -376,7 +383,7 @@ func mountsStr(mounts []*Mount) string {
 			continue
 		}
 
-		out += fmt.Sprintf("\n\t- mountaddr: %s", stats.MountAddress)
+		out += fmt.Sprintf("\n\t- opts: %s", stats.Opts)
 		out += fmt.Sprintf("\n\t- v%s, age: %s", stats.StatVersion, stats.Age)
 		out += fmt.Sprintf("\n\t- bytes: %v", stats.Bytes)
 		out += fmt.Sprintf("\n\t- events: %v", stats.Events)
diff --git a/net_dev_test.go b/net_dev_test.go
index 363c194..1c829bb 100644
--- a/net_dev_test.go
+++ b/net_dev_test.go
@@ -37,7 +37,7 @@ func TestNewNetDev(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	nd, err := fs.NewNetDev()
+	netDev, err := fs.NewNetDev()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -49,10 +49,10 @@ func TestNewNetDev(t *testing.T) {
 		"eth0":        {Name: "eth0", RxBytes: 874354587, RxPackets: 1036395, TxBytes: 563352563, TxPackets: 732147},
 	}
 
-	if want, have := len(lines), len(nd); want != have {
+	if want, have := len(lines), len(netDev); want != have {
 		t.Errorf("want %d parsed net/dev lines, have %d", want, have)
 	}
-	for _, line := range nd {
+	for _, line := range netDev {
 		if want, have := lines[line.Name], line; want != have {
 			t.Errorf("%s: want %v, have %v", line.Name, want, have)
 		}
@@ -65,7 +65,7 @@ func TestProcNewNetDev(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	nd, err := p.NewNetDev()
+	netDev, err := p.NewNetDev()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -75,10 +75,10 @@ func TestProcNewNetDev(t *testing.T) {
 		"eth0": {Name: "eth0", RxBytes: 438, RxPackets: 5, TxBytes: 648, TxPackets: 8},
 	}
 
-	if want, have := len(lines), len(nd); want != have {
+	if want, have := len(lines), len(netDev); want != have {
 		t.Errorf("want %d parsed net/dev lines, have %d", want, have)
 	}
-	for _, line := range nd {
+	for _, line := range netDev {
 		if want, have := lines[line.Name], line; want != have {
 			t.Errorf("%s: want %v, have %v", line.Name, want, have)
 		}
diff --git a/sysfs/clocksource_test.go b/sysfs/clocksource_test.go
new file mode 100644
index 0000000..4fb0e98
--- /dev/null
+++ b/sysfs/clocksource_test.go
@@ -0,0 +1,45 @@
+// Copyright 2019 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !windows
+
+package sysfs
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestNewClocksource(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	c, err := fs.ClockSources()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	clocksources := []ClockSource{
+		{
+			Name:      "0",
+			Available: []string{"tsc", "hpet", "acpi_pm"},
+			Current:   "tsc",
+		},
+	}
+
+	if !reflect.DeepEqual(clocksources, c) {
+		t.Errorf("Result not correct: want %v, have %v", clocksources, c)
+	}
+}