pax_global_header00006660000000000000000000000064131264142320014510gustar00rootroot0000000000000052 comment=e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2 golang-procfs-0+git20170703.e645f4e/000077500000000000000000000000001312641423200164365ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/.travis.yml000066400000000000000000000000651312641423200205500ustar00rootroot00000000000000sudo: false language: go go: - 1.7.6 - 1.8.3 golang-procfs-0+git20170703.e645f4e/CONTRIBUTING.md000066400000000000000000000015461312641423200206750ustar00rootroot00000000000000# Contributing Prometheus uses GitHub to manage reviews of pull requests. * If you have a trivial fix or improvement, go ahead and create a pull request, addressing (with `@...`) the maintainer of this repository (see [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. * If you plan to do something more involved, first discuss your ideas on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). This will avoid unnecessary work and surely give you and us a good deal of inspiration. * Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best Practices for Production Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). golang-procfs-0+git20170703.e645f4e/LICENSE000066400000000000000000000261351312641423200174520ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. golang-procfs-0+git20170703.e645f4e/MAINTAINERS.md000066400000000000000000000000441312641423200205300ustar00rootroot00000000000000* Tobias Schmidt golang-procfs-0+git20170703.e645f4e/Makefile000066400000000000000000000004521312641423200200770ustar00rootroot00000000000000ci: fmt lint test fmt: ! gofmt -l *.go | read nothing go vet lint: go get github.com/golang/lint/golint golint *.go test: sysfs/fixtures/.unpacked go test -v ./... sysfs/fixtures/.unpacked: sysfs/fixtures.ttar ./ttar -C sysfs -x -f sysfs/fixtures.ttar touch $@ .PHONY: fmt lint test ci golang-procfs-0+git20170703.e645f4e/NOTICE000066400000000000000000000003551312641423200173450ustar00rootroot00000000000000procfs provides functions to retrieve system, kernel and process metrics from the pseudo-filesystem proc. Copyright 2014-2015 The Prometheus Authors This product includes software developed at SoundCloud Ltd. (http://soundcloud.com/). golang-procfs-0+git20170703.e645f4e/README.md000066400000000000000000000012171312641423200177160ustar00rootroot00000000000000# procfs This procfs package provides functions to retrieve system, kernel and process metrics from the pseudo-filesystem proc. *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. [![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) golang-procfs-0+git20170703.e645f4e/bcache/000077500000000000000000000000001312641423200176435ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/bcache/bcache.go000066400000000000000000000050411312641423200213770ustar00rootroot00000000000000// Copyright 2017 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. // Package bcache provides access to statistics exposed by the bcache (Linux // block cache). package bcache // Stats contains bcache runtime statistics, parsed from /sys/fs/bcache/. // // The names and meanings of each statistic were taken from bcache.txt and // files in drivers/md/bcache in the Linux kernel source. Counters are uint64 // (in-kernel counters are mostly unsigned long). type Stats struct { // The name of the bcache used to source these statistics. Name string Bcache BcacheStats Bdevs []BdevStats Caches []CacheStats } // BcacheStats contains statistics tied to a bcache ID. type BcacheStats struct { AverageKeySize uint64 BtreeCacheSize uint64 CacheAvailablePercent uint64 Congested uint64 RootUsagePercent uint64 TreeDepth uint64 Internal InternalStats FiveMin PeriodStats Total PeriodStats } // BdevStats contains statistics for one backing device. type BdevStats struct { Name string DirtyData uint64 FiveMin PeriodStats Total PeriodStats } // CacheStats contains statistics for one cache device. type CacheStats struct { Name string IOErrors uint64 MetadataWritten uint64 Written uint64 Priority PriorityStats } // PriorityStats contains statistics from the priority_stats file. type PriorityStats struct { UnusedPercent uint64 MetadataPercent uint64 } // InternalStats contains internal bcache statistics. type InternalStats struct { ActiveJournalEntries uint64 BtreeNodes uint64 BtreeReadAverageDurationNanoSeconds uint64 CacheReadRaces uint64 } // PeriodStats contains statistics for a time period (5 min or total). type PeriodStats struct { Bypassed uint64 CacheBypassHits uint64 CacheBypassMisses uint64 CacheHits uint64 CacheMissCollisions uint64 CacheMisses uint64 CacheReadaheads uint64 } golang-procfs-0+git20170703.e645f4e/bcache/get.go000066400000000000000000000213301312641423200207500ustar00rootroot00000000000000// Copyright 2017 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. package bcache import ( "bufio" "fmt" "io/ioutil" "os" "path" "path/filepath" "strconv" "strings" ) // ParsePseudoFloat parses the peculiar format produced by bcache's bch_hprint. func parsePseudoFloat(str string) (float64, error) { ss := strings.Split(str, ".") intPart, err := strconv.ParseFloat(ss[0], 64) if err != nil { return 0, err } if len(ss) == 1 { // Pure integers are fine. return intPart, nil } fracPart, err := strconv.ParseFloat(ss[1], 64) if err != nil { return 0, err } // fracPart is a number between 0 and 1023 divided by 100; it is off // by a small amount. Unexpected bumps in time lines may occur because // for bch_hprint .1 != .10 and .10 > .9 (at least up to Linux // v4.12-rc3). // Restore the proper order: fracPart = fracPart / 10.24 return intPart + fracPart, nil } // Dehumanize converts a human-readable byte slice into a uint64. func dehumanize(hbytes []byte) (uint64, error) { ll := len(hbytes) if ll == 0 { return 0, fmt.Errorf("zero-length reply") } lastByte := hbytes[ll-1] mul := float64(1) var ( mant float64 err error ) // If lastByte is beyond the range of ASCII digits, it must be a // multiplier. if lastByte > 57 { // Remove multiplier from slice. hbytes = hbytes[:len(hbytes)-1] const ( _ = 1 << (10 * iota) KiB MiB GiB TiB PiB EiB ZiB YiB ) multipliers := map[rune]float64{ // Source for conversion rules: // linux-kernel/drivers/md/bcache/util.c:bch_hprint() 'k': KiB, 'M': MiB, 'G': GiB, 'T': TiB, 'P': PiB, 'E': EiB, 'Z': ZiB, 'Y': YiB, } mul = float64(multipliers[rune(lastByte)]) mant, err = parsePseudoFloat(string(hbytes)) if err != nil { return 0, err } } else { // Not humanized by bch_hprint mant, err = strconv.ParseFloat(string(hbytes), 64) if err != nil { return 0, err } } res := uint64(mant * mul) return res, nil } type parser struct { uuidPath string subDir string currentDir string err error } func (p *parser) setSubDir(pathElements ...string) { p.subDir = path.Join(pathElements...) p.currentDir = path.Join(p.uuidPath, p.subDir) } func (p *parser) readValue(fileName string) uint64 { if p.err != nil { return 0 } path := path.Join(p.currentDir, fileName) byt, err := ioutil.ReadFile(path) if err != nil { p.err = fmt.Errorf("failed to read: %s", path) return 0 } // Remove trailing newline. byt = byt[:len(byt)-1] res, err := dehumanize(byt) p.err = err return res } // ParsePriorityStats parses lines from the priority_stats file. func parsePriorityStats(line string, ps *PriorityStats) (error) { var ( value uint64 err error ) switch { case strings.HasPrefix(line, "Unused:"): fields := strings.Fields(line) rawValue := fields[len(fields)-1] valueStr := strings.TrimSuffix(rawValue, "%") value, err = strconv.ParseUint(valueStr, 10, 64) if err != nil { return err } ps.UnusedPercent = value case strings.HasPrefix(line, "Metadata:"): fields := strings.Fields(line) rawValue := fields[len(fields)-1] valueStr := strings.TrimSuffix(rawValue, "%") value, err = strconv.ParseUint(valueStr, 10, 64) if err != nil { return err } ps.MetadataPercent = value } return nil } func (p *parser) getPriorityStats() PriorityStats { var res PriorityStats if p.err != nil { return res } path := path.Join(p.currentDir, "priority_stats") file, err := os.Open(path) if err != nil { p.err = fmt.Errorf("failed to read: %s", path) return res } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { err = parsePriorityStats(scanner.Text(), &res) if err != nil { p.err = fmt.Errorf("failed to parse: %s (%s)", path, err) return res } } if err := scanner.Err(); err != nil { p.err = fmt.Errorf("failed to parse: %s (%s)", path, err) return res } return res } // GetStats collects from sysfs files data tied to one bcache ID. func GetStats(uuidPath string) (*Stats, error) { var bs Stats par := parser{uuidPath: uuidPath} // bcache stats // dir par.setSubDir("") bs.Bcache.AverageKeySize = par.readValue("average_key_size") bs.Bcache.BtreeCacheSize = par.readValue("btree_cache_size") bs.Bcache.CacheAvailablePercent = par.readValue("cache_available_percent") bs.Bcache.Congested = par.readValue("congested") bs.Bcache.RootUsagePercent = par.readValue("root_usage_percent") bs.Bcache.TreeDepth = par.readValue("tree_depth") // bcache stats (internal) // dir /internal par.setSubDir("internal") bs.Bcache.Internal.ActiveJournalEntries = par.readValue("active_journal_entries") bs.Bcache.Internal.BtreeNodes = par.readValue("btree_nodes") bs.Bcache.Internal.BtreeReadAverageDurationNanoSeconds = par.readValue("btree_read_average_duration_us") bs.Bcache.Internal.CacheReadRaces = par.readValue("cache_read_races") // bcache stats (period) // dir /stats_five_minute par.setSubDir("stats_five_minute") bs.Bcache.FiveMin.Bypassed = par.readValue("bypassed") bs.Bcache.FiveMin.CacheHits = par.readValue("cache_hits") bs.Bcache.FiveMin.Bypassed = par.readValue("bypassed") bs.Bcache.FiveMin.CacheBypassHits = par.readValue("cache_bypass_hits") bs.Bcache.FiveMin.CacheBypassMisses = par.readValue("cache_bypass_misses") bs.Bcache.FiveMin.CacheHits = par.readValue("cache_hits") bs.Bcache.FiveMin.CacheMissCollisions = par.readValue("cache_miss_collisions") bs.Bcache.FiveMin.CacheMisses = par.readValue("cache_misses") bs.Bcache.FiveMin.CacheReadaheads = par.readValue("cache_readaheads") // dir /stats_total par.setSubDir("stats_total") bs.Bcache.Total.Bypassed = par.readValue("bypassed") bs.Bcache.Total.CacheHits = par.readValue("cache_hits") bs.Bcache.Total.Bypassed = par.readValue("bypassed") bs.Bcache.Total.CacheBypassHits = par.readValue("cache_bypass_hits") bs.Bcache.Total.CacheBypassMisses = par.readValue("cache_bypass_misses") bs.Bcache.Total.CacheHits = par.readValue("cache_hits") bs.Bcache.Total.CacheMissCollisions = par.readValue("cache_miss_collisions") bs.Bcache.Total.CacheMisses = par.readValue("cache_misses") bs.Bcache.Total.CacheReadaheads = par.readValue("cache_readaheads") if par.err != nil { return nil, par.err } // bdev stats reg := path.Join(uuidPath, "bdev[0-9]*") bdevDirs, err := filepath.Glob(reg) if err != nil { return nil, err } bs.Bdevs = make([]BdevStats, len(bdevDirs)) for ii, bdevDir := range bdevDirs { var bds = &bs.Bdevs[ii] bds.Name = filepath.Base(bdevDir) par.setSubDir(bds.Name) bds.DirtyData = par.readValue("dirty_data") // dir //stats_five_minute par.setSubDir(bds.Name, "stats_five_minute") bds.FiveMin.Bypassed = par.readValue("bypassed") bds.FiveMin.CacheBypassHits = par.readValue("cache_bypass_hits") bds.FiveMin.CacheBypassMisses = par.readValue("cache_bypass_misses") bds.FiveMin.CacheHits = par.readValue("cache_hits") bds.FiveMin.CacheMissCollisions = par.readValue("cache_miss_collisions") bds.FiveMin.CacheMisses = par.readValue("cache_misses") bds.FiveMin.CacheReadaheads = par.readValue("cache_readaheads") // dir //stats_total par.setSubDir("stats_total") bds.Total.Bypassed = par.readValue("bypassed") bds.Total.CacheBypassHits = par.readValue("cache_bypass_hits") bds.Total.CacheBypassMisses = par.readValue("cache_bypass_misses") bds.Total.CacheHits = par.readValue("cache_hits") bds.Total.CacheMissCollisions = par.readValue("cache_miss_collisions") bds.Total.CacheMisses = par.readValue("cache_misses") bds.Total.CacheReadaheads = par.readValue("cache_readaheads") } if par.err != nil { return nil, par.err } // cache stats reg = path.Join(uuidPath, "cache[0-9]*") cacheDirs, err := filepath.Glob(reg) if err != nil { return nil, err } bs.Caches = make([]CacheStats, len(cacheDirs)) for ii, cacheDir := range cacheDirs { var cs = &bs.Caches[ii] cs.Name = filepath.Base(cacheDir) // dir is / par.setSubDir(cs.Name) cs.IOErrors = par.readValue("io_errors") cs.MetadataWritten = par.readValue("metadata_written") cs.Written = par.readValue("written") ps := par.getPriorityStats() cs.Priority = ps } if par.err != nil { return nil, par.err } return &bs, nil } golang-procfs-0+git20170703.e645f4e/bcache/get_test.go000066400000000000000000000047651312641423200220240ustar00rootroot00000000000000// Copyright 2017 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. package bcache import ( "testing" "math" ) func TestDehumanizeTests(t *testing.T) { dehumanizeTests := []struct { in []byte out uint64 invalid bool }{ { in: []byte("542k"), out: 555008, }, { in: []byte("322M"), out: 337641472, }, { in: []byte("1.1k"), out: 1124, }, { in: []byte("1.9k"), out: 1924, }, { in: []byte("1.10k"), out: 2024, }, { in: []byte(""), out: 0, invalid: true, }, } for _, tst := range dehumanizeTests { got, err := dehumanize(tst.in) if tst.invalid && err == nil { t.Error("expected an error, but none occurred") } if !tst.invalid && err != nil { t.Errorf("unexpected error: %v", err) } if got != tst.out { t.Errorf("dehumanize: '%s', want %f, got %f", tst.in, tst.out, got) } } } func TestParsePseudoFloatTests(t *testing.T) { parsePseudoFloatTests := []struct { in string out float64 }{ { in: "1.1", out: float64(1.097656), }, { in: "1.9", out: float64(1.878906), }, { in: "1.10", out: float64(1.976562), }, } for _, tst := range parsePseudoFloatTests { got, err := parsePseudoFloat(tst.in) if err != nil || math.Abs(got - tst.out) > 0.0001 { t.Errorf("parsePseudoFloat: %s, want %f, got %f", tst.in, tst.out, got) } } } func TestPriorityStats(t *testing.T) { var want = PriorityStats{ UnusedPercent: 99, MetadataPercent: 5, } var ( in string gotErr error got PriorityStats ) in = "Metadata: 5%" gotErr = parsePriorityStats(in, &got) if gotErr != nil || got.MetadataPercent != want.MetadataPercent { t.Errorf("parsePriorityStats: '%s', want %f, got %f", in, want.MetadataPercent, got.MetadataPercent) } in = "Unused: 99%" gotErr = parsePriorityStats(in, &got) if gotErr != nil || got.UnusedPercent != want.UnusedPercent { t.Errorf("parsePriorityStats: '%s', want %f, got %f", in, want.UnusedPercent, got.UnusedPercent) } } golang-procfs-0+git20170703.e645f4e/buddyinfo.go000066400000000000000000000045771312641423200207650ustar00rootroot00000000000000// Copyright 2017 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. package procfs import ( "bufio" "fmt" "io" "os" "strconv" "strings" ) // A BuddyInfo is the details parsed from /proc/buddyinfo. // The data is comprised of an array of free fragments of each size. // The sizes are 2^n*PAGE_SIZE, where n is the array index. type BuddyInfo struct { Node string Zone string Sizes []float64 } // NewBuddyInfo reads the buddyinfo statistics. func NewBuddyInfo() ([]BuddyInfo, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return nil, err } return fs.NewBuddyInfo() } // NewBuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem. func (fs FS) NewBuddyInfo() ([]BuddyInfo, error) { file, err := os.Open(fs.Path("buddyinfo")) if err != nil { return nil, err } defer file.Close() return parseBuddyInfo(file) } func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) { var ( buddyInfo = []BuddyInfo{} scanner = bufio.NewScanner(r) bucketCount = -1 ) for scanner.Scan() { var err error line := scanner.Text() parts := strings.Fields(string(line)) if len(parts) < 4 { return nil, fmt.Errorf("invalid number of fields when parsing buddyinfo") } node := strings.TrimRight(parts[1], ",") zone := strings.TrimRight(parts[3], ",") arraySize := len(parts[4:]) if bucketCount == -1 { bucketCount = arraySize } else { if bucketCount != arraySize { return nil, fmt.Errorf("mismatch in number of buddyinfo buckets, previous count %d, new count %d", bucketCount, arraySize) } } sizes := make([]float64, arraySize) for i := 0; i < arraySize; i++ { sizes[i], err = strconv.ParseFloat(parts[i+4], 64) if err != nil { return nil, fmt.Errorf("invalid value in buddyinfo: %s", err) } } buddyInfo = append(buddyInfo, BuddyInfo{node, zone, sizes}) } return buddyInfo, scanner.Err() } golang-procfs-0+git20170703.e645f4e/buddyinfo_test.go000066400000000000000000000036501312641423200220130ustar00rootroot00000000000000// Copyright 2017 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. package procfs import ( "strings" "testing" ) func TestBuddyInfo(t *testing.T) { buddyInfo, err := FS("fixtures/buddyinfo/valid").NewBuddyInfo() if err != nil { t.Fatal(err) } if want, got := "DMA", buddyInfo[0].Zone; want != got { t.Errorf("want Node 0, Zone %s, got %s", want, got) } if want, got := "Normal", buddyInfo[2].Zone; want != got { t.Errorf("want Node 0, Zone %s, got %s", want, got) } if want, got := 4381.0, buddyInfo[2].Sizes[0]; want != got { t.Errorf("want Node 0, Zone Normal %f, got %f", want, got) } if want, got := 572.0, buddyInfo[1].Sizes[1]; want != got { t.Errorf("want Node 0, Zone DMA32 %f, got %f", want, got) } } func TestBuddyInfoShort(t *testing.T) { _, err := FS("fixtures/buddyinfo/short").NewBuddyInfo() if err == nil { t.Errorf("expected error, but none occurred") } if want, got := "invalid number of fields when parsing buddyinfo", err.Error(); want != got { t.Errorf("wrong error returned, wanted %q, got %q", want, got) } } func TestBuddyInfoSizeMismatch(t *testing.T) { _, err := FS("fixtures/buddyinfo/sizemismatch").NewBuddyInfo() if err == nil { t.Errorf("expected error, but none occurred") } if want, got := "mismatch in number of buddyinfo buckets", err.Error(); !strings.HasPrefix(got, want) { t.Errorf("wrong error returned, wanted prefix %q, got %q", want, got) } } golang-procfs-0+git20170703.e645f4e/doc.go000066400000000000000000000025041312641423200175330ustar00rootroot00000000000000// Copyright 2014 Prometheus Team // 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. // Package procfs provides functions to retrieve system, kernel and process // metrics from the pseudo-filesystem proc. // // Example: // // package main // // import ( // "fmt" // "log" // // "github.com/prometheus/procfs" // ) // // func main() { // p, err := procfs.Self() // if err != nil { // log.Fatalf("could not get process: %s", err) // } // // stat, err := p.NewStat() // if err != nil { // log.Fatalf("could not get process stat: %s", err) // } // // fmt.Printf("command: %s\n", stat.Comm) // fmt.Printf("cpu time: %fs\n", stat.CPUTime()) // fmt.Printf("vsize: %dB\n", stat.VirtualMemory()) // fmt.Printf("rss: %dB\n", stat.ResidentMemory()) // } // package procfs golang-procfs-0+git20170703.e645f4e/fixtures/000077500000000000000000000000001312641423200203075ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/000077500000000000000000000000001312641423200207645ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/cmdline000066400000000000000000000000201312641423200223120ustar00rootroot00000000000000vimtest.go+10golang-procfs-0+git20170703.e645f4e/fixtures/26231/comm000066400000000000000000000000041312641423200216340ustar00rootroot00000000000000vim golang-procfs-0+git20170703.e645f4e/fixtures/26231/exe000077700000000000000000000000001312641423200236412/usr/bin/vimustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/fd/000077500000000000000000000000001312641423200213555ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/fd/0000077700000000000000000000000001312641423200256102../../symlinktargets/abcustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/fd/1000077700000000000000000000000001312641423200256222../../symlinktargets/defustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/fd/10000077700000000000000000000000001312641423200257762../../symlinktargets/xyzustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/fd/2000077700000000000000000000000001312641423200256342../../symlinktargets/ghiustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/fd/3000077700000000000000000000000001312641423200257072../../symlinktargets/uvwustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26231/io000066400000000000000000000001641312641423200213170ustar00rootroot00000000000000rchar: 750339 wchar: 818609 syscr: 7405 syscw: 5245 read_bytes: 1024 write_bytes: 2048 cancelled_write_bytes: -1024 golang-procfs-0+git20170703.e645f4e/fixtures/26231/limits000066400000000000000000000022751312641423200222160ustar00rootroot00000000000000Limit Soft Limit Hard Limit Units Max cpu time unlimited unlimited seconds Max file size unlimited unlimited bytes Max data size unlimited unlimited bytes Max stack size 8388608 unlimited bytes Max core file size 0 unlimited bytes Max resident set unlimited unlimited bytes Max processes 62898 62898 processes Max open files 2048 4096 files Max locked memory 65536 65536 bytes Max address space unlimited unlimited bytes Max file locks unlimited unlimited locks Max pending signals 62898 62898 signals Max msgqueue size 819200 819200 bytes Max nice priority 0 0 Max realtime priority 0 0 Max realtime timeout unlimited unlimited us golang-procfs-0+git20170703.e645f4e/fixtures/26231/mountstats000066400000000000000000000017541312641423200231370ustar00rootroot00000000000000device rootfs mounted on / with fstype rootfs 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 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 sec: flavor=1,pseudoflavor=1 events: 52 226 0 0 1 13 398 0 0 331 0 47 0 0 77 0 0 77 0 0 0 0 0 0 0 0 0 bytes: 1207640230 0 0 0 1210214218 0 295483 0 RPC iostats version: 1.0 p/v: 100003/4 (nfs) xprt: tcp 832 0 1 0 11 6428 6428 0 12154 0 24 26 5726 per-op statistics NULL: 0 0 0 0 0 0 0 0 READ: 1298 1298 0 207680 1210292152 6 79386 79407 WRITE: 0 0 0 0 0 0 0 0 golang-procfs-0+git20170703.e645f4e/fixtures/26231/stat000066400000000000000000000005121312641423200216600ustar00rootroot0000000000000026231 (vim) R 5392 7446 5392 34835 7446 4218880 32533 309516 26 82 1677 44 158 99 20 0 1 0 82375 56274944 1981 18446744073709551615 4194304 6294284 140736914091744 140736914087944 139965136429984 0 0 12288 1870679807 0 0 0 17 0 0 0 31 0 0 8391624 8481048 16420864 140736914093252 140736914093279 140736914093279 140736914096107 0 golang-procfs-0+git20170703.e645f4e/fixtures/26232/000077500000000000000000000000001312641423200207655ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/cmdline000066400000000000000000000000001312641423200223110ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/comm000066400000000000000000000000101312641423200216320ustar00rootroot00000000000000ata_sff golang-procfs-0+git20170703.e645f4e/fixtures/26232/fd/000077500000000000000000000000001312641423200213565ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/fd/0000077700000000000000000000000001312641423200256112../../symlinktargets/abcustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/fd/1000077700000000000000000000000001312641423200256232../../symlinktargets/defustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/fd/2000077700000000000000000000000001312641423200256352../../symlinktargets/ghiustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/fd/3000077700000000000000000000000001312641423200257102../../symlinktargets/uvwustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/fd/4000077700000000000000000000000001312641423200257222../../symlinktargets/xyzustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/26232/limits000066400000000000000000000024531312641423200222150ustar00rootroot00000000000000Limit Soft Limit Hard Limit Units Max cpu time unlimited unlimited seconds Max file size unlimited unlimited bytes Max data size unlimited unlimited bytes Max stack size 8388608 unlimited bytes Max core file size 0 unlimited bytes Max resident set unlimited unlimited bytes Max processes 29436 29436 processes Max open files 1024 4096 files Max locked memory 65536 65536 bytes Max address space unlimited unlimited bytes Max file locks unlimited unlimited locks Max pending signals 29436 29436 signals Max msgqueue size 819200 819200 bytes Max nice priority 0 0 Max realtime priority 0 0 Max realtime timeout unlimited unlimited us golang-procfs-0+git20170703.e645f4e/fixtures/26232/stat000066400000000000000000000002531312641423200216630ustar00rootroot0000000000000033 (ata_sff) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744073709551615 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0 golang-procfs-0+git20170703.e645f4e/fixtures/584/000077500000000000000000000000001312641423200206275ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/584/stat000066400000000000000000000005251312641423200215270ustar00rootroot000000000000001020 ((a b ) ( c d) ) R 28378 1020 28378 34842 1020 4218880 286 0 0 0 0 0 0 0 20 0 1 0 10839175 10395648 155 18446744073709551615 4194304 4238788 140736466511168 140736466511168 140609271124624 0 0 0 0 0 0 0 17 5 0 0 0 0 0 6336016 6337300 25579520 140736466515030 140736466515061 140736466515061 140736466518002 0 #!/bin/cat /proc/self/stat golang-procfs-0+git20170703.e645f4e/fixtures/buddyinfo/000077500000000000000000000000001312641423200222725ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/buddyinfo/short/000077500000000000000000000000001312641423200234315ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/buddyinfo/short/buddyinfo000066400000000000000000000000471312641423200253400ustar00rootroot00000000000000Node 0, zone Node 0, zone Node 0, zone golang-procfs-0+git20170703.e645f4e/fixtures/buddyinfo/sizemismatch/000077500000000000000000000000001312641423200247725ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/buddyinfo/sizemismatch/buddyinfo000066400000000000000000000004521312641423200267010ustar00rootroot00000000000000Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3 Node 0, zone DMA32 759 572 791 475 194 45 12 0 0 0 0 0 Node 0, zone Normal 4381 1093 185 1530 567 102 4 0 0 0 golang-procfs-0+git20170703.e645f4e/fixtures/buddyinfo/valid/000077500000000000000000000000001312641423200233715ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/buddyinfo/valid/buddyinfo000066400000000000000000000004541312641423200253020ustar00rootroot00000000000000Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3 Node 0, zone DMA32 759 572 791 475 194 45 12 0 0 0 0 Node 0, zone Normal 4381 1093 185 1530 567 102 4 0 0 0 0 golang-procfs-0+git20170703.e645f4e/fixtures/fs/000077500000000000000000000000001312641423200207175ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/fs/xfs/000077500000000000000000000000001312641423200215175ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/fs/xfs/stat000066400000000000000000000013461312641423200224210ustar00rootroot00000000000000extent_alloc 92447 97589 92448 93751 abt 0 0 0 0 blk_map 1767055 188820 184891 92447 92448 2140766 0 bmbt 0 0 0 0 dir 185039 92447 92444 136422 trans 706 944304 0 ig 185045 58807 0 126238 0 33637 22 log 2883 113448 9 17360 739 push_ail 945014 0 134260 15483 0 3940 464 159985 0 40 xstrat 92447 0 rw 107739 94045 attr 4 0 0 0 icluster 8677 7849 135802 vnodes 92601 0 0 0 92444 92444 92444 0 buf 2666287 7122 2659202 3599 2 7085 0 10297 7085 abtb2 184941 1277345 13257 13278 0 0 0 0 0 0 0 0 0 0 2746147 abtc2 345295 2416764 172637 172658 0 0 0 0 0 0 0 0 0 0 21406023 bmbt2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ibt2 343004 1358467 0 0 0 0 0 0 0 0 0 0 0 0 0 fibt2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 qm 0 0 0 0 0 0 0 0 xpc 399724544 92823103 86219234 debug 0 golang-procfs-0+git20170703.e645f4e/fixtures/mdstat000066400000000000000000000020131312641423200215220ustar00rootroot00000000000000Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] md3 : active raid6 sda1[8] sdh1[7] sdg1[6] sdf1[5] sde1[11] sdd1[3] sdc1[10] sdb1[9] 5853468288 blocks super 1.2 level 6, 64k chunk, algorithm 2 [8/8] [UUUUUUUU] md127 : active raid1 sdi2[0] sdj2[1] 312319552 blocks [2/2] [UU] md0 : active raid1 sdk[2](S) sdi1[0] sdj1[1] 248896 blocks [2/2] [UU] md4 : inactive raid1 sda3[0] sdb3[1] 4883648 blocks [2/2] [UU] md6 : active raid1 sdb2[2] sda2[0] 195310144 blocks [2/1] [U_] [=>...................] recovery = 8.5% (16775552/195310144) finish=17.0min speed=259783K/sec md8 : active raid1 sdb1[1] sda1[0] 195310144 blocks [2/2] [UU] [=>...................] resync = 8.5% (16775552/195310144) finish=17.0min speed=259783K/sec md7 : active raid6 sdb1[0] sde1[3] sdd1[2] sdc1[1] 7813735424 blocks super 1.2 level 6, 512k chunk, algorithm 2 [4/3] [U_UU] bitmap: 0/30 pages [0KB], 65536KB chunk unused devices: golang-procfs-0+git20170703.e645f4e/fixtures/net/000077500000000000000000000000001312641423200210755ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/net/ip_vs000066400000000000000000000020441312641423200221400ustar00rootroot00000000000000IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP C0A80016:0CEA wlc -> C0A85216:0CEA Tunnel 100 248 2 -> C0A85318:0CEA Tunnel 100 248 2 -> C0A85315:0CEA Tunnel 100 248 1 TCP C0A80039:0CEA wlc -> C0A85416:0CEA Tunnel 0 0 0 -> C0A85215:0CEA Tunnel 100 1499 0 -> C0A83215:0CEA Tunnel 100 1498 0 TCP C0A80037:0CEA wlc -> C0A8321A:0CEA Tunnel 0 0 0 -> C0A83120:0CEA Tunnel 100 0 0 TCP [2620:0000:0000:0000:0000:0000:0000:0001]:0050 sh -> [2620:0000:0000:0000:0000:0000:0000:0002]:0050 Route 1 0 0 -> [2620:0000:0000:0000:0000:0000:0000:0003]:0050 Route 1 0 0 -> [2620:0000:0000:0000:0000:0000:0000:0004]:0050 Route 1 1 1 FWM 10001000 wlc -> C0A8321A:0CEA Route 0 0 1 -> C0A83215:0CEA Route 0 0 2 golang-procfs-0+git20170703.e645f4e/fixtures/net/ip_vs_stats000066400000000000000000000004621312641423200233600ustar00rootroot00000000000000 Total Incoming Outgoing Incoming Outgoing Conns Packets Packets Bytes Bytes 16AA370 E33656E5 0 51D8C8883AB3 0 Conns/s Pkts/s Pkts/s Bytes/s Bytes/s 4 1FB3C 0 1282A8F 0 golang-procfs-0+git20170703.e645f4e/fixtures/net/xfrm_stat000066400000000000000000000017731312641423200230370ustar00rootroot00000000000000XfrmInError 1 XfrmInBufferError 2 XfrmInHdrError 4 XfrmInNoStates 3 XfrmInStateProtoError 40 XfrmInStateModeError 100 XfrmInStateSeqError 6000 XfrmInStateExpired 4 XfrmInStateMismatch 23451 XfrmInStateInvalid 55555 XfrmInTmplMismatch 51 XfrmInNoPols 65432 XfrmInPolBlock 100 XfrmInPolError 10000 XfrmOutError 1000000 XfrmOutBundleGenError 43321 XfrmOutBundleCheckError 555 XfrmOutNoStates 869 XfrmOutStateProtoError 4542 XfrmOutStateModeError 4 XfrmOutStateSeqError 543 XfrmOutStateExpired 565 XfrmOutPolBlock 43456 XfrmOutPolDead 7656 XfrmOutPolError 1454 XfrmFwdHdrError 6654 XfrmOutStateInvalid 28765 XfrmAcquireError 24532 golang-procfs-0+git20170703.e645f4e/fixtures/self00007770000000000000000000000000131264142320021556226231ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/stat000066400000000000000000000040561312641423200212120ustar00rootroot00000000000000cpu 301854 612 111922 8979004 3552 2 3944 0 0 0 cpu0 44490 19 21045 1087069 220 1 3410 0 0 0 cpu1 47869 23 16474 1110787 591 0 46 0 0 0 cpu2 46504 36 15916 1112321 441 0 326 0 0 0 cpu3 47054 102 15683 1113230 533 0 60 0 0 0 cpu4 28413 25 10776 1140321 217 0 8 0 0 0 cpu5 29271 101 11586 1136270 672 0 30 0 0 0 cpu6 29152 36 10276 1139721 319 0 29 0 0 0 cpu7 29098 268 10164 1139282 555 0 31 0 0 0 intr 8885917 17 0 0 0 0 0 0 0 1 79281 0 0 0 0 0 0 0 231237 0 0 0 0 250586 103 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 223424 190745 13 906 1283803 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ctxt 38014093 btime 1418183276 processes 26442 procs_running 2 procs_blocked 1 softirq 5057579 250191 1481983 1647 211099 186066 0 1783454 622196 12499 508444 golang-procfs-0+git20170703.e645f4e/fixtures/symlinktargets/000077500000000000000000000000001312641423200233675ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/symlinktargets/README000066400000000000000000000002211312641423200242420ustar00rootroot00000000000000This directory contains some empty files that are the symlinks the files in the "fd" directory point to. They are otherwise ignored by the tests golang-procfs-0+git20170703.e645f4e/fixtures/symlinktargets/abc000066400000000000000000000000001312641423200240250ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/symlinktargets/def000066400000000000000000000000001312641423200240360ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/symlinktargets/ghi000066400000000000000000000000001312641423200240470ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/symlinktargets/uvw000066400000000000000000000000001312641423200241210ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fixtures/symlinktargets/xyz000066400000000000000000000000001312641423200241320ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/fs.go000066400000000000000000000021431312641423200173750ustar00rootroot00000000000000package procfs import ( "fmt" "os" "path" "github.com/prometheus/procfs/xfs" ) // FS represents the pseudo-filesystem proc, which provides an interface to // kernel data structures. type FS string // DefaultMountPoint is the common mount point of the proc filesystem. const DefaultMountPoint = "/proc" // NewFS returns a new FS mounted under the given mountPoint. It will error // if the mount point can't be read. func NewFS(mountPoint string) (FS, error) { info, err := os.Stat(mountPoint) if err != nil { return "", fmt.Errorf("could not read %s: %s", mountPoint, err) } if !info.IsDir() { return "", fmt.Errorf("mount point %s is not a directory", mountPoint) } return FS(mountPoint), nil } // Path returns the path of the given subsystem relative to the procfs root. func (fs FS) Path(p ...string) string { return path.Join(append([]string{string(fs)}, p...)...) } // XFSStats retrieves XFS filesystem runtime statistics. func (fs FS) XFSStats() (*xfs.Stats, error) { f, err := os.Open(fs.Path("fs/xfs/stat")) if err != nil { return nil, err } defer f.Close() return xfs.ParseStats(f) } golang-procfs-0+git20170703.e645f4e/fs_test.go000066400000000000000000000013211312641423200204310ustar00rootroot00000000000000package procfs import "testing" func TestNewFS(t *testing.T) { if _, err := NewFS("foobar"); err == nil { t.Error("want NewFS to fail for non-existing mount point") } if _, err := NewFS("procfs.go"); err == nil { t.Error("want NewFS to fail if mount point is not a directory") } } func TestFSXFSStats(t *testing.T) { stats, err := FS("fixtures").XFSStats() if err != nil { t.Fatalf("failed to parse XFS stats: %v", err) } // Very lightweight test just to sanity check the path used // to open XFS stats. Heavier tests in package xfs. if want, got := uint32(92447), stats.ExtentAllocation.ExtentsAllocated; want != got { t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) } } golang-procfs-0+git20170703.e645f4e/ipvs.go000066400000000000000000000134021312641423200177460ustar00rootroot00000000000000package procfs import ( "bufio" "encoding/hex" "errors" "fmt" "io" "io/ioutil" "net" "os" "strconv" "strings" ) // IPVSStats holds IPVS statistics, as exposed by the kernel in `/proc/net/ip_vs_stats`. type IPVSStats struct { // Total count of connections. Connections uint64 // Total incoming packages processed. IncomingPackets uint64 // Total outgoing packages processed. OutgoingPackets uint64 // Total incoming traffic. IncomingBytes uint64 // Total outgoing traffic. OutgoingBytes uint64 } // IPVSBackendStatus holds current metrics of one virtual / real address pair. type IPVSBackendStatus struct { // The local (virtual) IP address. LocalAddress net.IP // The local (virtual) port. LocalPort uint16 // The local firewall mark LocalMark string // The transport protocol (TCP, UDP). Proto string // The remote (real) IP address. RemoteAddress net.IP // The remote (real) port. RemotePort uint16 // The current number of active connections for this virtual/real address pair. ActiveConn uint64 // The current number of inactive connections for this virtual/real address pair. InactConn uint64 // The current weight of this virtual/real address pair. Weight uint64 } // NewIPVSStats reads the IPVS statistics. func NewIPVSStats() (IPVSStats, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return IPVSStats{}, err } return fs.NewIPVSStats() } // NewIPVSStats reads the IPVS statistics from the specified `proc` filesystem. func (fs FS) NewIPVSStats() (IPVSStats, error) { file, err := os.Open(fs.Path("net/ip_vs_stats")) if err != nil { return IPVSStats{}, err } defer file.Close() return parseIPVSStats(file) } // parseIPVSStats performs the actual parsing of `ip_vs_stats`. func parseIPVSStats(file io.Reader) (IPVSStats, error) { var ( statContent []byte statLines []string statFields []string stats IPVSStats ) statContent, err := ioutil.ReadAll(file) if err != nil { return IPVSStats{}, err } statLines = strings.SplitN(string(statContent), "\n", 4) if len(statLines) != 4 { return IPVSStats{}, errors.New("ip_vs_stats corrupt: too short") } statFields = strings.Fields(statLines[2]) if len(statFields) != 5 { return IPVSStats{}, errors.New("ip_vs_stats corrupt: unexpected number of fields") } stats.Connections, err = strconv.ParseUint(statFields[0], 16, 64) if err != nil { return IPVSStats{}, err } stats.IncomingPackets, err = strconv.ParseUint(statFields[1], 16, 64) if err != nil { return IPVSStats{}, err } stats.OutgoingPackets, err = strconv.ParseUint(statFields[2], 16, 64) if err != nil { return IPVSStats{}, err } stats.IncomingBytes, err = strconv.ParseUint(statFields[3], 16, 64) if err != nil { return IPVSStats{}, err } stats.OutgoingBytes, err = strconv.ParseUint(statFields[4], 16, 64) if err != nil { return IPVSStats{}, err } return stats, nil } // NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs. func NewIPVSBackendStatus() ([]IPVSBackendStatus, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return []IPVSBackendStatus{}, err } return fs.NewIPVSBackendStatus() } // NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem. func (fs FS) NewIPVSBackendStatus() ([]IPVSBackendStatus, error) { file, err := os.Open(fs.Path("net/ip_vs")) if err != nil { return nil, err } defer file.Close() return parseIPVSBackendStatus(file) } func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) { var ( status []IPVSBackendStatus scanner = bufio.NewScanner(file) proto string localMark string localAddress net.IP localPort uint16 err error ) for scanner.Scan() { fields := strings.Fields(string(scanner.Text())) if len(fields) == 0 { continue } switch { case fields[0] == "IP" || fields[0] == "Prot" || fields[1] == "RemoteAddress:Port": continue case fields[0] == "TCP" || fields[0] == "UDP": if len(fields) < 2 { continue } proto = fields[0] localMark = "" localAddress, localPort, err = parseIPPort(fields[1]) if err != nil { return nil, err } case fields[0] == "FWM": if len(fields) < 2 { continue } proto = fields[0] localMark = fields[1] localAddress = nil localPort = 0 case fields[0] == "->": if len(fields) < 6 { continue } remoteAddress, remotePort, err := parseIPPort(fields[1]) if err != nil { return nil, err } weight, err := strconv.ParseUint(fields[3], 10, 64) if err != nil { return nil, err } activeConn, err := strconv.ParseUint(fields[4], 10, 64) if err != nil { return nil, err } inactConn, err := strconv.ParseUint(fields[5], 10, 64) if err != nil { return nil, err } status = append(status, IPVSBackendStatus{ LocalAddress: localAddress, LocalPort: localPort, LocalMark: localMark, RemoteAddress: remoteAddress, RemotePort: remotePort, Proto: proto, Weight: weight, ActiveConn: activeConn, InactConn: inactConn, }) } } return status, nil } func parseIPPort(s string) (net.IP, uint16, error) { var ( ip net.IP err error ) switch len(s) { case 13: ip, err = hex.DecodeString(s[0:8]) if err != nil { return nil, 0, err } case 46: ip = net.ParseIP(s[1:40]) if ip == nil { return nil, 0, fmt.Errorf("invalid IPv6 address: %s", s[1:40]) } default: return nil, 0, fmt.Errorf("unexpected IP:Port: %s", s) } portString := s[len(s)-4:] if len(portString) != 4 { return nil, 0, fmt.Errorf("unexpected port string format: %s", portString) } port, err := strconv.ParseUint(portString, 16, 16) if err != nil { return nil, 0, err } return ip, uint16(port), nil } golang-procfs-0+git20170703.e645f4e/ipvs_test.go000066400000000000000000000136701312641423200210140ustar00rootroot00000000000000package procfs import ( "net" "testing" ) var ( expectedIPVSStats = IPVSStats{ Connections: 23765872, IncomingPackets: 3811989221, OutgoingPackets: 0, IncomingBytes: 89991519156915, OutgoingBytes: 0, } expectedIPVSBackendStatuses = []IPVSBackendStatus{ { LocalAddress: net.ParseIP("192.168.0.22"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.82.22"), RemotePort: 3306, Proto: "TCP", Weight: 100, ActiveConn: 248, InactConn: 2, }, { LocalAddress: net.ParseIP("192.168.0.22"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.83.24"), RemotePort: 3306, Proto: "TCP", Weight: 100, ActiveConn: 248, InactConn: 2, }, { LocalAddress: net.ParseIP("192.168.0.22"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.83.21"), RemotePort: 3306, Proto: "TCP", Weight: 100, ActiveConn: 248, InactConn: 1, }, { LocalAddress: net.ParseIP("192.168.0.57"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.84.22"), RemotePort: 3306, Proto: "TCP", Weight: 0, ActiveConn: 0, InactConn: 0, }, { LocalAddress: net.ParseIP("192.168.0.57"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.82.21"), RemotePort: 3306, Proto: "TCP", Weight: 100, ActiveConn: 1499, InactConn: 0, }, { LocalAddress: net.ParseIP("192.168.0.57"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.50.21"), RemotePort: 3306, Proto: "TCP", Weight: 100, ActiveConn: 1498, InactConn: 0, }, { LocalAddress: net.ParseIP("192.168.0.55"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.50.26"), RemotePort: 3306, Proto: "TCP", Weight: 0, ActiveConn: 0, InactConn: 0, }, { LocalAddress: net.ParseIP("192.168.0.55"), LocalPort: 3306, RemoteAddress: net.ParseIP("192.168.49.32"), RemotePort: 3306, Proto: "TCP", Weight: 100, ActiveConn: 0, InactConn: 0, }, { LocalAddress: net.ParseIP("2620::1"), LocalPort: 80, RemoteAddress: net.ParseIP("2620::2"), RemotePort: 80, Proto: "TCP", Weight: 1, ActiveConn: 0, InactConn: 0, }, { LocalAddress: net.ParseIP("2620::1"), LocalPort: 80, RemoteAddress: net.ParseIP("2620::3"), RemotePort: 80, Proto: "TCP", Weight: 1, ActiveConn: 0, InactConn: 0, }, { LocalAddress: net.ParseIP("2620::1"), LocalPort: 80, RemoteAddress: net.ParseIP("2620::4"), RemotePort: 80, Proto: "TCP", Weight: 1, ActiveConn: 1, InactConn: 1, }, { LocalMark: "10001000", RemoteAddress: net.ParseIP("192.168.50.26"), RemotePort: 3306, Proto: "FWM", Weight: 0, ActiveConn: 0, InactConn: 1, }, { LocalMark: "10001000", RemoteAddress: net.ParseIP("192.168.50.21"), RemotePort: 3306, Proto: "FWM", Weight: 0, ActiveConn: 0, InactConn: 2, }, } ) func TestIPVSStats(t *testing.T) { stats, err := FS("fixtures").NewIPVSStats() if err != nil { t.Fatal(err) } if stats != expectedIPVSStats { t.Errorf("want %+v, have %+v", expectedIPVSStats, stats) } } func TestParseIPPort(t *testing.T) { ip := net.ParseIP("192.168.0.22") port := uint16(3306) gotIP, gotPort, err := parseIPPort("C0A80016:0CEA") if err != nil { t.Fatal(err) } if !(gotIP.Equal(ip) && port == gotPort) { t.Errorf("want %s:%d, have %s:%d", ip, port, gotIP, gotPort) } } func TestParseIPPortInvalid(t *testing.T) { testcases := []string{ "", "C0A80016", "C0A800:1234", "FOOBARBA:1234", "C0A80016:0CEA:1234", } for _, s := range testcases { ip, port, err := parseIPPort(s) if ip != nil || port != uint16(0) || err == nil { t.Errorf("Expected error for input %s, have ip = %s, port = %v, err = %v", s, ip, port, err) } } } func TestParseIPPortIPv6(t *testing.T) { ip := net.ParseIP("dead:beef::1") port := uint16(8080) gotIP, gotPort, err := parseIPPort("[DEAD:BEEF:0000:0000:0000:0000:0000:0001]:1F90") if err != nil { t.Fatal(err) } if !(gotIP.Equal(ip) && port == gotPort) { t.Errorf("want %s:%d, have %s:%d", ip, port, gotIP, gotPort) } } func TestIPVSBackendStatus(t *testing.T) { backendStats, err := FS("fixtures").NewIPVSBackendStatus() if err != nil { t.Fatal(err) } if want, have := len(expectedIPVSBackendStatuses), len(backendStats); want != have { t.Fatalf("want %d backend statuses, have %d", want, have) } for idx, expect := range expectedIPVSBackendStatuses { if !backendStats[idx].LocalAddress.Equal(expect.LocalAddress) { t.Errorf("want LocalAddress %s, have %s", expect.LocalAddress, backendStats[idx].LocalAddress) } if backendStats[idx].LocalPort != expect.LocalPort { t.Errorf("want LocalPort %d, have %d", expect.LocalPort, backendStats[idx].LocalPort) } if !backendStats[idx].RemoteAddress.Equal(expect.RemoteAddress) { t.Errorf("want RemoteAddress %s, have %s", expect.RemoteAddress, backendStats[idx].RemoteAddress) } if backendStats[idx].RemotePort != expect.RemotePort { t.Errorf("want RemotePort %d, have %d", expect.RemotePort, backendStats[idx].RemotePort) } if backendStats[idx].Proto != expect.Proto { t.Errorf("want Proto %s, have %s", expect.Proto, backendStats[idx].Proto) } if backendStats[idx].Weight != expect.Weight { t.Errorf("want Weight %d, have %d", expect.Weight, backendStats[idx].Weight) } if backendStats[idx].ActiveConn != expect.ActiveConn { t.Errorf("want ActiveConn %d, have %d", expect.ActiveConn, backendStats[idx].ActiveConn) } if backendStats[idx].InactConn != expect.InactConn { t.Errorf("want InactConn %d, have %d", expect.InactConn, backendStats[idx].InactConn) } } } golang-procfs-0+git20170703.e645f4e/mdstat.go000066400000000000000000000070341312641423200202650ustar00rootroot00000000000000package procfs import ( "fmt" "io/ioutil" "regexp" "strconv" "strings" ) var ( statuslineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`) buildlineRE = regexp.MustCompile(`\((\d+)/\d+\)`) ) // MDStat holds info parsed from /proc/mdstat. type MDStat struct { // Name of the device. Name string // activity-state of the device. ActivityState string // Number of active disks. DisksActive int64 // Total number of disks the device consists of. DisksTotal int64 // Number of blocks the device holds. BlocksTotal int64 // Number of blocks on the device that are in sync. BlocksSynced int64 } // ParseMDStat parses an mdstat-file and returns a struct with the relevant infos. func (fs FS) ParseMDStat() (mdstates []MDStat, err error) { mdStatusFilePath := fs.Path("mdstat") content, err := ioutil.ReadFile(mdStatusFilePath) if err != nil { return []MDStat{}, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err) } mdStates := []MDStat{} lines := strings.Split(string(content), "\n") for i, l := range lines { if l == "" { continue } if l[0] == ' ' { continue } if strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") { continue } mainLine := strings.Split(l, " ") if len(mainLine) < 3 { return mdStates, fmt.Errorf("error parsing mdline: %s", l) } mdName := mainLine[0] activityState := mainLine[2] if len(lines) <= i+3 { return mdStates, fmt.Errorf( "error parsing %s: too few lines for md device %s", mdStatusFilePath, mdName, ) } active, total, size, err := evalStatusline(lines[i+1]) if err != nil { return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err) } // j is the line number of the syncing-line. j := i + 2 if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line j = i + 3 } // If device is syncing at the moment, get the number of currently // synced bytes, otherwise that number equals the size of the device. syncedBlocks := size if strings.Contains(lines[j], "recovery") || strings.Contains(lines[j], "resync") { syncedBlocks, err = evalBuildline(lines[j]) if err != nil { return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err) } } mdStates = append(mdStates, MDStat{ Name: mdName, ActivityState: activityState, DisksActive: active, DisksTotal: total, BlocksTotal: size, BlocksSynced: syncedBlocks, }) } return mdStates, nil } func evalStatusline(statusline string) (active, total, size int64, err error) { matches := statuslineRE.FindStringSubmatch(statusline) if len(matches) != 4 { return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline) } size, err = strconv.ParseInt(matches[1], 10, 64) if err != nil { return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err) } total, err = strconv.ParseInt(matches[2], 10, 64) if err != nil { return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err) } active, err = strconv.ParseInt(matches[3], 10, 64) if err != nil { return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err) } return active, total, size, nil } func evalBuildline(buildline string) (syncedBlocks int64, err error) { matches := buildlineRE.FindStringSubmatch(buildline) if len(matches) != 2 { return 0, fmt.Errorf("unexpected buildline: %s", buildline) } syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64) if err != nil { return 0, fmt.Errorf("%s in buildline: %s", err, buildline) } return syncedBlocks, nil } golang-procfs-0+git20170703.e645f4e/mdstat_test.go000066400000000000000000000016171312641423200213250ustar00rootroot00000000000000package procfs import ( "testing" ) func TestMDStat(t *testing.T) { mdStates, err := FS("fixtures").ParseMDStat() if err != nil { t.Fatalf("parsing of reference-file failed entirely: %s", err) } refs := map[string]MDStat{ "md3": {"md3", "active", 8, 8, 5853468288, 5853468288}, "md127": {"md127", "active", 2, 2, 312319552, 312319552}, "md0": {"md0", "active", 2, 2, 248896, 248896}, "md4": {"md4", "inactive", 2, 2, 4883648, 4883648}, "md6": {"md6", "active", 1, 2, 195310144, 16775552}, "md8": {"md8", "active", 2, 2, 195310144, 16775552}, "md7": {"md7", "active", 3, 4, 7813735424, 7813735424}, } if want, have := len(refs), len(mdStates); want != have { t.Errorf("want %d parsed md-devices, have %d", want, have) } for _, md := range mdStates { if want, have := refs[md.Name], md; want != have { t.Errorf("%s: want %v, have %v", md.Name, want, have) } } } golang-procfs-0+git20170703.e645f4e/mountstats.go000066400000000000000000000376031312641423200212170ustar00rootroot00000000000000package procfs // While implementing parsing of /proc/[pid]/mountstats, this blog was used // heavily as a reference: // https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex // // Special thanks to Chris Siebenmann for all of his posts explaining the // various statistics available for NFS. import ( "bufio" "fmt" "io" "strconv" "strings" "time" ) // Constants shared between multiple functions. const ( deviceEntryLen = 8 fieldBytesLen = 8 fieldEventsLen = 27 statVersion10 = "1.0" statVersion11 = "1.1" fieldTransport10Len = 10 fieldTransport11Len = 13 ) // A Mount is a device mount parsed from /proc/[pid]/mountstats. type Mount struct { // Name of the device. Device string // The mount point of the device. Mount string // The filesystem type used by the device. Type string // If available additional statistics related to this Mount. // Use a type assertion to determine if additional statistics are available. Stats MountStats } // A MountStats is a type which contains detailed statistics for a specific // type of Mount. type MountStats interface { mountStats() } // A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts. type MountStatsNFS struct { // The version of statistics provided. StatVersion string // The age of the NFS mount. Age time.Duration // Statistics related to byte counters for various operations. Bytes NFSBytesStats // Statistics related to various NFS event occurrences. Events NFSEventsStats // Statistics broken down by filesystem operation. Operations []NFSOperationStats // Statistics about the NFS RPC transport. Transport NFSTransportStats } // mountStats implements MountStats. func (m MountStatsNFS) mountStats() {} // A NFSBytesStats contains statistics about the number of bytes read and written // by an NFS client to and from an NFS server. type NFSBytesStats struct { // Number of bytes read using the read() syscall. Read uint64 // Number of bytes written using the write() syscall. Write uint64 // Number of bytes read using the read() syscall in O_DIRECT mode. DirectRead uint64 // Number of bytes written using the write() syscall in O_DIRECT mode. DirectWrite uint64 // Number of bytes read from the NFS server, in total. ReadTotal uint64 // Number of bytes written to the NFS server, in total. WriteTotal uint64 // Number of pages read directly via mmap()'d files. ReadPages uint64 // Number of pages written directly via mmap()'d files. WritePages uint64 } // A NFSEventsStats contains statistics about NFS event occurrences. type NFSEventsStats struct { // Number of times cached inode attributes are re-validated from the server. InodeRevalidate uint64 // Number of times cached dentry nodes are re-validated from the server. DnodeRevalidate uint64 // Number of times an inode cache is cleared. DataInvalidate uint64 // Number of times cached inode attributes are invalidated. AttributeInvalidate uint64 // Number of times files or directories have been open()'d. VFSOpen uint64 // Number of times a directory lookup has occurred. VFSLookup uint64 // Number of times permissions have been checked. VFSAccess uint64 // Number of updates (and potential writes) to pages. VFSUpdatePage uint64 // Number of pages read directly via mmap()'d files. VFSReadPage uint64 // Number of times a group of pages have been read. VFSReadPages uint64 // Number of pages written directly via mmap()'d files. VFSWritePage uint64 // Number of times a group of pages have been written. VFSWritePages uint64 // Number of times directory entries have been read with getdents(). VFSGetdents uint64 // Number of times attributes have been set on inodes. VFSSetattr uint64 // Number of pending writes that have been forcefully flushed to the server. VFSFlush uint64 // Number of times fsync() has been called on directories and files. VFSFsync uint64 // Number of times locking has been attempted on a file. VFSLock uint64 // Number of times files have been closed and released. VFSFileRelease uint64 // Unknown. Possibly unused. CongestionWait uint64 // Number of times files have been truncated. Truncation uint64 // Number of times a file has been grown due to writes beyond its existing end. WriteExtension uint64 // Number of times a file was removed while still open by another process. SillyRename uint64 // Number of times the NFS server gave less data than expected while reading. ShortRead uint64 // Number of times the NFS server wrote less data than expected while writing. ShortWrite uint64 // Number of times the NFS server indicated EJUKEBOX; retrieving data from // offline storage. JukeboxDelay uint64 // Number of NFS v4.1+ pNFS reads. PNFSRead uint64 // Number of NFS v4.1+ pNFS writes. PNFSWrite uint64 } // A NFSOperationStats contains statistics for a single operation. type NFSOperationStats struct { // The name of the operation. Operation string // Number of requests performed for this operation. Requests uint64 // Number of times an actual RPC request has been transmitted for this operation. Transmissions uint64 // Number of times a request has had a major timeout. MajorTimeouts uint64 // Number of bytes sent for this operation, including RPC headers and payload. BytesSent uint64 // Number of bytes received for this operation, including RPC headers and payload. BytesReceived uint64 // Duration all requests spent queued for transmission before they were sent. CumulativeQueueTime time.Duration // Duration it took to get a reply back after the request was transmitted. CumulativeTotalResponseTime time.Duration // Duration from when a request was enqueued to when it was completely handled. CumulativeTotalRequestTime time.Duration } // A NFSTransportStats contains statistics for the NFS mount RPC requests and // responses. type NFSTransportStats struct { // The local port used for the NFS mount. Port uint64 // Number of times the client has had to establish a connection from scratch // to the NFS server. Bind uint64 // Number of times the client has made a TCP connection to the NFS server. Connect uint64 // Duration (in jiffies, a kernel internal unit of time) the NFS mount has // spent waiting for connections to the server to be established. ConnectIdleTime uint64 // Duration since the NFS mount last saw any RPC traffic. IdleTime time.Duration // Number of RPC requests for this mount sent to the NFS server. Sends uint64 // Number of RPC responses for this mount received from the NFS server. Receives uint64 // Number of times the NFS server sent a response with a transaction ID // unknown to this client. BadTransactionIDs uint64 // A running counter, incremented on each request as the current difference // ebetween sends and receives. CumulativeActiveRequests uint64 // A running counter, incremented on each request by the current backlog // queue size. CumulativeBacklog uint64 // Stats below only available with stat version 1.1. // Maximum number of simultaneously active RPC requests ever used. MaximumRPCSlotsUsed uint64 // A running counter, incremented on each request as the current size of the // sending queue. CumulativeSendingQueue uint64 // A running counter, incremented on each request as the current size of the // pending queue. CumulativePendingQueue uint64 } // parseMountStats parses a /proc/[pid]/mountstats file and returns a slice // of Mount structures containing detailed information about each mount. // If available, statistics for each mount are parsed as well. func parseMountStats(r io.Reader) ([]*Mount, error) { const ( device = "device" statVersionPrefix = "statvers=" nfs3Type = "nfs" nfs4Type = "nfs4" ) var mounts []*Mount s := bufio.NewScanner(r) for s.Scan() { // Only look for device entries in this function ss := strings.Fields(string(s.Bytes())) if len(ss) == 0 || ss[0] != device { continue } m, err := parseMount(ss) if err != nil { return nil, err } // Does this mount also possess statistics information? if len(ss) > deviceEntryLen { // Only NFSv3 and v4 are supported for parsing statistics if m.Type != nfs3Type && m.Type != nfs4Type { return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type) } statVersion := strings.TrimPrefix(ss[8], statVersionPrefix) stats, err := parseMountStatsNFS(s, statVersion) if err != nil { return nil, err } m.Stats = stats } mounts = append(mounts, m) } return mounts, s.Err() } // parseMount parses an entry in /proc/[pid]/mountstats in the format: // device [device] mounted on [mount] with fstype [type] func parseMount(ss []string) (*Mount, error) { if len(ss) < deviceEntryLen { return nil, fmt.Errorf("invalid device entry: %v", ss) } // Check for specific words appearing at specific indices to ensure // the format is consistent with what we expect format := []struct { i int s string }{ {i: 0, s: "device"}, {i: 2, s: "mounted"}, {i: 3, s: "on"}, {i: 5, s: "with"}, {i: 6, s: "fstype"}, } for _, f := range format { if ss[f.i] != f.s { return nil, fmt.Errorf("invalid device entry: %v", ss) } } return &Mount{ Device: ss[1], Mount: ss[4], Type: ss[7], }, nil } // parseMountStatsNFS parses a MountStatsNFS by scanning additional information // related to NFS statistics. func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) { // Field indicators for parsing specific types of data const ( fieldAge = "age:" fieldBytes = "bytes:" fieldEvents = "events:" fieldPerOpStats = "per-op" fieldTransport = "xprt:" ) stats := &MountStatsNFS{ StatVersion: statVersion, } for s.Scan() { ss := strings.Fields(string(s.Bytes())) if len(ss) == 0 { break } if len(ss) < 2 { return nil, fmt.Errorf("not enough information for NFS stats: %v", ss) } switch ss[0] { case fieldAge: // Age integer is in seconds d, err := time.ParseDuration(ss[1] + "s") if err != nil { return nil, err } stats.Age = d case fieldBytes: bstats, err := parseNFSBytesStats(ss[1:]) if err != nil { return nil, err } stats.Bytes = *bstats case fieldEvents: estats, err := parseNFSEventsStats(ss[1:]) if err != nil { return nil, err } stats.Events = *estats case fieldTransport: if len(ss) < 3 { return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss) } tstats, err := parseNFSTransportStats(ss[2:], statVersion) if err != nil { return nil, err } stats.Transport = *tstats } // When encountering "per-operation statistics", we must break this // loop and parse them separately to ensure we can terminate parsing // before reaching another device entry; hence why this 'if' statement // is not just another switch case if ss[0] == fieldPerOpStats { break } } if err := s.Err(); err != nil { return nil, err } // NFS per-operation stats appear last before the next device entry perOpStats, err := parseNFSOperationStats(s) if err != nil { return nil, err } stats.Operations = perOpStats return stats, nil } // parseNFSBytesStats parses a NFSBytesStats line using an input set of // integer fields. func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) { if len(ss) != fieldBytesLen { return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss) } ns := make([]uint64, 0, fieldBytesLen) for _, s := range ss { n, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, err } ns = append(ns, n) } return &NFSBytesStats{ Read: ns[0], Write: ns[1], DirectRead: ns[2], DirectWrite: ns[3], ReadTotal: ns[4], WriteTotal: ns[5], ReadPages: ns[6], WritePages: ns[7], }, nil } // parseNFSEventsStats parses a NFSEventsStats line using an input set of // integer fields. func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) { if len(ss) != fieldEventsLen { return nil, fmt.Errorf("invalid NFS events stats: %v", ss) } ns := make([]uint64, 0, fieldEventsLen) for _, s := range ss { n, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, err } ns = append(ns, n) } return &NFSEventsStats{ InodeRevalidate: ns[0], DnodeRevalidate: ns[1], DataInvalidate: ns[2], AttributeInvalidate: ns[3], VFSOpen: ns[4], VFSLookup: ns[5], VFSAccess: ns[6], VFSUpdatePage: ns[7], VFSReadPage: ns[8], VFSReadPages: ns[9], VFSWritePage: ns[10], VFSWritePages: ns[11], VFSGetdents: ns[12], VFSSetattr: ns[13], VFSFlush: ns[14], VFSFsync: ns[15], VFSLock: ns[16], VFSFileRelease: ns[17], CongestionWait: ns[18], Truncation: ns[19], WriteExtension: ns[20], SillyRename: ns[21], ShortRead: ns[22], ShortWrite: ns[23], JukeboxDelay: ns[24], PNFSRead: ns[25], PNFSWrite: ns[26], }, nil } // parseNFSOperationStats parses a slice of NFSOperationStats by scanning // additional information about per-operation statistics until an empty // line is reached. func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) { const ( // Number of expected fields in each per-operation statistics set numFields = 9 ) var ops []NFSOperationStats for s.Scan() { ss := strings.Fields(string(s.Bytes())) if len(ss) == 0 { // Must break when reading a blank line after per-operation stats to // enable top-level function to parse the next device entry break } if len(ss) != numFields { return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss) } // Skip string operation name for integers ns := make([]uint64, 0, numFields-1) for _, st := range ss[1:] { n, err := strconv.ParseUint(st, 10, 64) if err != nil { return nil, err } ns = append(ns, n) } ops = append(ops, NFSOperationStats{ Operation: strings.TrimSuffix(ss[0], ":"), Requests: ns[0], Transmissions: ns[1], MajorTimeouts: ns[2], BytesSent: ns[3], BytesReceived: ns[4], CumulativeQueueTime: time.Duration(ns[5]) * time.Millisecond, CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond, CumulativeTotalRequestTime: time.Duration(ns[7]) * time.Millisecond, }) } return ops, s.Err() } // parseNFSTransportStats parses a NFSTransportStats line using an input set of // integer fields matched to a specific stats version. func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) { switch statVersion { case statVersion10: if len(ss) != fieldTransport10Len { return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss) } case statVersion11: if len(ss) != fieldTransport11Len { return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss) } default: return nil, fmt.Errorf("unrecognized NFS transport stats version: %q", statVersion) } // Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay // in a v1.0 response. // // Note: slice length must be set to length of v1.1 stats to avoid a panic when // only v1.0 stats are present. // See: https://github.com/prometheus/node_exporter/issues/571. ns := make([]uint64, fieldTransport11Len) for i, s := range ss { n, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, err } ns[i] = n } return &NFSTransportStats{ Port: ns[0], Bind: ns[1], Connect: ns[2], ConnectIdleTime: ns[3], IdleTime: time.Duration(ns[4]) * time.Second, Sends: ns[5], Receives: ns[6], BadTransactionIDs: ns[7], CumulativeActiveRequests: ns[8], CumulativeBacklog: ns[9], MaximumRPCSlotsUsed: ns[10], CumulativeSendingQueue: ns[11], CumulativePendingQueue: ns[12], }, nil } golang-procfs-0+git20170703.e645f4e/mountstats_test.go000066400000000000000000000155631312641423200222570ustar00rootroot00000000000000package procfs import ( "fmt" "reflect" "strings" "testing" "time" ) func TestMountStats(t *testing.T) { tests := []struct { name string s string mounts []*Mount invalid bool }{ { name: "no devices", s: `hello`, }, { name: "device has too few fields", s: `device foo`, invalid: true, }, { name: "device incorrect format", s: `device rootfs BAD on / with fstype rootfs`, invalid: true, }, { name: "device incorrect format", s: `device rootfs mounted BAD / with fstype rootfs`, invalid: true, }, { name: "device incorrect format", s: `device rootfs mounted on / BAD fstype rootfs`, invalid: true, }, { name: "device incorrect format", s: `device rootfs mounted on / with BAD rootfs`, invalid: true, }, { name: "device rootfs cannot have stats", s: `device rootfs mounted on / with fstype rootfs stats`, invalid: true, }, { name: "NFSv4 device with too little info", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nhello", invalid: true, }, { name: "NFSv4 device with bad bytes", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nbytes: 0", invalid: true, }, { name: "NFSv4 device with bad events", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nevents: 0", invalid: true, }, { name: "NFSv4 device with bad per-op stats", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nper-op statistics\nFOO 0", invalid: true, }, { name: "NFSv4 device with bad transport stats", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp", invalid: true, }, { name: "NFSv4 device with bad transport version", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=foo\nxprt: tcp 0", invalid: true, }, { name: "NFSv4 device with bad transport stats version 1.0", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.0\nxprt: tcp 0 0 0 0 0 0 0 0 0 0 0 0 0", invalid: true, }, { name: "NFSv4 device with bad transport stats version 1.1", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp 0 0 0 0 0 0 0 0 0 0", invalid: true, }, { name: "NFSv3 device with transport stats version 1.0 OK", s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.0\nxprt: tcp 1 2 3 4 5 6 7 8 9 10", mounts: []*Mount{{ Device: "192.168.1.1:/srv", Mount: "/mnt/nfs", Type: "nfs", Stats: &MountStatsNFS{ StatVersion: "1.0", Transport: NFSTransportStats{ Port: 1, Bind: 2, Connect: 3, ConnectIdleTime: 4, IdleTime: 5 * time.Second, Sends: 6, Receives: 7, BadTransactionIDs: 8, CumulativeActiveRequests: 9, CumulativeBacklog: 10, }, }, }}, }, { name: "device rootfs OK", s: `device rootfs mounted on / with fstype rootfs`, mounts: []*Mount{{ Device: "rootfs", Mount: "/", Type: "rootfs", }}, }, { name: "NFSv3 device with minimal stats OK", s: `device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1`, mounts: []*Mount{{ Device: "192.168.1.1:/srv", Mount: "/mnt/nfs", Type: "nfs", Stats: &MountStatsNFS{ StatVersion: "1.1", }, }}, }, { name: "fixtures OK", mounts: []*Mount{ { Device: "rootfs", Mount: "/", Type: "rootfs", }, { Device: "sysfs", Mount: "/sys", Type: "sysfs", }, { Device: "proc", Mount: "/proc", Type: "proc", }, { Device: "/dev/sda1", Mount: "/", Type: "ext4", }, { Device: "192.168.1.1:/srv/test", Mount: "/mnt/nfs/test", Type: "nfs4", Stats: &MountStatsNFS{ StatVersion: "1.1", Age: 13968 * time.Second, Bytes: NFSBytesStats{ Read: 1207640230, ReadTotal: 1210214218, ReadPages: 295483, }, Events: NFSEventsStats{ InodeRevalidate: 52, DnodeRevalidate: 226, VFSOpen: 1, VFSLookup: 13, VFSAccess: 398, VFSReadPages: 331, VFSWritePages: 47, VFSFlush: 77, VFSFileRelease: 77, }, Operations: []NFSOperationStats{ { Operation: "NULL", }, { Operation: "READ", Requests: 1298, Transmissions: 1298, BytesSent: 207680, BytesReceived: 1210292152, CumulativeQueueTime: 6 * time.Millisecond, CumulativeTotalResponseTime: 79386 * time.Millisecond, CumulativeTotalRequestTime: 79407 * time.Millisecond, }, { Operation: "WRITE", }, }, Transport: NFSTransportStats{ Port: 832, Connect: 1, IdleTime: 11 * time.Second, Sends: 6428, Receives: 6428, CumulativeActiveRequests: 12154, MaximumRPCSlotsUsed: 24, CumulativeSendingQueue: 26, CumulativePendingQueue: 5726, }, }, }, }, }, } for i, tt := range tests { t.Logf("[%02d] test %q", i, tt.name) var mounts []*Mount var err error if tt.s != "" { mounts, err = parseMountStats(strings.NewReader(tt.s)) } else { proc, e := FS("fixtures").NewProc(26231) if e != nil { t.Fatalf("failed to create proc: %v", err) } mounts, err = proc.MountStats() } if tt.invalid && err == nil { t.Error("expected an error, but none occurred") } if !tt.invalid && err != nil { t.Errorf("unexpected error: %v", err) } if want, have := tt.mounts, mounts; !reflect.DeepEqual(want, have) { t.Errorf("mounts:\nwant:\n%v\nhave:\n%v", mountsStr(want), mountsStr(have)) } } } func mountsStr(mounts []*Mount) string { var out string for i, m := range mounts { out += fmt.Sprintf("[%d] %q on %q (%q)", i, m.Device, m.Mount, m.Type) stats, ok := m.Stats.(*MountStatsNFS) if !ok { out += "\n" continue } 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) out += fmt.Sprintf("\n\t- transport: %v", stats.Transport) out += fmt.Sprintf("\n\t- per-operation stats:") for _, o := range stats.Operations { out += fmt.Sprintf("\n\t\t- %v", o) } out += "\n" } return out } golang-procfs-0+git20170703.e645f4e/proc.go000066400000000000000000000113521312641423200177320ustar00rootroot00000000000000package procfs import ( "fmt" "io/ioutil" "os" "strconv" "strings" ) // Proc provides information about a running process. type Proc struct { // The process ID. PID int fs FS } // Procs represents a list of Proc structs. type Procs []Proc func (p Procs) Len() int { return len(p) } func (p Procs) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p Procs) Less(i, j int) bool { return p[i].PID < p[j].PID } // Self returns a process for the current process read via /proc/self. func Self() (Proc, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return Proc{}, err } return fs.Self() } // NewProc returns a process for the given pid under /proc. func NewProc(pid int) (Proc, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return Proc{}, err } return fs.NewProc(pid) } // AllProcs returns a list of all currently available processes under /proc. func AllProcs() (Procs, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return Procs{}, err } return fs.AllProcs() } // Self returns a process for the current process. func (fs FS) Self() (Proc, error) { p, err := os.Readlink(fs.Path("self")) if err != nil { return Proc{}, err } pid, err := strconv.Atoi(strings.Replace(p, string(fs), "", -1)) if err != nil { return Proc{}, err } return fs.NewProc(pid) } // NewProc returns a process for the given pid. func (fs FS) NewProc(pid int) (Proc, error) { if _, err := os.Stat(fs.Path(strconv.Itoa(pid))); err != nil { return Proc{}, err } return Proc{PID: pid, fs: fs}, nil } // AllProcs returns a list of all currently available processes. func (fs FS) AllProcs() (Procs, error) { d, err := os.Open(fs.Path()) if err != nil { return Procs{}, err } defer d.Close() names, err := d.Readdirnames(-1) if err != nil { return Procs{}, fmt.Errorf("could not read %s: %s", d.Name(), err) } p := Procs{} for _, n := range names { pid, err := strconv.ParseInt(n, 10, 64) if err != nil { continue } p = append(p, Proc{PID: int(pid), fs: fs}) } return p, nil } // CmdLine returns the command line of a process. func (p Proc) CmdLine() ([]string, error) { f, err := os.Open(p.path("cmdline")) if err != nil { return nil, err } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return nil, err } if len(data) < 1 { return []string{}, nil } return strings.Split(string(data[:len(data)-1]), string(byte(0))), nil } // Comm returns the command name of a process. func (p Proc) Comm() (string, error) { f, err := os.Open(p.path("comm")) if err != nil { return "", err } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return "", err } return strings.TrimSpace(string(data)), nil } // Executable returns the absolute path of the executable command of a process. func (p Proc) Executable() (string, error) { exe, err := os.Readlink(p.path("exe")) if os.IsNotExist(err) { return "", nil } return exe, err } // FileDescriptors returns the currently open file descriptors of a process. func (p Proc) FileDescriptors() ([]uintptr, error) { names, err := p.fileDescriptors() if err != nil { return nil, err } fds := make([]uintptr, len(names)) for i, n := range names { fd, err := strconv.ParseInt(n, 10, 32) if err != nil { return nil, fmt.Errorf("could not parse fd %s: %s", n, err) } fds[i] = uintptr(fd) } return fds, nil } // FileDescriptorTargets returns the targets of all file descriptors of a process. // If a file descriptor is not a symlink to a file (like a socket), that value will be the empty string. func (p Proc) FileDescriptorTargets() ([]string, error) { names, err := p.fileDescriptors() if err != nil { return nil, err } targets := make([]string, len(names)) for i, name := range names { target, err := os.Readlink(p.path("fd", name)) if err == nil { targets[i] = target } } return targets, nil } // FileDescriptorsLen returns the number of currently open file descriptors of // a process. func (p Proc) FileDescriptorsLen() (int, error) { fds, err := p.fileDescriptors() if err != nil { return 0, err } return len(fds), nil } // MountStats retrieves statistics and configuration for mount points in a // process's namespace. func (p Proc) MountStats() ([]*Mount, error) { f, err := os.Open(p.path("mountstats")) if err != nil { return nil, err } defer f.Close() return parseMountStats(f) } func (p Proc) fileDescriptors() ([]string, error) { d, err := os.Open(p.path("fd")) if err != nil { return nil, err } defer d.Close() names, err := d.Readdirnames(-1) if err != nil { return nil, fmt.Errorf("could not read %s: %s", d.Name(), err) } return names, nil } func (p Proc) path(pa ...string) string { return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...) } golang-procfs-0+git20170703.e645f4e/proc_io.go000066400000000000000000000021611312641423200204170ustar00rootroot00000000000000package procfs import ( "fmt" "io/ioutil" "os" ) // ProcIO models the content of /proc//io. type ProcIO struct { // Chars read. RChar uint64 // Chars written. WChar uint64 // Read syscalls. SyscR uint64 // Write syscalls. SyscW uint64 // Bytes read. ReadBytes uint64 // Bytes written. WriteBytes uint64 // Bytes written, but taking into account truncation. See // Documentation/filesystems/proc.txt in the kernel sources for // detailed explanation. CancelledWriteBytes int64 } // NewIO creates a new ProcIO instance from a given Proc instance. func (p Proc) NewIO() (ProcIO, error) { pio := ProcIO{} f, err := os.Open(p.path("io")) if err != nil { return pio, err } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return pio, err } ioFormat := "rchar: %d\nwchar: %d\nsyscr: %d\nsyscw: %d\n" + "read_bytes: %d\nwrite_bytes: %d\n" + "cancelled_write_bytes: %d\n" _, err = fmt.Sscanf(string(data), ioFormat, &pio.RChar, &pio.WChar, &pio.SyscR, &pio.SyscW, &pio.ReadBytes, &pio.WriteBytes, &pio.CancelledWriteBytes) if err != nil { return pio, err } return pio, nil } golang-procfs-0+git20170703.e645f4e/proc_io_test.go000066400000000000000000000014441312641423200214610ustar00rootroot00000000000000package procfs import "testing" func TestProcIO(t *testing.T) { p, err := FS("fixtures").NewProc(26231) if err != nil { t.Fatal(err) } s, err := p.NewIO() if err != nil { t.Fatal(err) } for _, test := range []struct { name string want int64 have int64 }{ {name: "RChar", want: 750339, have: int64(s.RChar)}, {name: "WChar", want: 818609, have: int64(s.WChar)}, {name: "SyscR", want: 7405, have: int64(s.SyscR)}, {name: "SyscW", want: 5245, have: int64(s.SyscW)}, {name: "ReadBytes", want: 1024, have: int64(s.ReadBytes)}, {name: "WriteBytes", want: 2048, have: int64(s.WriteBytes)}, {name: "CancelledWriteBytes", want: -1024, have: s.CancelledWriteBytes}, } { if test.want != test.have { t.Errorf("want %s %d, have %d", test.name, test.want, test.have) } } } golang-procfs-0+git20170703.e645f4e/proc_limits.go000066400000000000000000000075271312641423200213240ustar00rootroot00000000000000package procfs import ( "bufio" "fmt" "os" "regexp" "strconv" ) // ProcLimits represents the soft limits for each of the process's resource // limits. For more information see getrlimit(2): // http://man7.org/linux/man-pages/man2/getrlimit.2.html. type ProcLimits struct { // CPU time limit in seconds. CPUTime int // Maximum size of files that the process may create. FileSize int // Maximum size of the process's data segment (initialized data, // uninitialized data, and heap). DataSize int // Maximum size of the process stack in bytes. StackSize int // Maximum size of a core file. CoreFileSize int // Limit of the process's resident set in pages. ResidentSet int // Maximum number of processes that can be created for the real user ID of // the calling process. Processes int // Value one greater than the maximum file descriptor number that can be // opened by this process. OpenFiles int // Maximum number of bytes of memory that may be locked into RAM. LockedMemory int // Maximum size of the process's virtual memory address space in bytes. AddressSpace int // Limit on the combined number of flock(2) locks and fcntl(2) leases that // this process may establish. FileLocks int // Limit of signals that may be queued for the real user ID of the calling // process. PendingSignals int // Limit on the number of bytes that can be allocated for POSIX message // queues for the real user ID of the calling process. MsqqueueSize int // Limit of the nice priority set using setpriority(2) or nice(2). NicePriority int // Limit of the real-time priority set using sched_setscheduler(2) or // sched_setparam(2). RealtimePriority int // Limit (in microseconds) on the amount of CPU time that a process // scheduled under a real-time scheduling policy may consume without making // a blocking system call. RealtimeTimeout int } const ( limitsFields = 3 limitsUnlimited = "unlimited" ) var ( limitsDelimiter = regexp.MustCompile(" +") ) // NewLimits returns the current soft limits of the process. func (p Proc) NewLimits() (ProcLimits, error) { f, err := os.Open(p.path("limits")) if err != nil { return ProcLimits{}, err } defer f.Close() var ( l = ProcLimits{} s = bufio.NewScanner(f) ) for s.Scan() { fields := limitsDelimiter.Split(s.Text(), limitsFields) if len(fields) != limitsFields { return ProcLimits{}, fmt.Errorf( "couldn't parse %s line %s", f.Name(), s.Text()) } switch fields[0] { case "Max cpu time": l.CPUTime, err = parseInt(fields[1]) case "Max file size": l.FileSize, err = parseInt(fields[1]) case "Max data size": l.DataSize, err = parseInt(fields[1]) case "Max stack size": l.StackSize, err = parseInt(fields[1]) case "Max core file size": l.CoreFileSize, err = parseInt(fields[1]) case "Max resident set": l.ResidentSet, err = parseInt(fields[1]) case "Max processes": l.Processes, err = parseInt(fields[1]) case "Max open files": l.OpenFiles, err = parseInt(fields[1]) case "Max locked memory": l.LockedMemory, err = parseInt(fields[1]) case "Max address space": l.AddressSpace, err = parseInt(fields[1]) case "Max file locks": l.FileLocks, err = parseInt(fields[1]) case "Max pending signals": l.PendingSignals, err = parseInt(fields[1]) case "Max msgqueue size": l.MsqqueueSize, err = parseInt(fields[1]) case "Max nice priority": l.NicePriority, err = parseInt(fields[1]) case "Max realtime priority": l.RealtimePriority, err = parseInt(fields[1]) case "Max realtime timeout": l.RealtimeTimeout, err = parseInt(fields[1]) } if err != nil { return ProcLimits{}, err } } return l, s.Err() } func parseInt(s string) (int, error) { if s == limitsUnlimited { return -1, nil } i, err := strconv.ParseInt(s, 10, 32) if err != nil { return 0, fmt.Errorf("couldn't parse value %s: %s", s, err) } return int(i), nil } golang-procfs-0+git20170703.e645f4e/proc_limits_test.go000066400000000000000000000012441312641423200223510ustar00rootroot00000000000000package procfs import "testing" func TestNewLimits(t *testing.T) { p, err := FS("fixtures").NewProc(26231) if err != nil { t.Fatal(err) } l, err := p.NewLimits() if err != nil { t.Fatal(err) } for _, test := range []struct { name string want int have int }{ {name: "cpu time", want: -1, have: l.CPUTime}, {name: "open files", want: 2048, have: l.OpenFiles}, {name: "msgqueue size", want: 819200, have: l.MsqqueueSize}, {name: "nice priority", want: 0, have: l.NicePriority}, {name: "address space", want: -1, have: l.AddressSpace}, } { if test.want != test.have { t.Errorf("want %s %d, have %d", test.name, test.want, test.have) } } } golang-procfs-0+git20170703.e645f4e/proc_stat.go000066400000000000000000000111051312641423200207610ustar00rootroot00000000000000package procfs import ( "bytes" "fmt" "io/ioutil" "os" ) // Originally, this USER_HZ value was dynamically retrieved via a sysconf call // which required cgo. However, that caused a lot of problems regarding // cross-compilation. Alternatives such as running a binary to determine the // value, or trying to derive it in some other way were all problematic. After // much research it was determined that USER_HZ is actually hardcoded to 100 on // all Go-supported platforms as of the time of this writing. This is why we // decided to hardcode it here as well. It is not impossible that there could // be systems with exceptions, but they should be very exotic edge cases, and // in that case, the worst outcome will be two misreported metrics. // // See also the following discussions: // // - https://github.com/prometheus/node_exporter/issues/52 // - https://github.com/prometheus/procfs/pull/2 // - http://stackoverflow.com/questions/17410841/how-does-user-hz-solve-the-jiffy-scaling-issue const userHZ = 100 // ProcStat provides status information about the process, // read from /proc/[pid]/stat. type ProcStat struct { // The process ID. PID int // The filename of the executable. Comm string // The process state. State string // The PID of the parent of this process. PPID int // The process group ID of the process. PGRP int // The session ID of the process. Session int // The controlling terminal of the process. TTY int // The ID of the foreground process group of the controlling terminal of // the process. TPGID int // The kernel flags word of the process. Flags uint // The number of minor faults the process has made which have not required // loading a memory page from disk. MinFlt uint // The number of minor faults that the process's waited-for children have // made. CMinFlt uint // The number of major faults the process has made which have required // loading a memory page from disk. MajFlt uint // The number of major faults that the process's waited-for children have // made. CMajFlt uint // Amount of time that this process has been scheduled in user mode, // measured in clock ticks. UTime uint // Amount of time that this process has been scheduled in kernel mode, // measured in clock ticks. STime uint // Amount of time that this process's waited-for children have been // scheduled in user mode, measured in clock ticks. CUTime uint // Amount of time that this process's waited-for children have been // scheduled in kernel mode, measured in clock ticks. CSTime uint // For processes running a real-time scheduling policy, this is the negated // scheduling priority, minus one. Priority int // The nice value, a value in the range 19 (low priority) to -20 (high // priority). Nice int // Number of threads in this process. NumThreads int // The time the process started after system boot, the value is expressed // in clock ticks. Starttime uint64 // Virtual memory size in bytes. VSize int // Resident set size in pages. RSS int fs FS } // NewStat returns the current status information of the process. func (p Proc) NewStat() (ProcStat, error) { f, err := os.Open(p.path("stat")) if err != nil { return ProcStat{}, err } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return ProcStat{}, err } var ( ignore int s = ProcStat{PID: p.PID, fs: p.fs} l = bytes.Index(data, []byte("(")) r = bytes.LastIndex(data, []byte(")")) ) if l < 0 || r < 0 { return ProcStat{}, fmt.Errorf( "unexpected format, couldn't extract comm: %s", data, ) } s.Comm = string(data[l+1 : r]) _, err = fmt.Fscan( bytes.NewBuffer(data[r+2:]), &s.State, &s.PPID, &s.PGRP, &s.Session, &s.TTY, &s.TPGID, &s.Flags, &s.MinFlt, &s.CMinFlt, &s.MajFlt, &s.CMajFlt, &s.UTime, &s.STime, &s.CUTime, &s.CSTime, &s.Priority, &s.Nice, &s.NumThreads, &ignore, &s.Starttime, &s.VSize, &s.RSS, ) if err != nil { return ProcStat{}, err } return s, nil } // VirtualMemory returns the virtual memory size in bytes. func (s ProcStat) VirtualMemory() int { return s.VSize } // ResidentMemory returns the resident memory size in bytes. func (s ProcStat) ResidentMemory() int { return s.RSS * os.Getpagesize() } // StartTime returns the unix timestamp of the process in seconds. func (s ProcStat) StartTime() (float64, error) { stat, err := s.fs.NewStat() if err != nil { return 0, err } return float64(stat.BootTime) + (float64(s.Starttime) / userHZ), nil } // CPUTime returns the total CPU user and system time in seconds. func (s ProcStat) CPUTime() float64 { return float64(s.UTime+s.STime) / userHZ } golang-procfs-0+git20170703.e645f4e/proc_stat_test.go000066400000000000000000000043441312641423200220270ustar00rootroot00000000000000package procfs import ( "os" "testing" ) func TestProcStat(t *testing.T) { p, err := FS("fixtures").NewProc(26231) if err != nil { t.Fatal(err) } s, err := p.NewStat() if err != nil { t.Fatal(err) } for _, test := range []struct { name string want int have int }{ {name: "pid", want: 26231, have: s.PID}, {name: "user time", want: 1677, have: int(s.UTime)}, {name: "system time", want: 44, have: int(s.STime)}, {name: "start time", want: 82375, have: int(s.Starttime)}, {name: "virtual memory size", want: 56274944, have: s.VSize}, {name: "resident set size", want: 1981, have: s.RSS}, } { if test.want != test.have { t.Errorf("want %s %d, have %d", test.name, test.want, test.have) } } } func TestProcStatComm(t *testing.T) { s1, err := testProcStat(26231) if err != nil { t.Fatal(err) } if want, have := "vim", s1.Comm; want != have { t.Errorf("want comm %s, have %s", want, have) } s2, err := testProcStat(584) if err != nil { t.Fatal(err) } if want, have := "(a b ) ( c d) ", s2.Comm; want != have { t.Errorf("want comm %s, have %s", want, have) } } func TestProcStatVirtualMemory(t *testing.T) { s, err := testProcStat(26231) if err != nil { t.Fatal(err) } if want, have := 56274944, s.VirtualMemory(); want != have { t.Errorf("want virtual memory %d, have %d", want, have) } } func TestProcStatResidentMemory(t *testing.T) { s, err := testProcStat(26231) if err != nil { t.Fatal(err) } if want, have := 1981*os.Getpagesize(), s.ResidentMemory(); want != have { t.Errorf("want resident memory %d, have %d", want, have) } } func TestProcStatStartTime(t *testing.T) { s, err := testProcStat(26231) if err != nil { t.Fatal(err) } time, err := s.StartTime() if err != nil { t.Fatal(err) } if want, have := 1418184099.75, time; want != have { t.Errorf("want start time %f, have %f", want, have) } } func TestProcStatCPUTime(t *testing.T) { s, err := testProcStat(26231) if err != nil { t.Fatal(err) } if want, have := 17.21, s.CPUTime(); want != have { t.Errorf("want cpu time %f, have %f", want, have) } } func testProcStat(pid int) (ProcStat, error) { p, err := FS("fixtures").NewProc(pid) if err != nil { return ProcStat{}, err } return p.NewStat() } golang-procfs-0+git20170703.e645f4e/proc_test.go000066400000000000000000000062751312641423200210010ustar00rootroot00000000000000package procfs import ( "reflect" "sort" "testing" ) func TestSelf(t *testing.T) { fs := FS("fixtures") p1, err := fs.NewProc(26231) if err != nil { t.Fatal(err) } p2, err := fs.Self() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(p1, p2) { t.Errorf("want process %v, have %v", p1, p2) } } func TestAllProcs(t *testing.T) { procs, err := FS("fixtures").AllProcs() if err != nil { t.Fatal(err) } sort.Sort(procs) for i, p := range []*Proc{{PID: 584}, {PID: 26231}} { if want, have := p.PID, procs[i].PID; want != have { t.Errorf("want processes %d, have %d", want, have) } } } func TestCmdLine(t *testing.T) { for _, tt := range []struct { process int want []string }{ {process: 26231, want: []string{"vim", "test.go", "+10"}}, {process: 26232, want: []string{}}, } { p1, err := FS("fixtures").NewProc(tt.process) if err != nil { t.Fatal(err) } c1, err := p1.CmdLine() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(tt.want, c1) { t.Errorf("want cmdline %v, have %v", tt.want, c1) } } } func TestComm(t *testing.T) { for _, tt := range []struct { process int want string }{ {process: 26231, want: "vim"}, {process: 26232, want: "ata_sff"}, } { p1, err := FS("fixtures").NewProc(tt.process) if err != nil { t.Fatal(err) } c1, err := p1.Comm() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(tt.want, c1) { t.Errorf("want comm %v, have %v", tt.want, c1) } } } func TestExecutable(t *testing.T) { for _, tt := range []struct { process int want string }{ {process: 26231, want: "/usr/bin/vim"}, {process: 26232, want: ""}, } { p, err := FS("fixtures").NewProc(tt.process) if err != nil { t.Fatal(err) } exe, err := p.Executable() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(tt.want, exe) { t.Errorf("want absolute path to cmdline %v, have %v", tt.want, exe) } } } func TestFileDescriptors(t *testing.T) { p1, err := FS("fixtures").NewProc(26231) if err != nil { t.Fatal(err) } fds, err := p1.FileDescriptors() if err != nil { t.Fatal(err) } sort.Sort(byUintptr(fds)) if want := []uintptr{0, 1, 2, 3, 10}; !reflect.DeepEqual(want, fds) { t.Errorf("want fds %v, have %v", want, fds) } } func TestFileDescriptorTargets(t *testing.T) { p1, err := FS("fixtures").NewProc(26231) if err != nil { t.Fatal(err) } fds, err := p1.FileDescriptorTargets() if err != nil { t.Fatal(err) } sort.Strings(fds) var want = []string{ "../../symlinktargets/abc", "../../symlinktargets/def", "../../symlinktargets/ghi", "../../symlinktargets/uvw", "../../symlinktargets/xyz", } if !reflect.DeepEqual(want, fds) { t.Errorf("want fds %v, have %v", want, fds) } } func TestFileDescriptorsLen(t *testing.T) { p1, err := FS("fixtures").NewProc(26231) if err != nil { t.Fatal(err) } l, err := p1.FileDescriptorsLen() if err != nil { t.Fatal(err) } if want, have := 5, l; want != have { t.Errorf("want fds %d, have %d", want, have) } } type byUintptr []uintptr func (a byUintptr) Len() int { return len(a) } func (a byUintptr) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byUintptr) Less(i, j int) bool { return a[i] < a[j] } golang-procfs-0+git20170703.e645f4e/stat.go000066400000000000000000000141241312641423200177420ustar00rootroot00000000000000package procfs import ( "bufio" "fmt" "io" "os" "strconv" "strings" ) // CPUStat shows how much time the cpu spend in various stages. type CPUStat struct { User float64 Nice float64 System float64 Idle float64 Iowait float64 IRQ float64 SoftIRQ float64 Steal float64 Guest float64 GuestNice float64 } // SoftIRQStat represent the softirq statistics as exported in the procfs stat file. // A nice introduction can be found at https://0xax.gitbooks.io/linux-insides/content/interrupts/interrupts-9.html // It is possible to get per-cpu stats by reading /proc/softirqs type SoftIRQStat struct { Hi uint64 Timer uint64 NetTx uint64 NetRx uint64 Block uint64 BlockIoPoll uint64 Tasklet uint64 Sched uint64 Hrtimer uint64 Rcu uint64 } // Stat represents kernel/system statistics. type Stat struct { // Boot time in seconds since the Epoch. BootTime uint64 // Summed up cpu statistics. CPUTotal CPUStat // Per-CPU statistics. CPU []CPUStat // Number of times interrupts were handled, which contains numbered and unnumbered IRQs. IRQTotal uint64 // Number of times a numbered IRQ was triggered. IRQ []uint64 // Number of times a context switch happened. ContextSwitches uint64 // Number of times a process was created. ProcessCreated uint64 // Number of processes currently running. ProcessesRunning uint64 // Number of processes currently blocked (waiting for IO). ProcessesBlocked uint64 // Number of times a softirq was scheduled. SoftIRQTotal uint64 // Detailed softirq statistics. SoftIRQ SoftIRQStat } // NewStat returns kernel/system statistics read from /proc/stat. func NewStat() (Stat, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return Stat{}, err } return fs.NewStat() } // Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum). func parseCPUStat(line string) (CPUStat, int64, error) { cpuStat := CPUStat{} var cpu string count, err := fmt.Sscanf(line, "%s %f %f %f %f %f %f %f %f %f %f", &cpu, &cpuStat.User, &cpuStat.Nice, &cpuStat.System, &cpuStat.Idle, &cpuStat.Iowait, &cpuStat.IRQ, &cpuStat.SoftIRQ, &cpuStat.Steal, &cpuStat.Guest, &cpuStat.GuestNice) if err != nil && err != io.EOF { return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): %s", line, err) } if count == 0 { return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): 0 elements parsed", line) } cpuStat.User /= userHZ cpuStat.Nice /= userHZ cpuStat.System /= userHZ cpuStat.Idle /= userHZ cpuStat.Iowait /= userHZ cpuStat.IRQ /= userHZ cpuStat.SoftIRQ /= userHZ cpuStat.Steal /= userHZ cpuStat.Guest /= userHZ cpuStat.GuestNice /= userHZ if cpu == "cpu" { return cpuStat, -1, nil } cpuID, err := strconv.ParseInt(cpu[3:], 10, 64) if err != nil { return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu/cpuid): %s", line, err) } return cpuStat, cpuID, nil } // Parse a softirq line. func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) { softIRQStat := SoftIRQStat{} var total uint64 var prefix string _, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d", &prefix, &total, &softIRQStat.Hi, &softIRQStat.Timer, &softIRQStat.NetTx, &softIRQStat.NetRx, &softIRQStat.Block, &softIRQStat.BlockIoPoll, &softIRQStat.Tasklet, &softIRQStat.Sched, &softIRQStat.Hrtimer, &softIRQStat.Rcu) if err != nil { return SoftIRQStat{}, 0, fmt.Errorf("couldn't parse %s (softirq): %s", line, err) } return softIRQStat, total, nil } // NewStat returns an information about current kernel/system statistics. func (fs FS) NewStat() (Stat, error) { // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt f, err := os.Open(fs.Path("stat")) if err != nil { return Stat{}, err } defer f.Close() stat := Stat{} scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() parts := strings.Fields(scanner.Text()) // require at least if len(parts) < 2 { continue } switch { case parts[0] == "btime": if stat.BootTime, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (btime): %s", parts[1], err) } case parts[0] == "intr": if stat.IRQTotal, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (intr): %s", parts[1], err) } numberedIRQs := parts[2:] stat.IRQ = make([]uint64, len(numberedIRQs)) for i, count := range numberedIRQs { if stat.IRQ[i], err = strconv.ParseUint(count, 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (intr%d): %s", count, i, err) } } case parts[0] == "ctxt": if stat.ContextSwitches, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (ctxt): %s", parts[1], err) } case parts[0] == "processes": if stat.ProcessCreated, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (processes): %s", parts[1], err) } case parts[0] == "procs_running": if stat.ProcessesRunning, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (procs_running): %s", parts[1], err) } case parts[0] == "procs_blocked": if stat.ProcessesBlocked, err = strconv.ParseUint(parts[1], 10, 64); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s (procs_blocked): %s", parts[1], err) } case parts[0] == "softirq": softIRQStats, total, err := parseSoftIRQStat(line) if err != nil { return Stat{}, err } stat.SoftIRQTotal = total stat.SoftIRQ = softIRQStats case strings.HasPrefix(parts[0], "cpu"): cpuStat, cpuID, err := parseCPUStat(line) if err != nil { return Stat{}, err } if cpuID == -1 { stat.CPUTotal = cpuStat } else { for int64(len(stat.CPU)) <= cpuID { stat.CPU = append(stat.CPU, CPUStat{}) } stat.CPU[cpuID] = cpuStat } } } if err := scanner.Err(); err != nil { return Stat{}, fmt.Errorf("couldn't parse %s: %s", f.Name(), err) } return stat, nil } golang-procfs-0+git20170703.e645f4e/stat_test.go000066400000000000000000000032101312641423200207730ustar00rootroot00000000000000package procfs import "testing" func TestStat(t *testing.T) { s, err := FS("fixtures").NewStat() if err != nil { t.Fatal(err) } // cpu if want, have := float64(301854)/userHZ, s.CPUTotal.User; want != have { t.Errorf("want cpu/user %v, have %v", want, have) } if want, have := float64(31)/userHZ, s.CPU[7].SoftIRQ; want != have { t.Errorf("want cpu7/softirq %v, have %v", want, have) } // intr if want, have := uint64(8885917), s.IRQTotal; want != have { t.Errorf("want irq/total %d, have %d", want, have) } if want, have := uint64(1), s.IRQ[8]; want != have { t.Errorf("want irq8 %d, have %d", want, have) } // ctxt if want, have := uint64(38014093), s.ContextSwitches; want != have { t.Errorf("want context switches (ctxt) %d, have %d", want, have) } // btime if want, have := uint64(1418183276), s.BootTime; want != have { t.Errorf("want boot time (btime) %d, have %d", want, have) } // processes if want, have := uint64(26442), s.ProcessCreated; want != have { t.Errorf("want process created (processes) %d, have %d", want, have) } // procs_running if want, have := uint64(2), s.ProcessesRunning; want != have { t.Errorf("want processes running (procs_running) %d, have %d", want, have) } // procs_blocked if want, have := uint64(1), s.ProcessesBlocked; want != have { t.Errorf("want processes blocked (procs_blocked) %d, have %d", want, have) } // softirq if want, have := uint64(5057579), s.SoftIRQTotal; want != have { t.Errorf("want softirq total %d, have %d", want, have) } if want, have := uint64(508444), s.SoftIRQ.Rcu; want != have { t.Errorf("want softirq RCU %d, have %d", want, have) } } golang-procfs-0+git20170703.e645f4e/sysfs/000077500000000000000000000000001312641423200176055ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/sysfs/.gitignore000066400000000000000000000000121312641423200215660ustar00rootroot00000000000000fixtures/ golang-procfs-0+git20170703.e645f4e/sysfs/doc.go000066400000000000000000000013141312641423200207000ustar00rootroot00000000000000// Copyright 2017 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. // Package sysfs provides functions to retrieve system and kernel metrics // from the pseudo-filesystem sys. package sysfs golang-procfs-0+git20170703.e645f4e/sysfs/fixtures.ttar000066400000000000000000000735721312641423200223700ustar00rootroot00000000000000Directory: fixtures Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/dirty_data Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/cache_hit_ratio Lines: 1 100 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/cache_hits Lines: 1 289 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_day/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/cache_hit_ratio Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/cache_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_five_minute/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/cache_hit_ratio Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/cache_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_hour/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/cache_hit_ratio Lines: 1 100 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/cache_hits Lines: 1 546 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata4/host3/target3:0:0/3:0:0:0/block/sdb/bcache/stats_total/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata5 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0/block Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0/block/sdc Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0/block/sdc/bcache Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0/block/sdc/bcache/io_errors Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0/block/sdc/bcache/metadata_written Lines: 1 512 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0/block/sdc/bcache/priority_stats Lines: 5 Unused: 99% Metadata: 0% Average: 10473 Sectors per Q: 64 Quantiles: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946] Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/devices/pci0000:00/0000:00:0d.0/ata5/host4/target4:0:0/4:0:0:0/block/sdc/bcache/written Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/average_key_size Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0 Mode: 777 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/dirty_data Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/cache_hit_ratio Lines: 1 100 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/cache_hits Lines: 1 289 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_day/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/cache_hit_ratio Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/cache_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_five_minute/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/cache_hit_ratio Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/cache_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_hour/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/cache_hit_ratio Lines: 1 100 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/cache_hits Lines: 1 546 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/bdev0/stats_total/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/btree_cache_size Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/cache0 Mode: 777 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/cache0/io_errors Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/cache0/metadata_written Lines: 1 512 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/cache0/priority_stats Lines: 5 Unused: 99% Metadata: 0% Average: 10473 Sectors per Q: 64 Quantiles: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946 20946] Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/cache0/written Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/cache_available_percent Lines: 1 100 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/congested Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/internal Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/internal/active_journal_entries Lines: 1 1 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/internal/btree_nodes Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/internal/btree_read_average_duration_us Lines: 1 1305 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/internal/cache_read_races Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/root_usage_percent Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/cache_hit_ratio Lines: 1 100 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/cache_hits Lines: 1 289 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_day/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/cache_hit_ratio Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/cache_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_five_minute/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/cache_hit_ratio Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/cache_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_hour/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/bypassed Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/cache_bypass_hits Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/cache_bypass_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/cache_hit_ratio Lines: 1 100 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/cache_hits Lines: 1 546 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/cache_miss_collisions Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/cache_misses Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/stats_total/cache_readaheads Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/bcache/deaddd54-c735-46d5-868e-f331c5fd7c74/tree_depth Lines: 1 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/xfs Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/xfs/sda1 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/xfs/sda1/stats Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/xfs/sda1/stats/stats Lines: 1 extent_alloc 1 0 0 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/xfs/sdb1 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Directory: fixtures/fs/xfs/sdb1/stats Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/fs/xfs/sdb1/stats/stats Lines: 1 extent_alloc 2 0 0 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - golang-procfs-0+git20170703.e645f4e/sysfs/fs.go000066400000000000000000000056311312641423200205510ustar00rootroot00000000000000// Copyright 2017 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. package sysfs import ( "fmt" "os" "path/filepath" "github.com/prometheus/procfs/bcache" "github.com/prometheus/procfs/xfs" ) // FS represents the pseudo-filesystem sys, which provides an interface to // kernel data structures. type FS string // DefaultMountPoint is the common mount point of the sys filesystem. const DefaultMountPoint = "/sys" // NewFS returns a new FS mounted under the given mountPoint. It will error // if the mount point can't be read. func NewFS(mountPoint string) (FS, error) { info, err := os.Stat(mountPoint) if err != nil { return "", fmt.Errorf("could not read %s: %s", mountPoint, err) } if !info.IsDir() { return "", fmt.Errorf("mount point %s is not a directory", mountPoint) } return FS(mountPoint), nil } // Path returns the path of the given subsystem relative to the sys root. func (fs FS) Path(p ...string) string { return filepath.Join(append([]string{string(fs)}, p...)...) } // XFSStats retrieves XFS filesystem runtime statistics for each mounted XFS // filesystem. Only available on kernel 4.4+. On older kernels, an empty // slice of *xfs.Stats will be returned. func (fs FS) XFSStats() ([]*xfs.Stats, error) { matches, err := filepath.Glob(fs.Path("fs/xfs/*/stats/stats")) if err != nil { return nil, err } stats := make([]*xfs.Stats, 0, len(matches)) for _, m := range matches { f, err := os.Open(m) if err != nil { return nil, err } // "*" used in glob above indicates the name of the filesystem. name := filepath.Base(filepath.Dir(filepath.Dir(m))) // File must be closed after parsing, regardless of success or // failure. Defer is not used because of the loop. s, err := xfs.ParseStats(f) _ = f.Close() if err != nil { return nil, err } s.Name = name stats = append(stats, s) } return stats, nil } // BcacheStats retrieves bcache runtime statistics for each bcache. func (fs FS) BcacheStats() ([]*bcache.Stats, error) { matches, err := filepath.Glob(fs.Path("fs/bcache/*-*")) if err != nil { return nil, err } stats := make([]*bcache.Stats, 0, len(matches)) for _, uuidPath := range matches { // "*-*" in glob above indicates the name of the bcache. name := filepath.Base(uuidPath) // stats s, err := bcache.GetStats(uuidPath) if err != nil { return nil, err } s.Name = name stats = append(stats, s) } return stats, nil } golang-procfs-0+git20170703.e645f4e/sysfs/fs_test.go000066400000000000000000000051741312641423200216120ustar00rootroot00000000000000// Copyright 2017 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. package sysfs import "testing" func TestNewFS(t *testing.T) { if _, err := NewFS("foobar"); err == nil { t.Error("want NewFS to fail for non-existing mount point") } if _, err := NewFS("doc.go"); err == nil { t.Error("want NewFS to fail if mount point is not a directory") } } func TestFSXFSStats(t *testing.T) { stats, err := FS("fixtures").XFSStats() if err != nil { t.Fatalf("failed to parse XFS stats: %v", err) } tests := []struct { name string allocated uint32 }{ { name: "sda1", allocated: 1, }, { name: "sdb1", allocated: 2, }, } const expect = 2 if l := len(stats); l != expect { t.Fatalf("unexpected number of XFS stats: %d", l) } if l := len(tests); l != expect { t.Fatalf("unexpected number of tests: %d", l) } for i, tt := range tests { if want, got := tt.name, stats[i].Name; want != got { t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) } if want, got := tt.allocated, stats[i].ExtentAllocation.ExtentsAllocated; want != got { t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got) } } } func TestFSBcacheStats(t *testing.T) { stats, err := FS("fixtures").BcacheStats() if err != nil { t.Fatalf("failed to parse bcache stats: %v", err) } tests := []struct { name string bdevs int caches int }{ { name: "deaddd54-c735-46d5-868e-f331c5fd7c74", bdevs: 1, caches: 1, }, } const expect = 1 if l := len(stats); l != expect { t.Fatalf("unexpected number of bcache stats: %d", l) } if l := len(tests); l != expect { t.Fatalf("unexpected number of tests: %d", l) } for i, tt := range tests { if want, got := tt.name, stats[i].Name; want != got { t.Errorf("unexpected stats name:\nwant: %q\nhave: %q", want, got) } if want, got := tt.bdevs, len(stats[i].Bdevs); want != got { t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) } if want, got := tt.caches, len(stats[i].Caches); want != got { t.Errorf("unexpected value allocated:\nwant: %d\nhave: %d", want, got) } } } golang-procfs-0+git20170703.e645f4e/ttar000077500000000000000000000147601312641423200173460ustar00rootroot00000000000000#!/usr/bin/env bash # Purpose: plain text tar format # Limitations: - only suitable for text files, directories, and symlinks # - stores only filename, content, and mode # - not designed for untrusted input # Note: must work with bash version 3.2 (macOS) set -o errexit -o nounset # Sanitize environment (for instance, standard sorting of glob matches) export LC_ALL=C path="" CMD="" function usage { bname=$(basename "$0") cat << USAGE Usage: $bname [-C ] -c -f (create archive) $bname -t -f (list archive contents) $bname [-C ] -x -f (extract archive) Options: -C (change directory) Example: Change to sysfs directory, create ttar file from fixtures directory $bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/ USAGE exit "$1" } function vecho { if [ "${VERBOSE:-}" == "yes" ]; then echo >&7 "$@" fi } function set_cmd { if [ -n "$CMD" ]; then echo "ERROR: more than one command given" echo usage 2 fi CMD=$1 } while getopts :cf:htxvC: opt; do case $opt in c) set_cmd "create" ;; f) ARCHIVE=$OPTARG ;; h) usage 0 ;; t) set_cmd "list" ;; x) set_cmd "extract" ;; v) VERBOSE=yes exec 7>&1 ;; C) CDIR=$OPTARG ;; *) echo >&2 "ERROR: invalid option -$OPTARG" echo usage 1 ;; esac done # Remove processed options from arguments shift $(( OPTIND - 1 )); if [ "${CMD:-}" == "" ]; then echo >&2 "ERROR: no command given" echo usage 1 elif [ "${ARCHIVE:-}" == "" ]; then echo >&2 "ERROR: no archive name given" echo usage 1 fi function list { local path="" local size=0 local line_no=0 local ttar_file=$1 if [ -n "${2:-}" ]; then echo >&2 "ERROR: too many arguments." echo usage 1 fi if [ ! -e "$ttar_file" ]; then echo >&2 "ERROR: file not found ($ttar_file)" echo usage 1 fi while read -r line; do line_no=$(( line_no + 1 )) if [ $size -gt 0 ]; then size=$(( size - 1 )) continue fi if [[ $line =~ ^Path:\ (.*)$ ]]; then path=${BASH_REMATCH[1]} elif [[ $line =~ ^Lines:\ (.*)$ ]]; then size=${BASH_REMATCH[1]} echo "$path" elif [[ $line =~ ^Directory:\ (.*)$ ]]; then path=${BASH_REMATCH[1]} echo "$path/" elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then echo "$path -> ${BASH_REMATCH[1]}" fi done < "$ttar_file" } function extract { local path="" local size=0 local line_no=0 local ttar_file=$1 if [ -n "${2:-}" ]; then echo >&2 "ERROR: too many arguments." echo usage 1 fi if [ ! -e "$ttar_file" ]; then echo >&2 "ERROR: file not found ($ttar_file)" echo usage 1 fi while IFS= read -r line; do line_no=$(( line_no + 1 )) if [ "$size" -gt 0 ]; then echo "$line" >> "$path" size=$(( size - 1 )) continue fi if [[ $line =~ ^Path:\ (.*)$ ]]; then path=${BASH_REMATCH[1]} if [ -e "$path" ] || [ -L "$path" ]; then rm "$path" fi elif [[ $line =~ ^Lines:\ (.*)$ ]]; then size=${BASH_REMATCH[1]} # Create file even if it is zero-length. touch "$path" vecho " $path" elif [[ $line =~ ^Mode:\ (.*)$ ]]; then mode=${BASH_REMATCH[1]} chmod "$mode" "$path" vecho "$mode" elif [[ $line =~ ^Directory:\ (.*)$ ]]; then path=${BASH_REMATCH[1]} mkdir -p "$path" vecho " $path/" elif [[ $line =~ ^SymlinkTo:\ (.*)$ ]]; then ln -s "${BASH_REMATCH[1]}" "$path" vecho " $path -> ${BASH_REMATCH[1]}" elif [[ $line =~ ^# ]]; then # Ignore comments between files continue else echo >&2 "ERROR: Unknown keyword on line $line_no: $line" exit 1 fi done < "$ttar_file" } function div { echo "# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" \ "- - - - - -" } function get_mode { local mfile=$1 if [ -z "${STAT_OPTION:-}" ]; then if stat -c '%a' "$mfile" >/dev/null 2>&1; then STAT_OPTION='-c' STAT_FORMAT='%a' else STAT_OPTION='-f' STAT_FORMAT='%A' fi fi stat "${STAT_OPTION}" "${STAT_FORMAT}" "$mfile" } function _create { shopt -s nullglob local mode while (( "$#" )); do file=$1 if [ -L "$file" ]; then echo "Path: $file" symlinkTo=$(readlink "$file") echo "SymlinkTo: $symlinkTo" vecho " $file -> $symlinkTo" div elif [ -d "$file" ]; then # Strip trailing slash (if there is one) file=${file%/} echo "Directory: $file" mode=$(get_mode "$file") echo "Mode: $mode" vecho "$mode $file/" div # Find all files and dirs, including hidden/dot files for x in "$file/"{*,.[^.]*}; do _create "$x" done elif [ -f "$file" ]; then echo "Path: $file" lines=$(wc -l "$file"|awk '{print $1}') echo "Lines: $lines" cat "$file" mode=$(get_mode "$file") echo "Mode: $mode" vecho "$mode $file" div else echo >&2 "ERROR: file not found ($file in $(pwd))" exit 2 fi shift done } function create { ttar_file=$1 shift if [ -z "${1:-}" ]; then echo >&2 "ERROR: missing arguments." echo usage 1 fi if [ -e "$ttar_file" ]; then rm "$ttar_file" fi exec > "$ttar_file" _create "$@" } if [ -n "${CDIR:-}" ]; then if [[ "$ARCHIVE" != /* ]]; then # Relative path: preserve the archive's location before changing # directory ARCHIVE="$(pwd)/$ARCHIVE" fi cd "$CDIR" fi "$CMD" "$ARCHIVE" "$@" golang-procfs-0+git20170703.e645f4e/xfrm.go000066400000000000000000000114041312641423200177410ustar00rootroot00000000000000// Copyright 2017 Prometheus Team // 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. package procfs import ( "bufio" "fmt" "os" "strconv" "strings" ) // XfrmStat models the contents of /proc/net/xfrm_stat. type XfrmStat struct { // All errors which are not matched by other XfrmInError int // No buffer is left XfrmInBufferError int // Header Error XfrmInHdrError int // No state found // i.e. either inbound SPI, address, or IPSEC protocol at SA is wrong XfrmInNoStates int // Transformation protocol specific error // e.g. SA Key is wrong XfrmInStateProtoError int // Transformation mode specific error XfrmInStateModeError int // Sequence error // e.g. sequence number is out of window XfrmInStateSeqError int // State is expired XfrmInStateExpired int // State has mismatch option // e.g. UDP encapsulation type is mismatched XfrmInStateMismatch int // State is invalid XfrmInStateInvalid int // No matching template for states // e.g. Inbound SAs are correct but SP rule is wrong XfrmInTmplMismatch int // No policy is found for states // e.g. Inbound SAs are correct but no SP is found XfrmInNoPols int // Policy discards XfrmInPolBlock int // Policy error XfrmInPolError int // All errors which are not matched by others XfrmOutError int // Bundle generation error XfrmOutBundleGenError int // Bundle check error XfrmOutBundleCheckError int // No state was found XfrmOutNoStates int // Transformation protocol specific error XfrmOutStateProtoError int // Transportation mode specific error XfrmOutStateModeError int // Sequence error // i.e sequence number overflow XfrmOutStateSeqError int // State is expired XfrmOutStateExpired int // Policy discads XfrmOutPolBlock int // Policy is dead XfrmOutPolDead int // Policy Error XfrmOutPolError int XfrmFwdHdrError int XfrmOutStateInvalid int XfrmAcquireError int } // NewXfrmStat reads the xfrm_stat statistics. func NewXfrmStat() (XfrmStat, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { return XfrmStat{}, err } return fs.NewXfrmStat() } // NewXfrmStat reads the xfrm_stat statistics from the 'proc' filesystem. func (fs FS) NewXfrmStat() (XfrmStat, error) { file, err := os.Open(fs.Path("net/xfrm_stat")) if err != nil { return XfrmStat{}, err } defer file.Close() var ( x = XfrmStat{} s = bufio.NewScanner(file) ) for s.Scan() { fields := strings.Fields(s.Text()) if len(fields) != 2 { return XfrmStat{}, fmt.Errorf( "couldnt parse %s line %s", file.Name(), s.Text()) } name := fields[0] value, err := strconv.Atoi(fields[1]) if err != nil { return XfrmStat{}, err } switch name { case "XfrmInError": x.XfrmInError = value case "XfrmInBufferError": x.XfrmInBufferError = value case "XfrmInHdrError": x.XfrmInHdrError = value case "XfrmInNoStates": x.XfrmInNoStates = value case "XfrmInStateProtoError": x.XfrmInStateProtoError = value case "XfrmInStateModeError": x.XfrmInStateModeError = value case "XfrmInStateSeqError": x.XfrmInStateSeqError = value case "XfrmInStateExpired": x.XfrmInStateExpired = value case "XfrmInStateInvalid": x.XfrmInStateInvalid = value case "XfrmInTmplMismatch": x.XfrmInTmplMismatch = value case "XfrmInNoPols": x.XfrmInNoPols = value case "XfrmInPolBlock": x.XfrmInPolBlock = value case "XfrmInPolError": x.XfrmInPolError = value case "XfrmOutError": x.XfrmOutError = value case "XfrmInStateMismatch": x.XfrmInStateMismatch = value case "XfrmOutBundleGenError": x.XfrmOutBundleGenError = value case "XfrmOutBundleCheckError": x.XfrmOutBundleCheckError = value case "XfrmOutNoStates": x.XfrmOutNoStates = value case "XfrmOutStateProtoError": x.XfrmOutStateProtoError = value case "XfrmOutStateModeError": x.XfrmOutStateModeError = value case "XfrmOutStateSeqError": x.XfrmOutStateSeqError = value case "XfrmOutStateExpired": x.XfrmOutStateExpired = value case "XfrmOutPolBlock": x.XfrmOutPolBlock = value case "XfrmOutPolDead": x.XfrmOutPolDead = value case "XfrmOutPolError": x.XfrmOutPolError = value case "XfrmFwdHdrError": x.XfrmFwdHdrError = value case "XfrmOutStateInvalid": x.XfrmOutStateInvalid = value case "XfrmAcquireError": x.XfrmAcquireError = value } } return x, s.Err() } golang-procfs-0+git20170703.e645f4e/xfrm_test.go000066400000000000000000000062211312641423200210010ustar00rootroot00000000000000// Copyright 2017 Prometheus Team // 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. package procfs import ( "testing" ) func TestXfrmStats(t *testing.T) { xfrmStats, err := FS("fixtures").NewXfrmStat() if err != nil { t.Fatal(err) } for _, test := range []struct { name string want int got int }{ {name: "XfrmInError", want: 1, got: xfrmStats.XfrmInError}, {name: "XfrmInBufferError", want: 2, got: xfrmStats.XfrmInBufferError}, {name: "XfrmInHdrError", want: 4, got: xfrmStats.XfrmInHdrError}, {name: "XfrmInNoStates", want: 3, got: xfrmStats.XfrmInNoStates}, {name: "XfrmInStateProtoError", want: 40, got: xfrmStats.XfrmInStateProtoError}, {name: "XfrmInStateModeError", want: 100, got: xfrmStats.XfrmInStateModeError}, {name: "XfrmInStateSeqError", want: 6000, got: xfrmStats.XfrmInStateSeqError}, {name: "XfrmInStateExpired", want: 4, got: xfrmStats.XfrmInStateExpired}, {name: "XfrmInStateMismatch", want: 23451, got: xfrmStats.XfrmInStateMismatch}, {name: "XfrmInStateInvalid", want: 55555, got: xfrmStats.XfrmInStateInvalid}, {name: "XfrmInTmplMismatch", want: 51, got: xfrmStats.XfrmInTmplMismatch}, {name: "XfrmInNoPols", want: 65432, got: xfrmStats.XfrmInNoPols}, {name: "XfrmInPolBlock", want: 100, got: xfrmStats.XfrmInPolBlock}, {name: "XfrmInPolError", want: 10000, got: xfrmStats.XfrmInPolError}, {name: "XfrmOutError", want: 1000000, got: xfrmStats.XfrmOutError}, {name: "XfrmOutBundleGenError", want: 43321, got: xfrmStats.XfrmOutBundleGenError}, {name: "XfrmOutBundleCheckError", want: 555, got: xfrmStats.XfrmOutBundleCheckError}, {name: "XfrmOutNoStates", want: 869, got: xfrmStats.XfrmOutNoStates}, {name: "XfrmOutStateProtoError", want: 4542, got: xfrmStats.XfrmOutStateProtoError}, {name: "XfrmOutStateModeError", want: 4, got: xfrmStats.XfrmOutStateModeError}, {name: "XfrmOutStateSeqError", want: 543, got: xfrmStats.XfrmOutStateSeqError}, {name: "XfrmOutStateExpired", want: 565, got: xfrmStats.XfrmOutStateExpired}, {name: "XfrmOutPolBlock", want: 43456, got: xfrmStats.XfrmOutPolBlock}, {name: "XfrmOutPolDead", want: 7656, got: xfrmStats.XfrmOutPolDead}, {name: "XfrmOutPolError", want: 1454, got: xfrmStats.XfrmOutPolError}, {name: "XfrmFwdHdrError", want: 6654, got: xfrmStats.XfrmFwdHdrError}, {name: "XfrmOutStateInvaliad", want: 28765, got: xfrmStats.XfrmOutStateInvalid}, {name: "XfrmAcquireError", want: 24532, got: xfrmStats.XfrmAcquireError}, {name: "XfrmInStateInvalid", want: 55555, got: xfrmStats.XfrmInStateInvalid}, {name: "XfrmOutError", want: 1000000, got: xfrmStats.XfrmOutError}, } { if test.want != test.got { t.Errorf("Want %s %d, have %d", test.name, test.want, test.got) } } } golang-procfs-0+git20170703.e645f4e/xfs/000077500000000000000000000000001312641423200172365ustar00rootroot00000000000000golang-procfs-0+git20170703.e645f4e/xfs/parse.go000066400000000000000000000227051312641423200207050ustar00rootroot00000000000000// Copyright 2017 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. package xfs import ( "bufio" "fmt" "io" "strconv" "strings" ) // ParseStats parses a Stats from an input io.Reader, using the format // found in /proc/fs/xfs/stat. func ParseStats(r io.Reader) (*Stats, error) { const ( // Fields parsed into stats structures. fieldExtentAlloc = "extent_alloc" fieldAbt = "abt" fieldBlkMap = "blk_map" fieldBmbt = "bmbt" fieldDir = "dir" fieldTrans = "trans" fieldIg = "ig" fieldLog = "log" fieldRw = "rw" fieldAttr = "attr" fieldIcluster = "icluster" fieldVnodes = "vnodes" fieldBuf = "buf" fieldXpc = "xpc" // Unimplemented at this time due to lack of documentation. fieldPushAil = "push_ail" fieldXstrat = "xstrat" fieldAbtb2 = "abtb2" fieldAbtc2 = "abtc2" fieldBmbt2 = "bmbt2" fieldIbt2 = "ibt2" fieldFibt2 = "fibt2" fieldQm = "qm" fieldDebug = "debug" ) var xfss Stats s := bufio.NewScanner(r) for s.Scan() { // Expect at least a string label and a single integer value, ex: // - abt 0 // - rw 1 2 ss := strings.Fields(string(s.Bytes())) if len(ss) < 2 { continue } label := ss[0] // Extended precision counters are uint64 values. if label == fieldXpc { us, err := parseUint64s(ss[1:]) if err != nil { return nil, err } xfss.ExtendedPrecision, err = extendedPrecisionStats(us) if err != nil { return nil, err } continue } // All other counters are uint32 values. us, err := parseUint32s(ss[1:]) if err != nil { return nil, err } switch label { case fieldExtentAlloc: xfss.ExtentAllocation, err = extentAllocationStats(us) case fieldAbt: xfss.AllocationBTree, err = btreeStats(us) case fieldBlkMap: xfss.BlockMapping, err = blockMappingStats(us) case fieldBmbt: xfss.BlockMapBTree, err = btreeStats(us) case fieldDir: xfss.DirectoryOperation, err = directoryOperationStats(us) case fieldTrans: xfss.Transaction, err = transactionStats(us) case fieldIg: xfss.InodeOperation, err = inodeOperationStats(us) case fieldLog: xfss.LogOperation, err = logOperationStats(us) case fieldRw: xfss.ReadWrite, err = readWriteStats(us) case fieldAttr: xfss.AttributeOperation, err = attributeOperationStats(us) case fieldIcluster: xfss.InodeClustering, err = inodeClusteringStats(us) case fieldVnodes: xfss.Vnode, err = vnodeStats(us) case fieldBuf: xfss.Buffer, err = bufferStats(us) } if err != nil { return nil, err } } return &xfss, s.Err() } // extentAllocationStats builds an ExtentAllocationStats from a slice of uint32s. func extentAllocationStats(us []uint32) (ExtentAllocationStats, error) { if l := len(us); l != 4 { return ExtentAllocationStats{}, fmt.Errorf("incorrect number of values for XFS extent allocation stats: %d", l) } return ExtentAllocationStats{ ExtentsAllocated: us[0], BlocksAllocated: us[1], ExtentsFreed: us[2], BlocksFreed: us[3], }, nil } // btreeStats builds a BTreeStats from a slice of uint32s. func btreeStats(us []uint32) (BTreeStats, error) { if l := len(us); l != 4 { return BTreeStats{}, fmt.Errorf("incorrect number of values for XFS btree stats: %d", l) } return BTreeStats{ Lookups: us[0], Compares: us[1], RecordsInserted: us[2], RecordsDeleted: us[3], }, nil } // BlockMappingStat builds a BlockMappingStats from a slice of uint32s. func blockMappingStats(us []uint32) (BlockMappingStats, error) { if l := len(us); l != 7 { return BlockMappingStats{}, fmt.Errorf("incorrect number of values for XFS block mapping stats: %d", l) } return BlockMappingStats{ Reads: us[0], Writes: us[1], Unmaps: us[2], ExtentListInsertions: us[3], ExtentListDeletions: us[4], ExtentListLookups: us[5], ExtentListCompares: us[6], }, nil } // DirectoryOperationStats builds a DirectoryOperationStats from a slice of uint32s. func directoryOperationStats(us []uint32) (DirectoryOperationStats, error) { if l := len(us); l != 4 { return DirectoryOperationStats{}, fmt.Errorf("incorrect number of values for XFS directory operation stats: %d", l) } return DirectoryOperationStats{ Lookups: us[0], Creates: us[1], Removes: us[2], Getdents: us[3], }, nil } // TransactionStats builds a TransactionStats from a slice of uint32s. func transactionStats(us []uint32) (TransactionStats, error) { if l := len(us); l != 3 { return TransactionStats{}, fmt.Errorf("incorrect number of values for XFS transaction stats: %d", l) } return TransactionStats{ Sync: us[0], Async: us[1], Empty: us[2], }, nil } // InodeOperationStats builds an InodeOperationStats from a slice of uint32s. func inodeOperationStats(us []uint32) (InodeOperationStats, error) { if l := len(us); l != 7 { return InodeOperationStats{}, fmt.Errorf("incorrect number of values for XFS inode operation stats: %d", l) } return InodeOperationStats{ Attempts: us[0], Found: us[1], Recycle: us[2], Missed: us[3], Duplicate: us[4], Reclaims: us[5], AttributeChange: us[6], }, nil } // LogOperationStats builds a LogOperationStats from a slice of uint32s. func logOperationStats(us []uint32) (LogOperationStats, error) { if l := len(us); l != 5 { return LogOperationStats{}, fmt.Errorf("incorrect number of values for XFS log operation stats: %d", l) } return LogOperationStats{ Writes: us[0], Blocks: us[1], NoInternalBuffers: us[2], Force: us[3], ForceSleep: us[4], }, nil } // ReadWriteStats builds a ReadWriteStats from a slice of uint32s. func readWriteStats(us []uint32) (ReadWriteStats, error) { if l := len(us); l != 2 { return ReadWriteStats{}, fmt.Errorf("incorrect number of values for XFS read write stats: %d", l) } return ReadWriteStats{ Read: us[0], Write: us[1], }, nil } // AttributeOperationStats builds an AttributeOperationStats from a slice of uint32s. func attributeOperationStats(us []uint32) (AttributeOperationStats, error) { if l := len(us); l != 4 { return AttributeOperationStats{}, fmt.Errorf("incorrect number of values for XFS attribute operation stats: %d", l) } return AttributeOperationStats{ Get: us[0], Set: us[1], Remove: us[2], List: us[3], }, nil } // InodeClusteringStats builds an InodeClusteringStats from a slice of uint32s. func inodeClusteringStats(us []uint32) (InodeClusteringStats, error) { if l := len(us); l != 3 { return InodeClusteringStats{}, fmt.Errorf("incorrect number of values for XFS inode clustering stats: %d", l) } return InodeClusteringStats{ Iflush: us[0], Flush: us[1], FlushInode: us[2], }, nil } // VnodeStats builds a VnodeStats from a slice of uint32s. func vnodeStats(us []uint32) (VnodeStats, error) { // The attribute "Free" appears to not be available on older XFS // stats versions. Therefore, 7 or 8 elements may appear in // this slice. l := len(us) if l != 7 && l != 8 { return VnodeStats{}, fmt.Errorf("incorrect number of values for XFS vnode stats: %d", l) } s := VnodeStats{ Active: us[0], Allocate: us[1], Get: us[2], Hold: us[3], Release: us[4], Reclaim: us[5], Remove: us[6], } // Skip adding free, unless it is present. The zero value will // be used in place of an actual count. if l == 7 { return s, nil } s.Free = us[7] return s, nil } // BufferStats builds a BufferStats from a slice of uint32s. func bufferStats(us []uint32) (BufferStats, error) { if l := len(us); l != 9 { return BufferStats{}, fmt.Errorf("incorrect number of values for XFS buffer stats: %d", l) } return BufferStats{ Get: us[0], Create: us[1], GetLocked: us[2], GetLockedWaited: us[3], BusyLocked: us[4], MissLocked: us[5], PageRetries: us[6], PageFound: us[7], GetRead: us[8], }, nil } // ExtendedPrecisionStats builds an ExtendedPrecisionStats from a slice of uint32s. func extendedPrecisionStats(us []uint64) (ExtendedPrecisionStats, error) { if l := len(us); l != 3 { return ExtendedPrecisionStats{}, fmt.Errorf("incorrect number of values for XFS extended precision stats: %d", l) } return ExtendedPrecisionStats{ FlushBytes: us[0], WriteBytes: us[1], ReadBytes: us[2], }, nil } // parseUint32s parses a slice of strings into a slice of uint32s. func parseUint32s(ss []string) ([]uint32, error) { us := make([]uint32, 0, len(ss)) for _, s := range ss { u, err := strconv.ParseUint(s, 10, 32) if err != nil { return nil, err } us = append(us, uint32(u)) } return us, nil } // parseUint64s parses a slice of strings into a slice of uint64s. func parseUint64s(ss []string) ([]uint64, error) { us := make([]uint64, 0, len(ss)) for _, s := range ss { u, err := strconv.ParseUint(s, 10, 64) if err != nil { return nil, err } us = append(us, u) } return us, nil } golang-procfs-0+git20170703.e645f4e/xfs/parse_test.go000066400000000000000000000213251312641423200217410ustar00rootroot00000000000000// Copyright 2017 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. package xfs_test import ( "reflect" "strings" "testing" "github.com/prometheus/procfs" "github.com/prometheus/procfs/xfs" ) func TestParseStats(t *testing.T) { tests := []struct { name string s string fs bool stats *xfs.Stats invalid bool }{ { name: "empty file OK", }, { name: "short or empty lines and unknown labels ignored", s: "one\n\ntwo 1 2 3\n", stats: &xfs.Stats{}, }, { name: "bad uint32", s: "extent_alloc XXX", invalid: true, }, { name: "bad uint64", s: "xpc XXX", invalid: true, }, { name: "extent_alloc bad", s: "extent_alloc 1", invalid: true, }, { name: "extent_alloc OK", s: "extent_alloc 1 2 3 4", stats: &xfs.Stats{ ExtentAllocation: xfs.ExtentAllocationStats{ ExtentsAllocated: 1, BlocksAllocated: 2, ExtentsFreed: 3, BlocksFreed: 4, }, }, }, { name: "abt bad", s: "abt 1", invalid: true, }, { name: "abt OK", s: "abt 1 2 3 4", stats: &xfs.Stats{ AllocationBTree: xfs.BTreeStats{ Lookups: 1, Compares: 2, RecordsInserted: 3, RecordsDeleted: 4, }, }, }, { name: "blk_map bad", s: "blk_map 1", invalid: true, }, { name: "blk_map OK", s: "blk_map 1 2 3 4 5 6 7", stats: &xfs.Stats{ BlockMapping: xfs.BlockMappingStats{ Reads: 1, Writes: 2, Unmaps: 3, ExtentListInsertions: 4, ExtentListDeletions: 5, ExtentListLookups: 6, ExtentListCompares: 7, }, }, }, { name: "bmbt bad", s: "bmbt 1", invalid: true, }, { name: "bmbt OK", s: "bmbt 1 2 3 4", stats: &xfs.Stats{ BlockMapBTree: xfs.BTreeStats{ Lookups: 1, Compares: 2, RecordsInserted: 3, RecordsDeleted: 4, }, }, }, { name: "dir bad", s: "dir 1", invalid: true, }, { name: "dir OK", s: "dir 1 2 3 4", stats: &xfs.Stats{ DirectoryOperation: xfs.DirectoryOperationStats{ Lookups: 1, Creates: 2, Removes: 3, Getdents: 4, }, }, }, { name: "trans bad", s: "trans 1", invalid: true, }, { name: "trans OK", s: "trans 1 2 3", stats: &xfs.Stats{ Transaction: xfs.TransactionStats{ Sync: 1, Async: 2, Empty: 3, }, }, }, { name: "ig bad", s: "ig 1", invalid: true, }, { name: "ig OK", s: "ig 1 2 3 4 5 6 7", stats: &xfs.Stats{ InodeOperation: xfs.InodeOperationStats{ Attempts: 1, Found: 2, Recycle: 3, Missed: 4, Duplicate: 5, Reclaims: 6, AttributeChange: 7, }, }, }, { name: "log bad", s: "log 1", invalid: true, }, { name: "log OK", s: "log 1 2 3 4 5", stats: &xfs.Stats{ LogOperation: xfs.LogOperationStats{ Writes: 1, Blocks: 2, NoInternalBuffers: 3, Force: 4, ForceSleep: 5, }, }, }, { name: "rw bad", s: "rw 1", invalid: true, }, { name: "rw OK", s: "rw 1 2", stats: &xfs.Stats{ ReadWrite: xfs.ReadWriteStats{ Read: 1, Write: 2, }, }, }, { name: "attr bad", s: "attr 1", invalid: true, }, { name: "attr OK", s: "attr 1 2 3 4", stats: &xfs.Stats{ AttributeOperation: xfs.AttributeOperationStats{ Get: 1, Set: 2, Remove: 3, List: 4, }, }, }, { name: "icluster bad", s: "icluster 1", invalid: true, }, { name: "icluster OK", s: "icluster 1 2 3", stats: &xfs.Stats{ InodeClustering: xfs.InodeClusteringStats{ Iflush: 1, Flush: 2, FlushInode: 3, }, }, }, { name: "vnodes bad", s: "vnodes 1", invalid: true, }, { name: "vnodes (missing free) OK", s: "vnodes 1 2 3 4 5 6 7", stats: &xfs.Stats{ Vnode: xfs.VnodeStats{ Active: 1, Allocate: 2, Get: 3, Hold: 4, Release: 5, Reclaim: 6, Remove: 7, }, }, }, { name: "vnodes (with free) OK", s: "vnodes 1 2 3 4 5 6 7 8", stats: &xfs.Stats{ Vnode: xfs.VnodeStats{ Active: 1, Allocate: 2, Get: 3, Hold: 4, Release: 5, Reclaim: 6, Remove: 7, Free: 8, }, }, }, { name: "buf bad", s: "buf 1", invalid: true, }, { name: "buf OK", s: "buf 1 2 3 4 5 6 7 8 9", stats: &xfs.Stats{ Buffer: xfs.BufferStats{ Get: 1, Create: 2, GetLocked: 3, GetLockedWaited: 4, BusyLocked: 5, MissLocked: 6, PageRetries: 7, PageFound: 8, GetRead: 9, }, }, }, { name: "xpc bad", s: "xpc 1", invalid: true, }, { name: "xpc OK", s: "xpc 1 2 3", stats: &xfs.Stats{ ExtendedPrecision: xfs.ExtendedPrecisionStats{ FlushBytes: 1, WriteBytes: 2, ReadBytes: 3, }, }, }, { name: "fixtures OK", fs: true, stats: &xfs.Stats{ ExtentAllocation: xfs.ExtentAllocationStats{ ExtentsAllocated: 92447, BlocksAllocated: 97589, ExtentsFreed: 92448, BlocksFreed: 93751, }, AllocationBTree: xfs.BTreeStats{ Lookups: 0, Compares: 0, RecordsInserted: 0, RecordsDeleted: 0, }, BlockMapping: xfs.BlockMappingStats{ Reads: 1767055, Writes: 188820, Unmaps: 184891, ExtentListInsertions: 92447, ExtentListDeletions: 92448, ExtentListLookups: 2140766, ExtentListCompares: 0, }, BlockMapBTree: xfs.BTreeStats{ Lookups: 0, Compares: 0, RecordsInserted: 0, RecordsDeleted: 0, }, DirectoryOperation: xfs.DirectoryOperationStats{ Lookups: 185039, Creates: 92447, Removes: 92444, Getdents: 136422, }, Transaction: xfs.TransactionStats{ Sync: 706, Async: 944304, Empty: 0, }, InodeOperation: xfs.InodeOperationStats{ Attempts: 185045, Found: 58807, Recycle: 0, Missed: 126238, Duplicate: 0, Reclaims: 33637, AttributeChange: 22, }, LogOperation: xfs.LogOperationStats{ Writes: 2883, Blocks: 113448, NoInternalBuffers: 9, Force: 17360, ForceSleep: 739, }, ReadWrite: xfs.ReadWriteStats{ Read: 107739, Write: 94045, }, AttributeOperation: xfs.AttributeOperationStats{ Get: 4, Set: 0, Remove: 0, List: 0, }, InodeClustering: xfs.InodeClusteringStats{ Iflush: 8677, Flush: 7849, FlushInode: 135802, }, Vnode: xfs.VnodeStats{ Active: 92601, Allocate: 0, Get: 0, Hold: 0, Release: 92444, Reclaim: 92444, Remove: 92444, Free: 0, }, Buffer: xfs.BufferStats{ Get: 2666287, Create: 7122, GetLocked: 2659202, GetLockedWaited: 3599, BusyLocked: 2, MissLocked: 7085, PageRetries: 0, PageFound: 10297, GetRead: 7085, }, ExtendedPrecision: xfs.ExtendedPrecisionStats{ FlushBytes: 399724544, WriteBytes: 92823103, ReadBytes: 86219234, }, }, }, } for _, tt := range tests { var ( stats *xfs.Stats err error ) if tt.s != "" { stats, err = xfs.ParseStats(strings.NewReader(tt.s)) } if tt.fs { stats, err = procfs.FS("../fixtures").XFSStats() } if tt.invalid && err == nil { t.Error("expected an error, but none occurred") } if !tt.invalid && err != nil { t.Errorf("unexpected error: %v", err) } if want, have := tt.stats, stats; !reflect.DeepEqual(want, have) { t.Errorf("unexpected XFS stats:\nwant:\n%v\nhave:\n%v", want, have) } } } golang-procfs-0+git20170703.e645f4e/xfs/xfs.go000066400000000000000000000111341312641423200203650ustar00rootroot00000000000000// Copyright 2017 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. // Package xfs provides access to statistics exposed by the XFS filesystem. package xfs // Stats contains XFS filesystem runtime statistics, parsed from // /proc/fs/xfs/stat. // // The names and meanings of each statistic were taken from // http://xfs.org/index.php/Runtime_Stats and xfs_stats.h in the Linux // kernel source. Most counters are uint32s (same data types used in // xfs_stats.h), but some of the "extended precision stats" are uint64s. type Stats struct { // The name of the filesystem used to source these statistics. // If empty, this indicates aggregated statistics for all XFS // filesystems on the host. Name string ExtentAllocation ExtentAllocationStats AllocationBTree BTreeStats BlockMapping BlockMappingStats BlockMapBTree BTreeStats DirectoryOperation DirectoryOperationStats Transaction TransactionStats InodeOperation InodeOperationStats LogOperation LogOperationStats ReadWrite ReadWriteStats AttributeOperation AttributeOperationStats InodeClustering InodeClusteringStats Vnode VnodeStats Buffer BufferStats ExtendedPrecision ExtendedPrecisionStats } // ExtentAllocationStats contains statistics regarding XFS extent allocations. type ExtentAllocationStats struct { ExtentsAllocated uint32 BlocksAllocated uint32 ExtentsFreed uint32 BlocksFreed uint32 } // BTreeStats contains statistics regarding an XFS internal B-tree. type BTreeStats struct { Lookups uint32 Compares uint32 RecordsInserted uint32 RecordsDeleted uint32 } // BlockMappingStats contains statistics regarding XFS block maps. type BlockMappingStats struct { Reads uint32 Writes uint32 Unmaps uint32 ExtentListInsertions uint32 ExtentListDeletions uint32 ExtentListLookups uint32 ExtentListCompares uint32 } // DirectoryOperationStats contains statistics regarding XFS directory entries. type DirectoryOperationStats struct { Lookups uint32 Creates uint32 Removes uint32 Getdents uint32 } // TransactionStats contains statistics regarding XFS metadata transactions. type TransactionStats struct { Sync uint32 Async uint32 Empty uint32 } // InodeOperationStats contains statistics regarding XFS inode operations. type InodeOperationStats struct { Attempts uint32 Found uint32 Recycle uint32 Missed uint32 Duplicate uint32 Reclaims uint32 AttributeChange uint32 } // LogOperationStats contains statistics regarding the XFS log buffer. type LogOperationStats struct { Writes uint32 Blocks uint32 NoInternalBuffers uint32 Force uint32 ForceSleep uint32 } // ReadWriteStats contains statistics regarding the number of read and write // system calls for XFS filesystems. type ReadWriteStats struct { Read uint32 Write uint32 } // AttributeOperationStats contains statistics regarding manipulation of // XFS extended file attributes. type AttributeOperationStats struct { Get uint32 Set uint32 Remove uint32 List uint32 } // InodeClusteringStats contains statistics regarding XFS inode clustering // operations. type InodeClusteringStats struct { Iflush uint32 Flush uint32 FlushInode uint32 } // VnodeStats contains statistics regarding XFS vnode operations. type VnodeStats struct { Active uint32 Allocate uint32 Get uint32 Hold uint32 Release uint32 Reclaim uint32 Remove uint32 Free uint32 } // BufferStats contains statistics regarding XFS read/write I/O buffers. type BufferStats struct { Get uint32 Create uint32 GetLocked uint32 GetLockedWaited uint32 BusyLocked uint32 MissLocked uint32 PageRetries uint32 PageFound uint32 GetRead uint32 } // ExtendedPrecisionStats contains high precision counters used to track the // total number of bytes read, written, or flushed, during XFS operations. type ExtendedPrecisionStats struct { FlushBytes uint64 WriteBytes uint64 ReadBytes uint64 }