From 9969c4e810998a8084153ff76769fbff85f7f255 Mon Sep 17 00:00:00 2001 From: Cameron Sparr Date: Mon, 31 Aug 2015 14:03:38 -0600 Subject: [PATCH] Add system uptime metric, string formatted AND in float64 closes #150 --- CHANGELOG.md | 3 +- Godeps/Godeps.json | 7 +- .../cloudfoundry/gosigar/.gitignore | 1 + .../cloudfoundry/gosigar/.travis.yml | 8 + .../github.com/cloudfoundry/gosigar/LICENSE | 201 ++++++++ .../github.com/cloudfoundry/gosigar/NOTICE | 9 + .../github.com/cloudfoundry/gosigar/README.md | 22 + .../cloudfoundry/gosigar/Vagrantfile | 25 + .../cloudfoundry/gosigar/concrete_sigar.go | 69 +++ .../gosigar/concrete_sigar_test.go | 85 ++++ .../cloudfoundry/gosigar/examples/cputimes.go | 52 ++ .../cloudfoundry/gosigar/examples/df.go | 39 ++ .../cloudfoundry/gosigar/examples/free.go | 33 ++ .../cloudfoundry/gosigar/examples/ps.go | 37 ++ .../cloudfoundry/gosigar/examples/uptime.go | 27 + .../cloudfoundry/gosigar/fakes/fake_sigar.go | 72 +++ .../cloudfoundry/gosigar/psnotify/README.md | 50 ++ .../cloudfoundry/gosigar/psnotify/psnotify.go | 136 +++++ .../gosigar/psnotify/psnotify_bsd.go | 93 ++++ .../gosigar/psnotify/psnotify_linux.go | 253 ++++++++++ .../gosigar/psnotify/psnotify_test.go | 283 +++++++++++ .../cloudfoundry/gosigar/sigar_darwin.go | 467 ++++++++++++++++++ .../cloudfoundry/gosigar/sigar_format.go | 126 +++++ .../cloudfoundry/gosigar/sigar_interface.go | 141 ++++++ .../gosigar/sigar_interface_test.go | 135 +++++ .../cloudfoundry/gosigar/sigar_linux.go | 386 +++++++++++++++ .../cloudfoundry/gosigar/sigar_linux_test.go | 225 +++++++++ .../cloudfoundry/gosigar/sigar_suite_test.go | 13 + .../cloudfoundry/gosigar/sigar_unix.go | 26 + .../cloudfoundry/gosigar/sigar_util.go | 22 + .../cloudfoundry/gosigar/sigar_windows.go | 100 ++++ .../gosigar/sigar_windows_test.go | 32 ++ Makefile | 2 +- plugins/system/ps.go | 6 - plugins/system/system.go | 35 +- plugins/system/system_test.go | 20 +- 36 files changed, 3200 insertions(+), 41 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.gitignore create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/LICENSE create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/NOTICE create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/README.md create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/Vagrantfile create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar_test.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/cputimes.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/df.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/free.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/ps.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/uptime.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/fakes/fake_sigar.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/README.md create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_bsd.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_linux.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_test.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_darwin.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_format.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface_test.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux_test.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_suite_test.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_unix.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_util.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows.go create mode 100644 Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f42cb6a..940df2cab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ### Features -[#158](https://github.com/influxdb/telegraf/pull/158): Apache Plugin +[#150](https://github.com/influxdb/telegraf/pull/150): Add Host Uptime metric to system plugin +[#158](https://github.com/influxdb/telegraf/pull/158): Apache Plugin. Thanks @KPACHbIuLLIAnO4 ### Bugfixes diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 9400bc682..8ffab7faa 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/influxdb/telegraf", - "GoVersion": "go1.4.2", + "GoVersion": "go1.5", "Packages": [ "./..." ], @@ -28,6 +28,11 @@ "ImportPath": "github.com/cenkalti/backoff", "Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99" }, + { + "ImportPath": "github.com/cloudfoundry/gosigar", + "Comment": "scotty_09012012-27-g3ed7c74", + "Rev": "3ed7c74352dae6dc00bdc8c74045375352e3ec05" + }, { "ImportPath": "github.com/dancannon/gorethink/encoding", "Comment": "v1.x.x-1-g786f12a", diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.gitignore b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.gitignore new file mode 100644 index 000000000..8000dd9db --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.gitignore @@ -0,0 +1 @@ +.vagrant diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.travis.yml b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.travis.yml new file mode 100644 index 000000000..2a9c5d0c7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/.travis.yml @@ -0,0 +1,8 @@ +language: go + +go: + - 1.2 + +install: + - 'go install github.com/onsi/ginkgo/ginkgo' +script: 'ginkgo -r' diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/LICENSE b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/LICENSE new file mode 100644 index 000000000..11069edd7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/NOTICE b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/NOTICE new file mode 100644 index 000000000..fda553b5c --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/NOTICE @@ -0,0 +1,9 @@ +Copyright (c) [2009-2011] VMware, Inc. All Rights Reserved. + +This product is licensed to you under the Apache License, Version 2.0 (the "License"). +You may not use this product except in compliance with the License. + +This product includes a number of subcomponents with +separate copyright notices and license terms. Your use of these +subcomponents is subject to the terms and conditions of the +subcomponent's license, as noted in the LICENSE file. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/README.md b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/README.md new file mode 100644 index 000000000..90d51f9b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/README.md @@ -0,0 +1,22 @@ +# Go sigar + +## Overview + +Go sigar is a golang implementation of the +[sigar API](https://github.com/hyperic/sigar). The Go version of +sigar has a very similar interface, but is being written from scratch +in pure go/cgo, rather than cgo bindings for libsigar. + +## Test drive + + $ go get github.com/cloudfoundry/gosigar + $ cd $GOPATH/src/github.com/cloudfoundry/gosigar/examples + $ go run uptime.go + +## Supported platforms + +Currently targeting modern flavors of darwin and linux. + +## License + +Apache 2.0 diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/Vagrantfile b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/Vagrantfile new file mode 100644 index 000000000..6fd990c14 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/Vagrantfile @@ -0,0 +1,25 @@ +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + config.vm.box = "hashicorp/precise64" + config.vm.provision "shell", inline: "mkdir -p /home/vagrant/go" + config.vm.synced_folder ".", "/home/vagrant/go/src/github.com/cloudfoundry/gosigar" + config.vm.provision "shell", inline: "chown -R vagrant:vagrant /home/vagrant/go" + install_go = <<-BASH + set -e + +if [ ! -d "/usr/local/go" ]; then + cd /tmp && wget https://storage.googleapis.com/golang/go1.3.3.linux-amd64.tar.gz + cd /usr/local + tar xvzf /tmp/go1.3.3.linux-amd64.tar.gz + echo 'export GOPATH=/home/vagrant/go; export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin' >> /home/vagrant/.bashrc +fi +export GOPATH=/home/vagrant/go +export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin +/usr/local/go/bin/go get -u github.com/onsi/ginkgo/ginkgo +/usr/local/go/bin/go get -u github.com/onsi/gomega; +BASH + config.vm.provision "shell", inline: 'apt-get install -y git-core' + config.vm.provision "shell", inline: install_go +end diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar.go new file mode 100644 index 000000000..0e80aa4b9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar.go @@ -0,0 +1,69 @@ +package sigar + +import ( + "time" +) + +type ConcreteSigar struct{} + +func (c *ConcreteSigar) CollectCpuStats(collectionInterval time.Duration) (<-chan Cpu, chan<- struct{}) { + // samplesCh is buffered to 1 value to immediately return first CPU sample + samplesCh := make(chan Cpu, 1) + + stopCh := make(chan struct{}) + + go func() { + var cpuUsage Cpu + + // Immediately provide non-delta value. + // samplesCh is buffered to 1 value, so it will not block. + cpuUsage.Get() + samplesCh <- cpuUsage + + ticker := time.NewTicker(collectionInterval) + + for { + select { + case <-ticker.C: + previousCpuUsage := cpuUsage + + cpuUsage.Get() + + select { + case samplesCh <- cpuUsage.Delta(previousCpuUsage): + default: + // Include default to avoid channel blocking + } + + case <-stopCh: + return + } + } + }() + + return samplesCh, stopCh +} + +func (c *ConcreteSigar) GetLoadAverage() (LoadAverage, error) { + l := LoadAverage{} + err := l.Get() + return l, err +} + +func (c *ConcreteSigar) GetMem() (Mem, error) { + m := Mem{} + err := m.Get() + return m, err +} + +func (c *ConcreteSigar) GetSwap() (Swap, error) { + s := Swap{} + err := s.Get() + return s, err +} + +func (c *ConcreteSigar) GetFileSystemUsage(path string) (FileSystemUsage, error) { + f := FileSystemUsage{} + err := f.Get(path) + return f, err +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar_test.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar_test.go new file mode 100644 index 000000000..ec51811c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/concrete_sigar_test.go @@ -0,0 +1,85 @@ +package sigar_test + +import ( + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + sigar "github.com/cloudfoundry/gosigar" +) + +var _ = Describe("ConcreteSigar", func() { + var concreteSigar *sigar.ConcreteSigar + + BeforeEach(func() { + concreteSigar = &sigar.ConcreteSigar{} + }) + + Describe("CollectCpuStats", func() { + It("immediately makes first CPU usage available even though it's not very accurate", func() { + samplesCh, stop := concreteSigar.CollectCpuStats(500 * time.Millisecond) + + firstValue := <-samplesCh + Expect(firstValue.User).To(BeNumerically(">", 0)) + + stop <- struct{}{} + }) + + It("makes CPU usage delta values available", func() { + samplesCh, stop := concreteSigar.CollectCpuStats(500 * time.Millisecond) + + firstValue := <-samplesCh + + secondValue := <-samplesCh + Expect(secondValue.User).To(BeNumerically("<", firstValue.User)) + + stop <- struct{}{} + }) + + It("does not block", func() { + _, stop := concreteSigar.CollectCpuStats(10 * time.Millisecond) + + // Sleep long enough for samplesCh to fill at least 2 values + time.Sleep(20 * time.Millisecond) + + stop <- struct{}{} + + // If CollectCpuStats blocks it will never get here + Expect(true).To(BeTrue()) + }) + }) + + It("GetLoadAverage", func() { + avg, err := concreteSigar.GetLoadAverage() + Expect(avg.One).ToNot(BeNil()) + Expect(avg.Five).ToNot(BeNil()) + Expect(avg.Fifteen).ToNot(BeNil()) + + Expect(err).ToNot(HaveOccurred()) + }) + + It("GetMem", func() { + mem, err := concreteSigar.GetMem() + Expect(err).ToNot(HaveOccurred()) + + Expect(mem.Total).To(BeNumerically(">", 0)) + Expect(mem.Used + mem.Free).To(BeNumerically("<=", mem.Total)) + }) + + It("GetSwap", func() { + swap, err := concreteSigar.GetSwap() + Expect(err).ToNot(HaveOccurred()) + Expect(swap.Used + swap.Free).To(BeNumerically("<=", swap.Total)) + }) + + It("GetSwap", func() { + fsusage, err := concreteSigar.GetFileSystemUsage("/") + Expect(err).ToNot(HaveOccurred()) + Expect(fsusage.Total).ToNot(BeNil()) + + fsusage, err = concreteSigar.GetFileSystemUsage("T O T A L L Y B O G U S") + Expect(err).To(HaveOccurred()) + Expect(fsusage.Total).To(Equal(uint64(0))) + }) +}) diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/cputimes.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/cputimes.go new file mode 100644 index 000000000..cdfcd2f11 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/cputimes.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "time" + + "github.com/cloudfoundry/gosigar" +) + +func main() { + cpus := sigar.CpuList{} + cpus.Get() + tcpu := getOverallCpu(cpus) + + for i, cpu := range cpus.List { + fmt.Printf("CPU%d Ticks: %d\n", i, cpu.Total()) + } + + fmt.Printf("Total CPU Ticks: %d\n", tcpu.Total()) + fmt.Printf("Total CPU Time: %d\n", tcpu.Total()/128) + fmt.Printf("User CPU Time: %d\n", tcpu.User/128) + + time.Sleep(1 * time.Second) + tcpu2 := sigar.Cpu{} + tcpu2.Get() + + dcpu := tcpu2.Delta(tcpu) + tcpuDelta := tcpu2.Total() - tcpu.Total() + iPercentage := 100.0 * float64(dcpu.Idle) / float64(tcpuDelta) + fmt.Printf("Idle percentage: %f\n", iPercentage) + bPercentage := 100.0 * float64(busy(tcpu2)-busy(tcpu)) / float64(tcpuDelta) + fmt.Printf("Busy percentage: %f\n", bPercentage) +} + +func busy(c sigar.Cpu) uint64 { + return c.Total() - c.Idle +} + +func getOverallCpu(cl sigar.CpuList) sigar.Cpu { + var overallCpu sigar.Cpu + for _, c := range cl.List { + overallCpu.User += c.User + overallCpu.Nice += c.Nice + overallCpu.Sys += c.Sys + overallCpu.Idle += c.Idle + overallCpu.Wait += c.Wait + overallCpu.Irq += c.Irq + overallCpu.SoftIrq += c.SoftIrq + overallCpu.Stolen += c.Stolen + } + return overallCpu +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/df.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/df.go new file mode 100644 index 000000000..96c92f41d --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/df.go @@ -0,0 +1,39 @@ +// Copyright (c) 2012 VMware, Inc. + +package main + +import ( + "fmt" + "github.com/cloudfoundry/gosigar" + "os" +) + +const output_format = "%-15s %4s %4s %5s %4s %-15s\n" + +func formatSize(size uint64) string { + return sigar.FormatSize(size * 1024) +} + +func main() { + fslist := sigar.FileSystemList{} + fslist.Get() + + fmt.Fprintf(os.Stdout, output_format, + "Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on") + + for _, fs := range fslist.List { + dir_name := fs.DirName + + usage := sigar.FileSystemUsage{} + + usage.Get(dir_name) + + fmt.Fprintf(os.Stdout, output_format, + fs.DevName, + formatSize(usage.Total), + formatSize(usage.Used), + formatSize(usage.Avail), + sigar.FormatPercent(usage.UsePercent()), + dir_name) + } +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/free.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/free.go new file mode 100644 index 000000000..9bf9d3db3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/free.go @@ -0,0 +1,33 @@ +// Copyright (c) 2012 VMware, Inc. + +package main + +import ( + "fmt" + "github.com/cloudfoundry/gosigar" + "os" +) + +func format(val uint64) uint64 { + return val / 1024 +} + +func main() { + mem := sigar.Mem{} + swap := sigar.Swap{} + + mem.Get() + swap.Get() + + fmt.Fprintf(os.Stdout, "%18s %10s %10s\n", + "total", "used", "free") + + fmt.Fprintf(os.Stdout, "Mem: %10d %10d %10d\n", + format(mem.Total), format(mem.Used), format(mem.Free)) + + fmt.Fprintf(os.Stdout, "-/+ buffers/cache: %10d %10d\n", + format(mem.ActualUsed), format(mem.ActualFree)) + + fmt.Fprintf(os.Stdout, "Swap: %10d %10d %10d\n", + format(swap.Total), format(swap.Used), format(swap.Free)) +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/ps.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/ps.go new file mode 100644 index 000000000..e3cc2281f --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/ps.go @@ -0,0 +1,37 @@ +// Copyright (c) 2012 VMware, Inc. + +package main + +import ( + "fmt" + "github.com/cloudfoundry/gosigar" +) + +func main() { + pids := sigar.ProcList{} + pids.Get() + + // ps -eo pid,ppid,stime,time,rss,state,comm + fmt.Print(" PID PPID STIME TIME RSS S COMMAND\n") + + for _, pid := range pids.List { + state := sigar.ProcState{} + mem := sigar.ProcMem{} + time := sigar.ProcTime{} + + if err := state.Get(pid); err != nil { + continue + } + if err := mem.Get(pid); err != nil { + continue + } + if err := time.Get(pid); err != nil { + continue + } + + fmt.Printf("%5d %5d %s %s %6d %c %s\n", + pid, state.Ppid, + time.FormatStartTime(), time.FormatTotal(), + mem.Resident/1024, state.State, state.Name) + } +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/uptime.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/uptime.go new file mode 100644 index 000000000..337a9b01a --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/examples/uptime.go @@ -0,0 +1,27 @@ +// Copyright (c) 2012 VMware, Inc. + +package main + +import ( + "fmt" + "github.com/cloudfoundry/gosigar" + "os" + "time" +) + +func main() { + concreteSigar := sigar.ConcreteSigar{} + + uptime := sigar.Uptime{} + uptime.Get() + avg, err := concreteSigar.GetLoadAverage() + if err != nil { + fmt.Printf("Failed to get load average") + return + } + + fmt.Fprintf(os.Stdout, " %s up %s load average: %.2f, %.2f, %.2f\n", + time.Now().Format("15:04:05"), + uptime.Format(), + avg.One, avg.Five, avg.Fifteen) +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/fakes/fake_sigar.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/fakes/fake_sigar.go new file mode 100644 index 000000000..6fb77417c --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/fakes/fake_sigar.go @@ -0,0 +1,72 @@ +package fakes + +import ( + "time" + + sigar "github.com/cloudfoundry/gosigar" +) + +type FakeSigar struct { + LoadAverage sigar.LoadAverage + LoadAverageErr error + + Mem sigar.Mem + MemErr error + + Swap sigar.Swap + SwapErr error + + FileSystemUsage sigar.FileSystemUsage + FileSystemUsageErr error + FileSystemUsagePath string + + CollectCpuStatsCpuCh chan sigar.Cpu + CollectCpuStatsStopCh chan struct{} +} + +func NewFakeSigar() *FakeSigar { + return &FakeSigar{ + CollectCpuStatsCpuCh: make(chan sigar.Cpu, 1), + CollectCpuStatsStopCh: make(chan struct{}), + } +} + +func (f *FakeSigar) CollectCpuStats(collectionInterval time.Duration) (<-chan sigar.Cpu, chan<- struct{}) { + samplesCh := make(chan sigar.Cpu, 1) + stopCh := make(chan struct{}) + + go func() { + for { + select { + case cpuStat := <-f.CollectCpuStatsCpuCh: + select { + case samplesCh <- cpuStat: + default: + // Include default to avoid channel blocking + } + + case <-f.CollectCpuStatsStopCh: + return + } + } + }() + + return samplesCh, stopCh +} + +func (f *FakeSigar) GetLoadAverage() (sigar.LoadAverage, error) { + return f.LoadAverage, f.LoadAverageErr +} + +func (f *FakeSigar) GetMem() (sigar.Mem, error) { + return f.Mem, f.MemErr +} + +func (f *FakeSigar) GetSwap() (sigar.Swap, error) { + return f.Swap, f.SwapErr +} + +func (f *FakeSigar) GetFileSystemUsage(path string) (sigar.FileSystemUsage, error) { + f.FileSystemUsagePath = path + return f.FileSystemUsage, f.FileSystemUsageErr +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/README.md b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/README.md new file mode 100644 index 000000000..dd34ebcfb --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/README.md @@ -0,0 +1,50 @@ +# Process notifications for Go + +## Overview + +The psnotify package captures process events from the kernel via +kqueue on Darwin/BSD and the netlink connector on Linux. + +The psnotify API is similar to the +[fsnotify](https://github.com/howeyc/fsnotify) package. + +Example: +```go + watcher, err := psnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + + // Process events + go func() { + for { + select { + case ev := <-watcher.Fork: + log.Println("fork event:", ev) + case ev := <-watcher.Exec: + log.Println("exec event:", ev) + case ev := <-watcher.Exit: + log.Println("exit event:", ev) + case err := <-watcher.Error: + log.Println("error:", err) + } + } + }() + + err = watcher.Watch(os.Getpid(), psnotify.PROC_EVENT_ALL) + if err != nil { + log.Fatal(err) + } + + /* ... do stuff ... */ + watcher.Close() +``` + +## Supported platforms + +Currently targeting modern flavors of Darwin and Linux. +Should work on BSD, but untested. + +## License + +Apache 2.0 diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify.go new file mode 100644 index 000000000..6a69f4de2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify.go @@ -0,0 +1,136 @@ +// Copyright (c) 2012 VMware, Inc. + +package psnotify + +import ( + "errors" + "fmt" +) + +type ProcEventFork struct { + ParentPid int // Pid of the process that called fork() + ChildPid int // Child process pid created by fork() +} + +type ProcEventExec struct { + Pid int // Pid of the process that called exec() +} + +type ProcEventExit struct { + Pid int // Pid of the process that called exit() +} + +type watch struct { + flags uint32 // Saved value of Watch() flags param +} + +type eventListener interface { + close() error // Watch.Close() closes the OS specific listener +} + +type Watcher struct { + listener eventListener // OS specifics (kqueue or netlink) + watches map[int]*watch // Map of watched process ids + Error chan error // Errors are sent on this channel + Fork chan *ProcEventFork // Fork events are sent on this channel + Exec chan *ProcEventExec // Exec events are sent on this channel + Exit chan *ProcEventExit // Exit events are sent on this channel + done chan bool // Used to stop the readEvents() goroutine + isClosed bool // Set to true when Close() is first called +} + +// Initialize event listener and channels +func NewWatcher() (*Watcher, error) { + listener, err := createListener() + + if err != nil { + return nil, err + } + + w := &Watcher{ + listener: listener, + watches: make(map[int]*watch), + Fork: make(chan *ProcEventFork), + Exec: make(chan *ProcEventExec), + Exit: make(chan *ProcEventExit), + Error: make(chan error), + done: make(chan bool, 1), + } + + go w.readEvents() + return w, nil +} + +// Close event channels when done message is received +func (w *Watcher) finish() { + close(w.Fork) + close(w.Exec) + close(w.Exit) + close(w.Error) +} + +// Closes the OS specific event listener, +// removes all watches and closes all event channels. +func (w *Watcher) Close() error { + if w.isClosed { + return nil + } + w.isClosed = true + + for pid := range w.watches { + w.RemoveWatch(pid) + } + + w.done <- true + + w.listener.close() + + return nil +} + +// Add pid to the watched process set. +// The flags param is a bitmask of process events to capture, +// must be one or more of: PROC_EVENT_FORK, PROC_EVENT_EXEC, PROC_EVENT_EXIT +func (w *Watcher) Watch(pid int, flags uint32) error { + if w.isClosed { + return errors.New("psnotify watcher is closed") + } + + watchEntry, found := w.watches[pid] + + if found { + watchEntry.flags |= flags + } else { + if err := w.register(pid, flags); err != nil { + return err + } + w.watches[pid] = &watch{flags: flags} + } + + return nil +} + +// Remove pid from the watched process set. +func (w *Watcher) RemoveWatch(pid int) error { + _, ok := w.watches[pid] + if !ok { + msg := fmt.Sprintf("watch for pid=%d does not exist", pid) + return errors.New(msg) + } + delete(w.watches, pid) + return w.unregister(pid) +} + +// Internal helper to check if there is a message on the "done" channel. +// The "done" message is sent by the Close() method; when received here, +// the Watcher.finish method is called to close all channels and return +// true - in which case the caller should break from the readEvents loop. +func (w *Watcher) isDone() bool { + var done bool + select { + case done = <-w.done: + w.finish() + default: + } + return done +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_bsd.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_bsd.go new file mode 100644 index 000000000..e147d7638 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_bsd.go @@ -0,0 +1,93 @@ +// Copyright (c) 2012 VMware, Inc. + +// +build darwin freebsd netbsd openbsd + +// Go interface to BSD kqueue process events. +package psnotify + +import ( + "syscall" +) + +const ( + // Flags (from ) + PROC_EVENT_FORK = syscall.NOTE_FORK // fork() events + PROC_EVENT_EXEC = syscall.NOTE_EXEC // exec() events + PROC_EVENT_EXIT = syscall.NOTE_EXIT // exit() events + + // Watch for all process events + PROC_EVENT_ALL = PROC_EVENT_FORK | PROC_EVENT_EXEC | PROC_EVENT_EXIT +) + +type kqueueListener struct { + kq int // The syscall.Kqueue() file descriptor + buf [1]syscall.Kevent_t // An event buffer for Add/Remove watch +} + +// Initialize bsd implementation of the eventListener interface +func createListener() (eventListener, error) { + listener := &kqueueListener{} + kq, err := syscall.Kqueue() + listener.kq = kq + return listener, err +} + +// Initialize Kevent_t fields and propagate changelist for the given pid +func (w *Watcher) kevent(pid int, fflags uint32, flags int) error { + listener, _ := w.listener.(*kqueueListener) + event := &listener.buf[0] + + syscall.SetKevent(event, pid, syscall.EVFILT_PROC, flags) + event.Fflags = fflags + + _, err := syscall.Kevent(listener.kq, listener.buf[:], nil, nil) + + return err +} + +// Delete filter for given pid from the queue +func (w *Watcher) unregister(pid int) error { + return w.kevent(pid, 0, syscall.EV_DELETE) +} + +// Add and enable filter for given pid in the queue +func (w *Watcher) register(pid int, flags uint32) error { + return w.kevent(pid, flags, syscall.EV_ADD|syscall.EV_ENABLE) +} + +// Poll the kqueue file descriptor and dispatch to the Event channels +func (w *Watcher) readEvents() { + listener, _ := w.listener.(*kqueueListener) + events := make([]syscall.Kevent_t, 10) + + for { + if w.isDone() { + return + } + + n, err := syscall.Kevent(listener.kq, nil, events, nil) + if err != nil { + w.Error <- err + continue + } + + for _, ev := range events[:n] { + pid := int(ev.Ident) + + switch ev.Fflags { + case syscall.NOTE_FORK: + w.Fork <- &ProcEventFork{ParentPid: pid} + case syscall.NOTE_EXEC: + w.Exec <- &ProcEventExec{Pid: pid} + case syscall.NOTE_EXIT: + w.RemoveWatch(pid) + w.Exit <- &ProcEventExit{Pid: pid} + } + } + } +} + +// Close our kqueue file descriptor; deletes any remaining filters +func (listener *kqueueListener) close() error { + return syscall.Close(listener.kq) +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_linux.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_linux.go new file mode 100644 index 000000000..f9154ef3d --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_linux.go @@ -0,0 +1,253 @@ +// Copyright (c) 2012 VMware, Inc. + +// Go interface to the Linux netlink process connector. +// See Documentation/connector/connector.txt in the linux kernel source tree. +package psnotify + +import ( + "bytes" + "encoding/binary" + "os" + "syscall" +) + +const ( + // internal flags (from ) + _CN_IDX_PROC = 0x1 + _CN_VAL_PROC = 0x1 + + // internal flags (from ) + _PROC_CN_MCAST_LISTEN = 1 + _PROC_CN_MCAST_IGNORE = 2 + + // Flags (from ) + PROC_EVENT_FORK = 0x00000001 // fork() events + PROC_EVENT_EXEC = 0x00000002 // exec() events + PROC_EVENT_EXIT = 0x80000000 // exit() events + + // Watch for all process events + PROC_EVENT_ALL = PROC_EVENT_FORK | PROC_EVENT_EXEC | PROC_EVENT_EXIT +) + +var ( + byteOrder = binary.LittleEndian +) + +// linux/connector.h: struct cb_id +type cbId struct { + Idx uint32 + Val uint32 +} + +// linux/connector.h: struct cb_msg +type cnMsg struct { + Id cbId + Seq uint32 + Ack uint32 + Len uint16 + Flags uint16 +} + +// linux/cn_proc.h: struct proc_event.{what,cpu,timestamp_ns} +type procEventHeader struct { + What uint32 + Cpu uint32 + Timestamp uint64 +} + +// linux/cn_proc.h: struct proc_event.fork +type forkProcEvent struct { + ParentPid uint32 + ParentTgid uint32 + ChildPid uint32 + ChildTgid uint32 +} + +// linux/cn_proc.h: struct proc_event.exec +type execProcEvent struct { + ProcessPid uint32 + ProcessTgid uint32 +} + +// linux/cn_proc.h: struct proc_event.exit +type exitProcEvent struct { + ProcessPid uint32 + ProcessTgid uint32 + ExitCode uint32 + ExitSignal uint32 +} + +// standard netlink header + connector header +type netlinkProcMessage struct { + Header syscall.NlMsghdr + Data cnMsg +} + +type netlinkListener struct { + addr *syscall.SockaddrNetlink // Netlink socket address + sock int // The syscall.Socket() file descriptor + seq uint32 // struct cn_msg.seq +} + +// Initialize linux implementation of the eventListener interface +func createListener() (eventListener, error) { + listener := &netlinkListener{} + err := listener.bind() + return listener, err +} + +// noop on linux +func (w *Watcher) unregister(pid int) error { + return nil +} + +// noop on linux +func (w *Watcher) register(pid int, flags uint32) error { + return nil +} + +// Read events from the netlink socket +func (w *Watcher) readEvents() { + buf := make([]byte, syscall.Getpagesize()) + + listener, _ := w.listener.(*netlinkListener) + + for { + if w.isDone() { + return + } + + nr, _, err := syscall.Recvfrom(listener.sock, buf, 0) + + if err != nil { + w.Error <- err + continue + } + if nr < syscall.NLMSG_HDRLEN { + w.Error <- syscall.EINVAL + continue + } + + msgs, _ := syscall.ParseNetlinkMessage(buf[:nr]) + + for _, m := range msgs { + if m.Header.Type == syscall.NLMSG_DONE { + w.handleEvent(m.Data) + } + } + } +} + +// Internal helper to check if pid && event is being watched +func (w *Watcher) isWatching(pid int, event uint32) bool { + if watch, ok := w.watches[pid]; ok { + return (watch.flags & event) == event + } + return false +} + +// Dispatch events from the netlink socket to the Event channels. +// Unlike bsd kqueue, netlink receives events for all pids, +// so we apply filtering based on the watch table via isWatching() +func (w *Watcher) handleEvent(data []byte) { + buf := bytes.NewBuffer(data) + msg := &cnMsg{} + hdr := &procEventHeader{} + + binary.Read(buf, byteOrder, msg) + binary.Read(buf, byteOrder, hdr) + + switch hdr.What { + case PROC_EVENT_FORK: + event := &forkProcEvent{} + binary.Read(buf, byteOrder, event) + ppid := int(event.ParentTgid) + pid := int(event.ChildTgid) + + if w.isWatching(ppid, PROC_EVENT_EXEC) { + // follow forks + watch, _ := w.watches[ppid] + w.Watch(pid, watch.flags) + } + + if w.isWatching(ppid, PROC_EVENT_FORK) { + w.Fork <- &ProcEventFork{ParentPid: ppid, ChildPid: pid} + } + case PROC_EVENT_EXEC: + event := &execProcEvent{} + binary.Read(buf, byteOrder, event) + pid := int(event.ProcessTgid) + + if w.isWatching(pid, PROC_EVENT_EXEC) { + w.Exec <- &ProcEventExec{Pid: pid} + } + case PROC_EVENT_EXIT: + event := &exitProcEvent{} + binary.Read(buf, byteOrder, event) + pid := int(event.ProcessTgid) + + if w.isWatching(pid, PROC_EVENT_EXIT) { + w.RemoveWatch(pid) + w.Exit <- &ProcEventExit{Pid: pid} + } + } +} + +// Bind our netlink socket and +// send a listen control message to the connector driver. +func (listener *netlinkListener) bind() error { + sock, err := syscall.Socket( + syscall.AF_NETLINK, + syscall.SOCK_DGRAM, + syscall.NETLINK_CONNECTOR) + + if err != nil { + return err + } + + listener.sock = sock + listener.addr = &syscall.SockaddrNetlink{ + Family: syscall.AF_NETLINK, + Groups: _CN_IDX_PROC, + } + + err = syscall.Bind(listener.sock, listener.addr) + + if err != nil { + return err + } + + return listener.send(_PROC_CN_MCAST_LISTEN) +} + +// Send an ignore control message to the connector driver +// and close our netlink socket. +func (listener *netlinkListener) close() error { + err := listener.send(_PROC_CN_MCAST_IGNORE) + syscall.Close(listener.sock) + return err +} + +// Generic method for sending control messages to the connector +// driver; where op is one of PROC_CN_MCAST_{LISTEN,IGNORE} +func (listener *netlinkListener) send(op uint32) error { + listener.seq++ + pr := &netlinkProcMessage{} + plen := binary.Size(pr.Data) + binary.Size(op) + pr.Header.Len = syscall.NLMSG_HDRLEN + uint32(plen) + pr.Header.Type = uint16(syscall.NLMSG_DONE) + pr.Header.Flags = 0 + pr.Header.Seq = listener.seq + pr.Header.Pid = uint32(os.Getpid()) + + pr.Data.Id.Idx = _CN_IDX_PROC + pr.Data.Id.Val = _CN_VAL_PROC + + pr.Data.Len = uint16(binary.Size(op)) + + buf := bytes.NewBuffer(make([]byte, 0, pr.Header.Len)) + binary.Write(buf, byteOrder, pr) + binary.Write(buf, byteOrder, op) + + return syscall.Sendto(listener.sock, buf.Bytes(), 0, listener.addr) +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_test.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_test.go new file mode 100644 index 000000000..28f38a8d7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/psnotify/psnotify_test.go @@ -0,0 +1,283 @@ +// Copyright (c) 2012 VMware, Inc. + +package psnotify + +import ( + "fmt" + "os" + "os/exec" + "runtime" + "syscall" + "testing" + "time" +) + +type anyEvent struct { + exits []int + forks []int + execs []int + errors []error + done chan bool +} + +type testWatcher struct { + t *testing.T + watcher *Watcher + events *anyEvent +} + +// General purpose Watcher wrapper for all tests +func newTestWatcher(t *testing.T) *testWatcher { + watcher, err := NewWatcher() + if err != nil { + t.Fatal(err) + } + + events := &anyEvent{ + done: make(chan bool, 1), + } + + tw := &testWatcher{ + t: t, + watcher: watcher, + events: events, + } + + go func() { + for { + select { + case <-events.done: + return + case ev := <-watcher.Fork: + events.forks = append(events.forks, ev.ParentPid) + case ev := <-watcher.Exec: + events.execs = append(events.execs, ev.Pid) + case ev := <-watcher.Exit: + events.exits = append(events.exits, ev.Pid) + case err := <-watcher.Error: + events.errors = append(events.errors, err) + } + } + }() + + return tw +} + +func (tw *testWatcher) close() { + pause := 100 * time.Millisecond + time.Sleep(pause) + + tw.events.done <- true + + tw.watcher.Close() + + time.Sleep(pause) +} + +func skipTest(t *testing.T) bool { + if runtime.GOOS == "linux" && os.Getuid() != 0 { + fmt.Println("SKIP: test must be run as root on linux") + return true + } + return false +} + +func startSleepCommand(t *testing.T) *exec.Cmd { + cmd := exec.Command("sh", "-c", "sleep 100") + if err := cmd.Start(); err != nil { + t.Error(err) + } + return cmd +} + +func runCommand(t *testing.T, name string) *exec.Cmd { + cmd := exec.Command(name) + if err := cmd.Run(); err != nil { + t.Error(err) + } + return cmd +} + +func expectEvents(t *testing.T, num int, name string, pids []int) bool { + if len(pids) != num { + t.Errorf("Expected %d %s events, got=%v", num, name, pids) + return false + } + return true +} + +func expectEventPid(t *testing.T, name string, expect int, pid int) bool { + if expect != pid { + t.Errorf("Expected %s pid=%d, received=%d", name, expect, pid) + return false + } + return true +} + +func TestWatchFork(t *testing.T) { + if skipTest(t) { + return + } + + pid := os.Getpid() + + tw := newTestWatcher(t) + + // no watches added yet, so this fork event will no be captured + runCommand(t, "date") + + // watch fork events for this process + if err := tw.watcher.Watch(pid, PROC_EVENT_FORK); err != nil { + t.Error(err) + } + + // this fork event will be captured, + // the exec and exit events will not be captured + runCommand(t, "cal") + + tw.close() + + if expectEvents(t, 1, "forks", tw.events.forks) { + expectEventPid(t, "fork", pid, tw.events.forks[0]) + } + + expectEvents(t, 0, "execs", tw.events.execs) + expectEvents(t, 0, "exits", tw.events.exits) +} + +func TestWatchExit(t *testing.T) { + if skipTest(t) { + return + } + + tw := newTestWatcher(t) + + cmd := startSleepCommand(t) + + childPid := cmd.Process.Pid + + // watch for exit event of our child process + if err := tw.watcher.Watch(childPid, PROC_EVENT_EXIT); err != nil { + t.Error(err) + } + + // kill our child process, triggers exit event + syscall.Kill(childPid, syscall.SIGTERM) + + cmd.Wait() + + tw.close() + + expectEvents(t, 0, "forks", tw.events.forks) + + expectEvents(t, 0, "execs", tw.events.execs) + + if expectEvents(t, 1, "exits", tw.events.exits) { + expectEventPid(t, "exit", childPid, tw.events.exits[0]) + } +} + +// combined version of TestWatchFork() and TestWatchExit() +func TestWatchForkAndExit(t *testing.T) { + if skipTest(t) { + return + } + + pid := os.Getpid() + + tw := newTestWatcher(t) + + if err := tw.watcher.Watch(pid, PROC_EVENT_FORK); err != nil { + t.Error(err) + } + + cmd := startSleepCommand(t) + + childPid := cmd.Process.Pid + + if err := tw.watcher.Watch(childPid, PROC_EVENT_EXIT); err != nil { + t.Error(err) + } + + syscall.Kill(childPid, syscall.SIGTERM) + + cmd.Wait() + + tw.close() + + if expectEvents(t, 1, "forks", tw.events.forks) { + expectEventPid(t, "fork", pid, tw.events.forks[0]) + } + + expectEvents(t, 0, "execs", tw.events.execs) + + if expectEvents(t, 1, "exits", tw.events.exits) { + expectEventPid(t, "exit", childPid, tw.events.exits[0]) + } +} + +func TestWatchFollowFork(t *testing.T) { + if skipTest(t) { + return + } + + // Darwin is not able to follow forks, as the kqueue fork event + // does not provide the child pid. + if runtime.GOOS != "linux" { + fmt.Println("SKIP: test follow forks is linux only") + return + } + + pid := os.Getpid() + + tw := newTestWatcher(t) + + // watch for all process events related to this process + if err := tw.watcher.Watch(pid, PROC_EVENT_ALL); err != nil { + t.Error(err) + } + + commands := []string{"date", "cal"} + childPids := make([]int, len(commands)) + + // triggers fork/exec/exit events for each command + for i, name := range commands { + cmd := runCommand(t, name) + childPids[i] = cmd.Process.Pid + } + + // remove watch for this process + tw.watcher.RemoveWatch(pid) + + // run commands again to make sure we don't receive any unwanted events + for _, name := range commands { + runCommand(t, name) + } + + tw.close() + + // run commands again to make sure nothing panics after + // closing the watcher + for _, name := range commands { + runCommand(t, name) + } + + num := len(commands) + if expectEvents(t, num, "forks", tw.events.forks) { + for _, epid := range tw.events.forks { + expectEventPid(t, "fork", pid, epid) + } + } + + if expectEvents(t, num, "execs", tw.events.execs) { + for i, epid := range tw.events.execs { + expectEventPid(t, "exec", childPids[i], epid) + } + } + + if expectEvents(t, num, "exits", tw.events.exits) { + for i, epid := range tw.events.exits { + expectEventPid(t, "exit", childPids[i], epid) + } + } +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_darwin.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_darwin.go new file mode 100644 index 000000000..e3a8c4b9c --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_darwin.go @@ -0,0 +1,467 @@ +// Copyright (c) 2012 VMware, Inc. + +package sigar + +/* +#include +#include +#include +#include +#include +#include +#include +#include +#include +*/ +import "C" + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "syscall" + "time" + "unsafe" +) + +func (self *LoadAverage) Get() error { + avg := []C.double{0, 0, 0} + + C.getloadavg(&avg[0], C.int(len(avg))) + + self.One = float64(avg[0]) + self.Five = float64(avg[1]) + self.Fifteen = float64(avg[2]) + + return nil +} + +func (self *Uptime) Get() error { + tv := syscall.Timeval32{} + + if err := sysctlbyname("kern.boottime", &tv); err != nil { + return err + } + + self.Length = time.Since(time.Unix(int64(tv.Sec), int64(tv.Usec)*1000)).Seconds() + + return nil +} + +func (self *Mem) Get() error { + var vmstat C.vm_statistics_data_t + + if err := sysctlbyname("hw.memsize", &self.Total); err != nil { + return err + } + + if err := vm_info(&vmstat); err != nil { + return err + } + + kern := uint64(vmstat.inactive_count) << 12 + self.Free = uint64(vmstat.free_count) << 12 + + self.Used = self.Total - self.Free + self.ActualFree = self.Free + kern + self.ActualUsed = self.Used - kern + + return nil +} + +type xsw_usage struct { + Total, Avail, Used uint64 +} + +func (self *Swap) Get() error { + sw_usage := xsw_usage{} + + if err := sysctlbyname("vm.swapusage", &sw_usage); err != nil { + return err + } + + self.Total = sw_usage.Total + self.Used = sw_usage.Used + self.Free = sw_usage.Avail + + return nil +} + +func (self *Cpu) Get() error { + var count C.mach_msg_type_number_t = C.HOST_CPU_LOAD_INFO_COUNT + var cpuload C.host_cpu_load_info_data_t + + status := C.host_statistics(C.host_t(C.mach_host_self()), + C.HOST_CPU_LOAD_INFO, + C.host_info_t(unsafe.Pointer(&cpuload)), + &count) + + if status != C.KERN_SUCCESS { + return fmt.Errorf("host_statistics error=%d", status) + } + + self.User = uint64(cpuload.cpu_ticks[C.CPU_STATE_USER]) + self.Sys = uint64(cpuload.cpu_ticks[C.CPU_STATE_SYSTEM]) + self.Idle = uint64(cpuload.cpu_ticks[C.CPU_STATE_IDLE]) + self.Nice = uint64(cpuload.cpu_ticks[C.CPU_STATE_NICE]) + + return nil +} + +func (self *CpuList) Get() error { + var count C.mach_msg_type_number_t + var cpuload *C.processor_cpu_load_info_data_t + var ncpu C.natural_t + + status := C.host_processor_info(C.host_t(C.mach_host_self()), + C.PROCESSOR_CPU_LOAD_INFO, + &ncpu, + (*C.processor_info_array_t)(unsafe.Pointer(&cpuload)), + &count) + + if status != C.KERN_SUCCESS { + return fmt.Errorf("host_processor_info error=%d", status) + } + + // jump through some cgo casting hoops and ensure we properly free + // the memory that cpuload points to + target := C.vm_map_t(C.mach_task_self_) + address := C.vm_address_t(uintptr(unsafe.Pointer(cpuload))) + defer C.vm_deallocate(target, address, C.vm_size_t(ncpu)) + + // the body of struct processor_cpu_load_info + // aka processor_cpu_load_info_data_t + var cpu_ticks [C.CPU_STATE_MAX]uint32 + + // copy the cpuload array to a []byte buffer + // where we can binary.Read the data + size := int(ncpu) * binary.Size(cpu_ticks) + buf := C.GoBytes(unsafe.Pointer(cpuload), C.int(size)) + + bbuf := bytes.NewBuffer(buf) + + self.List = make([]Cpu, 0, ncpu) + + for i := 0; i < int(ncpu); i++ { + cpu := Cpu{} + + err := binary.Read(bbuf, binary.LittleEndian, &cpu_ticks) + if err != nil { + return err + } + + cpu.User = uint64(cpu_ticks[C.CPU_STATE_USER]) + cpu.Sys = uint64(cpu_ticks[C.CPU_STATE_SYSTEM]) + cpu.Idle = uint64(cpu_ticks[C.CPU_STATE_IDLE]) + cpu.Nice = uint64(cpu_ticks[C.CPU_STATE_NICE]) + + self.List = append(self.List, cpu) + } + + return nil +} + +func (self *FileSystemList) Get() error { + num, err := getfsstat(nil, C.MNT_NOWAIT) + if num < 0 { + return err + } + + buf := make([]syscall.Statfs_t, num) + + num, err = getfsstat(buf, C.MNT_NOWAIT) + if err != nil { + return err + } + + fslist := make([]FileSystem, 0, num) + + for i := 0; i < num; i++ { + fs := FileSystem{} + + fs.DirName = bytePtrToString(&buf[i].Mntonname[0]) + fs.DevName = bytePtrToString(&buf[i].Mntfromname[0]) + fs.SysTypeName = bytePtrToString(&buf[i].Fstypename[0]) + + fslist = append(fslist, fs) + } + + self.List = fslist + + return err +} + +func (self *ProcList) Get() error { + n := C.proc_listpids(C.PROC_ALL_PIDS, 0, nil, 0) + if n <= 0 { + return syscall.EINVAL + } + buf := make([]byte, n) + n = C.proc_listpids(C.PROC_ALL_PIDS, 0, unsafe.Pointer(&buf[0]), n) + if n <= 0 { + return syscall.ENOMEM + } + + var pid int32 + num := int(n) / binary.Size(pid) + list := make([]int, 0, num) + bbuf := bytes.NewBuffer(buf) + + for i := 0; i < num; i++ { + if err := binary.Read(bbuf, binary.LittleEndian, &pid); err != nil { + return err + } + if pid == 0 { + continue + } + + list = append(list, int(pid)) + } + + self.List = list + + return nil +} + +func (self *ProcState) Get(pid int) error { + info := C.struct_proc_taskallinfo{} + + if err := task_info(pid, &info); err != nil { + return err + } + + self.Name = C.GoString(&info.pbsd.pbi_comm[0]) + + switch info.pbsd.pbi_status { + case C.SIDL: + self.State = RunStateIdle + case C.SRUN: + self.State = RunStateRun + case C.SSLEEP: + self.State = RunStateSleep + case C.SSTOP: + self.State = RunStateStop + case C.SZOMB: + self.State = RunStateZombie + default: + self.State = RunStateUnknown + } + + self.Ppid = int(info.pbsd.pbi_ppid) + + self.Tty = int(info.pbsd.e_tdev) + + self.Priority = int(info.ptinfo.pti_priority) + + self.Nice = int(info.pbsd.pbi_nice) + + return nil +} + +func (self *ProcMem) Get(pid int) error { + info := C.struct_proc_taskallinfo{} + + if err := task_info(pid, &info); err != nil { + return err + } + + self.Size = uint64(info.ptinfo.pti_virtual_size) + self.Resident = uint64(info.ptinfo.pti_resident_size) + self.PageFaults = uint64(info.ptinfo.pti_faults) + + return nil +} + +func (self *ProcTime) Get(pid int) error { + info := C.struct_proc_taskallinfo{} + + if err := task_info(pid, &info); err != nil { + return err + } + + self.User = + uint64(info.ptinfo.pti_total_user) / uint64(time.Millisecond) + + self.Sys = + uint64(info.ptinfo.pti_total_system) / uint64(time.Millisecond) + + self.Total = self.User + self.Sys + + self.StartTime = (uint64(info.pbsd.pbi_start_tvsec) * 1000) + + (uint64(info.pbsd.pbi_start_tvusec) / 1000) + + return nil +} + +func (self *ProcArgs) Get(pid int) error { + var args []string + + argv := func(arg string) { + args = append(args, arg) + } + + err := kern_procargs(pid, nil, argv, nil) + + self.List = args + + return err +} + +func (self *ProcExe) Get(pid int) error { + exe := func(arg string) { + self.Name = arg + } + + return kern_procargs(pid, exe, nil, nil) +} + +// wrapper around sysctl KERN_PROCARGS2 +// callbacks params are optional, +// up to the caller as to which pieces of data they want +func kern_procargs(pid int, + exe func(string), + argv func(string), + env func(string, string)) error { + + mib := []C.int{C.CTL_KERN, C.KERN_PROCARGS2, C.int(pid)} + argmax := uintptr(C.ARG_MAX) + buf := make([]byte, argmax) + err := sysctl(mib, &buf[0], &argmax, nil, 0) + if err != nil { + return nil + } + + bbuf := bytes.NewBuffer(buf) + bbuf.Truncate(int(argmax)) + + var argc int32 + binary.Read(bbuf, binary.LittleEndian, &argc) + + path, err := bbuf.ReadBytes(0) + if exe != nil { + exe(string(chop(path))) + } + + // skip trailing \0's + for { + c, _ := bbuf.ReadByte() + if c != 0 { + bbuf.UnreadByte() + break // start of argv[0] + } + } + + for i := 0; i < int(argc); i++ { + arg, err := bbuf.ReadBytes(0) + if err == io.EOF { + break + } + if argv != nil { + argv(string(chop(arg))) + } + } + + if env == nil { + return nil + } + + delim := []byte{61} // "=" + + for { + line, err := bbuf.ReadBytes(0) + if err == io.EOF || line[0] == 0 { + break + } + pair := bytes.SplitN(chop(line), delim, 2) + env(string(pair[0]), string(pair[1])) + } + + return nil +} + +// XXX copied from zsyscall_darwin_amd64.go +func sysctl(mib []C.int, old *byte, oldlen *uintptr, + new *byte, newlen uintptr) (err error) { + var p0 unsafe.Pointer + p0 = unsafe.Pointer(&mib[0]) + _, _, e1 := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(p0), + uintptr(len(mib)), + uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), + uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = e1 + } + return +} + +func vm_info(vmstat *C.vm_statistics_data_t) error { + var count C.mach_msg_type_number_t = C.HOST_VM_INFO_COUNT + + status := C.host_statistics( + C.host_t(C.mach_host_self()), + C.HOST_VM_INFO, + C.host_info_t(unsafe.Pointer(vmstat)), + &count) + + if status != C.KERN_SUCCESS { + return fmt.Errorf("host_statistics=%d", status) + } + + return nil +} + +// generic Sysctl buffer unmarshalling +func sysctlbyname(name string, data interface{}) (err error) { + val, err := syscall.Sysctl(name) + if err != nil { + return err + } + + buf := []byte(val) + + switch v := data.(type) { + case *uint64: + *v = *(*uint64)(unsafe.Pointer(&buf[0])) + return + } + + bbuf := bytes.NewBuffer([]byte(val)) + return binary.Read(bbuf, binary.LittleEndian, data) +} + +// syscall.Getfsstat() wrapper is broken, roll our own to workaround. +func getfsstat(buf []syscall.Statfs_t, flags int) (n int, err error) { + var ptr uintptr + var size uintptr + + if len(buf) > 0 { + ptr = uintptr(unsafe.Pointer(&buf[0])) + size = unsafe.Sizeof(buf[0]) * uintptr(len(buf)) + } else { + ptr = uintptr(0) + size = uintptr(0) + } + + trap := uintptr(syscall.SYS_GETFSSTAT64) + ret, _, errno := syscall.Syscall(trap, ptr, size, uintptr(flags)) + + n = int(ret) + if errno != 0 { + err = errno + } + + return +} + +func task_info(pid int, info *C.struct_proc_taskallinfo) error { + size := C.int(unsafe.Sizeof(*info)) + ptr := unsafe.Pointer(info) + + n := C.proc_pidinfo(C.int(pid), C.PROC_PIDTASKALLINFO, 0, ptr, size) + if n != size { + return syscall.ENOMEM + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_format.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_format.go new file mode 100644 index 000000000..d80a64e88 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_format.go @@ -0,0 +1,126 @@ +// Copyright (c) 2012 VMware, Inc. + +package sigar + +import ( + "bufio" + "bytes" + "fmt" + "strconv" + "time" +) + +// Go version of apr_strfsize +func FormatSize(size uint64) string { + ord := []string{"K", "M", "G", "T", "P", "E"} + o := 0 + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + + if size < 973 { + fmt.Fprintf(w, "%3d ", size) + w.Flush() + return buf.String() + } + + for { + remain := size & 1023 + size >>= 10 + + if size >= 973 { + o++ + continue + } + + if size < 9 || (size == 9 && remain < 973) { + remain = ((remain * 5) + 256) / 512 + if remain >= 10 { + size++ + remain = 0 + } + + fmt.Fprintf(w, "%d.%d%s", size, remain, ord[o]) + break + } + + if remain >= 512 { + size++ + } + + fmt.Fprintf(w, "%3d%s", size, ord[o]) + break + } + + w.Flush() + return buf.String() +} + +func FormatPercent(percent float64) string { + return strconv.FormatFloat(percent, 'f', -1, 64) + "%" +} + +func (self *FileSystemUsage) UsePercent() float64 { + b_used := (self.Total - self.Free) / 1024 + b_avail := self.Avail / 1024 + utotal := b_used + b_avail + used := b_used + + if utotal != 0 { + u100 := used * 100 + pct := u100 / utotal + if u100%utotal != 0 { + pct += 1 + } + return (float64(pct) / float64(100)) * 100.0 + } + + return 0.0 +} + +func (self *Uptime) Format() string { + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + uptime := uint64(self.Length) + + days := uptime / (60 * 60 * 24) + + if days != 0 { + s := "" + if days > 1 { + s = "s" + } + fmt.Fprintf(w, "%d day%s, ", days, s) + } + + minutes := uptime / 60 + hours := minutes / 60 + hours %= 24 + minutes %= 60 + + fmt.Fprintf(w, "%2d:%02d", hours, minutes) + + w.Flush() + return buf.String() +} + +func (self *ProcTime) FormatStartTime() string { + if self.StartTime == 0 { + return "00:00" + } + start := time.Unix(int64(self.StartTime)/1000, 0) + format := "Jan02" + if time.Since(start).Seconds() < (60 * 60 * 24) { + format = "15:04" + } + return start.Format(format) +} + +func (self *ProcTime) FormatTotal() string { + t := self.Total / 1000 + ss := t % 60 + t /= 60 + mm := t % 60 + t /= 60 + hh := t % 24 + return fmt.Sprintf("%02d:%02d:%02d", hh, mm, ss) +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface.go new file mode 100644 index 000000000..dd72a76b0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface.go @@ -0,0 +1,141 @@ +package sigar + +import ( + "time" +) + +type Sigar interface { + CollectCpuStats(collectionInterval time.Duration) (<-chan Cpu, chan<- struct{}) + GetLoadAverage() (LoadAverage, error) + GetMem() (Mem, error) + GetSwap() (Swap, error) + GetFileSystemUsage(string) (FileSystemUsage, error) +} + +type Cpu struct { + User uint64 + Nice uint64 + Sys uint64 + Idle uint64 + Wait uint64 + Irq uint64 + SoftIrq uint64 + Stolen uint64 +} + +func (cpu *Cpu) Total() uint64 { + return cpu.User + cpu.Nice + cpu.Sys + cpu.Idle + + cpu.Wait + cpu.Irq + cpu.SoftIrq + cpu.Stolen +} + +func (cpu Cpu) Delta(other Cpu) Cpu { + return Cpu{ + User: cpu.User - other.User, + Nice: cpu.Nice - other.Nice, + Sys: cpu.Sys - other.Sys, + Idle: cpu.Idle - other.Idle, + Wait: cpu.Wait - other.Wait, + Irq: cpu.Irq - other.Irq, + SoftIrq: cpu.SoftIrq - other.SoftIrq, + Stolen: cpu.Stolen - other.Stolen, + } +} + +type LoadAverage struct { + One, Five, Fifteen float64 +} + +type Uptime struct { + Length float64 +} + +type Mem struct { + Total uint64 + Used uint64 + Free uint64 + ActualFree uint64 + ActualUsed uint64 +} + +type Swap struct { + Total uint64 + Used uint64 + Free uint64 +} + +type CpuList struct { + List []Cpu +} + +type FileSystem struct { + DirName string + DevName string + TypeName string + SysTypeName string + Options string + Flags uint32 +} + +type FileSystemList struct { + List []FileSystem +} + +type FileSystemUsage struct { + Total uint64 + Used uint64 + Free uint64 + Avail uint64 + Files uint64 + FreeFiles uint64 +} + +type ProcList struct { + List []int +} + +type RunState byte + +const ( + RunStateSleep = 'S' + RunStateRun = 'R' + RunStateStop = 'T' + RunStateZombie = 'Z' + RunStateIdle = 'D' + RunStateUnknown = '?' +) + +type ProcState struct { + Name string + State RunState + Ppid int + Tty int + Priority int + Nice int + Processor int +} + +type ProcMem struct { + Size uint64 + Resident uint64 + Share uint64 + MinorFaults uint64 + MajorFaults uint64 + PageFaults uint64 +} + +type ProcTime struct { + StartTime uint64 + User uint64 + Sys uint64 + Total uint64 +} + +type ProcArgs struct { + List []string +} + +type ProcExe struct { + Name string + Cwd string + Root string +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface_test.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface_test.go new file mode 100644 index 000000000..fe26abd1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_interface_test.go @@ -0,0 +1,135 @@ +package sigar_test + +import ( + "os" + "path/filepath" + "runtime" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + . "github.com/cloudfoundry/gosigar" +) + +var _ = Describe("Sigar", func() { + var invalidPid = 666666 + + It("cpu", func() { + cpu := Cpu{} + err := cpu.Get() + Expect(err).ToNot(HaveOccurred()) + }) + + It("load average", func() { + avg := LoadAverage{} + err := avg.Get() + Expect(err).ToNot(HaveOccurred()) + }) + + It("uptime", func() { + uptime := Uptime{} + err := uptime.Get() + Expect(err).ToNot(HaveOccurred()) + Expect(uptime.Length).To(BeNumerically(">", 0)) + }) + + It("mem", func() { + mem := Mem{} + err := mem.Get() + Expect(err).ToNot(HaveOccurred()) + + Expect(mem.Total).To(BeNumerically(">", 0)) + Expect(mem.Used + mem.Free).To(BeNumerically("<=", mem.Total)) + }) + + It("swap", func() { + swap := Swap{} + err := swap.Get() + Expect(err).ToNot(HaveOccurred()) + Expect(swap.Used + swap.Free).To(BeNumerically("<=", swap.Total)) + }) + + It("cpu list", func() { + cpulist := CpuList{} + err := cpulist.Get() + Expect(err).ToNot(HaveOccurred()) + + nsigar := len(cpulist.List) + numcpu := runtime.NumCPU() + Expect(nsigar).To(Equal(numcpu)) + }) + + It("file system list", func() { + fslist := FileSystemList{} + err := fslist.Get() + Expect(err).ToNot(HaveOccurred()) + + Expect(len(fslist.List)).To(BeNumerically(">", 0)) + }) + + It("file system usage", func() { + fsusage := FileSystemUsage{} + err := fsusage.Get("/") + Expect(err).ToNot(HaveOccurred()) + + err = fsusage.Get("T O T A L L Y B O G U S") + Expect(err).To(HaveOccurred()) + }) + + It("proc list", func() { + pids := ProcList{} + err := pids.Get() + Expect(err).ToNot(HaveOccurred()) + + Expect(len(pids.List)).To(BeNumerically(">", 2)) + + err = pids.Get() + Expect(err).ToNot(HaveOccurred()) + }) + + It("proc state", func() { + state := ProcState{} + err := state.Get(os.Getppid()) + Expect(err).ToNot(HaveOccurred()) + + Expect([]RunState{RunStateRun, RunStateSleep}).To(ContainElement(state.State)) + Expect([]string{"go", "ginkgo"}).To(ContainElement(state.Name)) + + err = state.Get(invalidPid) + Expect(err).To(HaveOccurred()) + }) + + It("proc mem", func() { + mem := ProcMem{} + err := mem.Get(os.Getppid()) + Expect(err).ToNot(HaveOccurred()) + + err = mem.Get(invalidPid) + Expect(err).To(HaveOccurred()) + }) + + It("proc time", func() { + time := ProcTime{} + err := time.Get(os.Getppid()) + Expect(err).ToNot(HaveOccurred()) + + err = time.Get(invalidPid) + Expect(err).To(HaveOccurred()) + }) + + It("proc args", func() { + args := ProcArgs{} + err := args.Get(os.Getppid()) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(args.List)).To(BeNumerically(">=", 2)) + }) + + It("proc exe", func() { + exe := ProcExe{} + err := exe.Get(os.Getppid()) + Expect(err).ToNot(HaveOccurred()) + + Expect([]string{"go", "ginkgo"}).To(ContainElement(filepath.Base(exe.Name))) + }) +}) diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux.go new file mode 100644 index 000000000..68ffb0f9a --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux.go @@ -0,0 +1,386 @@ +// Copyright (c) 2012 VMware, Inc. + +package sigar + +import ( + "bufio" + "bytes" + "io" + "io/ioutil" + "os" + "strconv" + "strings" + "syscall" +) + +var system struct { + ticks uint64 + btime uint64 +} + +var Procd string + +func init() { + system.ticks = 100 // C.sysconf(C._SC_CLK_TCK) + + Procd = "/proc" + + // grab system boot time + readFile(Procd+"/stat", func(line string) bool { + if strings.HasPrefix(line, "btime") { + system.btime, _ = strtoull(line[6:]) + return false // stop reading + } + return true + }) +} + +func (self *LoadAverage) Get() error { + line, err := ioutil.ReadFile(Procd + "/loadavg") + if err != nil { + return nil + } + + fields := strings.Fields(string(line)) + + self.One, _ = strconv.ParseFloat(fields[0], 64) + self.Five, _ = strconv.ParseFloat(fields[1], 64) + self.Fifteen, _ = strconv.ParseFloat(fields[2], 64) + + return nil +} + +func (self *Uptime) Get() error { + sysinfo := syscall.Sysinfo_t{} + + if err := syscall.Sysinfo(&sysinfo); err != nil { + return err + } + + self.Length = float64(sysinfo.Uptime) + + return nil +} + +func (self *Mem) Get() error { + var buffers, cached uint64 + table := map[string]*uint64{ + "MemTotal": &self.Total, + "MemFree": &self.Free, + "Buffers": &buffers, + "Cached": &cached, + } + + if err := parseMeminfo(table); err != nil { + return err + } + + self.Used = self.Total - self.Free + kern := buffers + cached + self.ActualFree = self.Free + kern + self.ActualUsed = self.Used - kern + + return nil +} + +func (self *Swap) Get() error { + table := map[string]*uint64{ + "SwapTotal": &self.Total, + "SwapFree": &self.Free, + } + + if err := parseMeminfo(table); err != nil { + return err + } + + self.Used = self.Total - self.Free + return nil +} + +func (self *Cpu) Get() error { + return readFile(Procd+"/stat", func(line string) bool { + if len(line) > 4 && line[0:4] == "cpu " { + parseCpuStat(self, line) + return false + } + return true + + }) +} + +func (self *CpuList) Get() error { + capacity := len(self.List) + if capacity == 0 { + capacity = 4 + } + list := make([]Cpu, 0, capacity) + + err := readFile(Procd+"/stat", func(line string) bool { + if len(line) > 3 && line[0:3] == "cpu" && line[3] != ' ' { + cpu := Cpu{} + parseCpuStat(&cpu, line) + list = append(list, cpu) + } + return true + }) + + self.List = list + + return err +} + +func (self *FileSystemList) Get() error { + capacity := len(self.List) + if capacity == 0 { + capacity = 10 + } + fslist := make([]FileSystem, 0, capacity) + + err := readFile("/etc/mtab", func(line string) bool { + fields := strings.Fields(line) + + fs := FileSystem{} + fs.DevName = fields[0] + fs.DirName = fields[1] + fs.SysTypeName = fields[2] + fs.Options = fields[3] + + fslist = append(fslist, fs) + + return true + }) + + self.List = fslist + + return err +} + +func (self *ProcList) Get() error { + dir, err := os.Open(Procd) + if err != nil { + return err + } + defer dir.Close() + + const readAllDirnames = -1 // see os.File.Readdirnames doc + + names, err := dir.Readdirnames(readAllDirnames) + if err != nil { + return err + } + + capacity := len(names) + list := make([]int, 0, capacity) + + for _, name := range names { + if name[0] < '0' || name[0] > '9' { + continue + } + pid, err := strconv.Atoi(name) + if err == nil { + list = append(list, pid) + } + } + + self.List = list + + return nil +} + +func (self *ProcState) Get(pid int) error { + contents, err := readProcFile(pid, "stat") + if err != nil { + return err + } + + fields := strings.Fields(string(contents)) + + self.Name = fields[1][1 : len(fields[1])-1] // strip ()'s + + self.State = RunState(fields[2][0]) + + self.Ppid, _ = strconv.Atoi(fields[3]) + + self.Tty, _ = strconv.Atoi(fields[6]) + + self.Priority, _ = strconv.Atoi(fields[17]) + + self.Nice, _ = strconv.Atoi(fields[18]) + + self.Processor, _ = strconv.Atoi(fields[38]) + + return nil +} + +func (self *ProcMem) Get(pid int) error { + contents, err := readProcFile(pid, "statm") + if err != nil { + return err + } + + fields := strings.Fields(string(contents)) + + size, _ := strtoull(fields[0]) + self.Size = size << 12 + + rss, _ := strtoull(fields[1]) + self.Resident = rss << 12 + + share, _ := strtoull(fields[2]) + self.Share = share << 12 + + contents, err = readProcFile(pid, "stat") + if err != nil { + return err + } + + fields = strings.Fields(string(contents)) + + self.MinorFaults, _ = strtoull(fields[10]) + self.MajorFaults, _ = strtoull(fields[12]) + self.PageFaults = self.MinorFaults + self.MajorFaults + + return nil +} + +func (self *ProcTime) Get(pid int) error { + contents, err := readProcFile(pid, "stat") + if err != nil { + return err + } + + fields := strings.Fields(string(contents)) + + user, _ := strtoull(fields[13]) + sys, _ := strtoull(fields[14]) + // convert to millis + self.User = user * (1000 / system.ticks) + self.Sys = sys * (1000 / system.ticks) + self.Total = self.User + self.Sys + + // convert to millis + self.StartTime, _ = strtoull(fields[21]) + self.StartTime /= system.ticks + self.StartTime += system.btime + self.StartTime *= 1000 + + return nil +} + +func (self *ProcArgs) Get(pid int) error { + contents, err := readProcFile(pid, "cmdline") + if err != nil { + return err + } + + bbuf := bytes.NewBuffer(contents) + + var args []string + + for { + arg, err := bbuf.ReadBytes(0) + if err == io.EOF { + break + } + args = append(args, string(chop(arg))) + } + + self.List = args + + return nil +} + +func (self *ProcExe) Get(pid int) error { + fields := map[string]*string{ + "exe": &self.Name, + "cwd": &self.Cwd, + "root": &self.Root, + } + + for name, field := range fields { + val, err := os.Readlink(procFileName(pid, name)) + + if err != nil { + return err + } + + *field = val + } + + return nil +} + +func parseMeminfo(table map[string]*uint64) error { + return readFile(Procd+"/meminfo", func(line string) bool { + fields := strings.Split(line, ":") + + if ptr := table[fields[0]]; ptr != nil { + num := strings.TrimLeft(fields[1], " ") + val, err := strtoull(strings.Fields(num)[0]) + if err == nil { + *ptr = val * 1024 + } + } + + return true + }) +} + +func parseCpuStat(self *Cpu, line string) error { + fields := strings.Fields(line) + + self.User, _ = strtoull(fields[1]) + self.Nice, _ = strtoull(fields[2]) + self.Sys, _ = strtoull(fields[3]) + self.Idle, _ = strtoull(fields[4]) + self.Wait, _ = strtoull(fields[5]) + self.Irq, _ = strtoull(fields[6]) + self.SoftIrq, _ = strtoull(fields[7]) + self.Stolen, _ = strtoull(fields[8]) + + return nil +} + +func readFile(file string, handler func(string) bool) error { + contents, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + reader := bufio.NewReader(bytes.NewBuffer(contents)) + + for { + line, _, err := reader.ReadLine() + if err == io.EOF { + break + } + if !handler(string(line)) { + break + } + } + + return nil +} + +func strtoull(val string) (uint64, error) { + return strconv.ParseUint(val, 10, 64) +} + +func procFileName(pid int, name string) string { + return Procd + "/" + strconv.Itoa(pid) + "/" + name +} + +func readProcFile(pid int, name string) ([]byte, error) { + path := procFileName(pid, name) + contents, err := ioutil.ReadFile(path) + + if err != nil { + if perr, ok := err.(*os.PathError); ok { + if perr.Err == syscall.ENOENT { + return nil, syscall.ESRCH + } + } + } + + return contents, err +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux_test.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux_test.go new file mode 100644 index 000000000..c5fcdbc9a --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_linux_test.go @@ -0,0 +1,225 @@ +package sigar_test + +import ( + "io/ioutil" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + sigar "github.com/cloudfoundry/gosigar" +) + +var _ = Describe("sigarLinux", func() { + var procd string + + BeforeEach(func() { + var err error + procd, err = ioutil.TempDir("", "sigarTests") + Expect(err).ToNot(HaveOccurred()) + sigar.Procd = procd + }) + + AfterEach(func() { + sigar.Procd = "/proc" + }) + + Describe("CPU", func() { + var ( + statFile string + cpu sigar.Cpu + ) + + BeforeEach(func() { + statFile = procd + "/stat" + cpu = sigar.Cpu{} + }) + + Describe("Get", func() { + It("gets CPU usage", func() { + statContents := []byte("cpu 25 1 2 3 4 5 6 7") + err := ioutil.WriteFile(statFile, statContents, 0644) + Expect(err).ToNot(HaveOccurred()) + + err = cpu.Get() + Expect(err).ToNot(HaveOccurred()) + Expect(cpu.User).To(Equal(uint64(25))) + }) + + It("ignores empty lines", func() { + statContents := []byte("cpu ") + err := ioutil.WriteFile(statFile, statContents, 0644) + Expect(err).ToNot(HaveOccurred()) + + err = cpu.Get() + Expect(err).ToNot(HaveOccurred()) + Expect(cpu.User).To(Equal(uint64(0))) + }) + }) + + Describe("CollectCpuStats", func() { + It("collects CPU usage over time", func() { + statContents := []byte("cpu 25 1 2 3 4 5 6 7") + err := ioutil.WriteFile(statFile, statContents, 0644) + Expect(err).ToNot(HaveOccurred()) + + concreteSigar := &sigar.ConcreteSigar{} + cpuUsages, stop := concreteSigar.CollectCpuStats(500 * time.Millisecond) + + Expect(<-cpuUsages).To(Equal(sigar.Cpu{ + User: uint64(25), + Nice: uint64(1), + Sys: uint64(2), + Idle: uint64(3), + Wait: uint64(4), + Irq: uint64(5), + SoftIrq: uint64(6), + Stolen: uint64(7), + })) + + statContents = []byte("cpu 30 3 7 10 25 55 36 65") + err = ioutil.WriteFile(statFile, statContents, 0644) + Expect(err).ToNot(HaveOccurred()) + + Expect(<-cpuUsages).To(Equal(sigar.Cpu{ + User: uint64(5), + Nice: uint64(2), + Sys: uint64(5), + Idle: uint64(7), + Wait: uint64(21), + Irq: uint64(50), + SoftIrq: uint64(30), + Stolen: uint64(58), + })) + + stop <- struct{}{} + }) + }) + }) + + Describe("Mem", func() { + var meminfoFile string + BeforeEach(func() { + meminfoFile = procd + "/meminfo" + + meminfoContents := ` +MemTotal: 374256 kB +MemFree: 274460 kB +Buffers: 9764 kB +Cached: 38648 kB +SwapCached: 0 kB +Active: 33772 kB +Inactive: 31184 kB +Active(anon): 16572 kB +Inactive(anon): 552 kB +Active(file): 17200 kB +Inactive(file): 30632 kB +Unevictable: 0 kB +Mlocked: 0 kB +SwapTotal: 786428 kB +SwapFree: 786428 kB +Dirty: 0 kB +Writeback: 0 kB +AnonPages: 16564 kB +Mapped: 6612 kB +Shmem: 584 kB +Slab: 19092 kB +SReclaimable: 9128 kB +SUnreclaim: 9964 kB +KernelStack: 672 kB +PageTables: 1864 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 973556 kB +Committed_AS: 55880 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 21428 kB +VmallocChunk: 34359713596 kB +HardwareCorrupted: 0 kB +AnonHugePages: 0 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +DirectMap4k: 59328 kB +DirectMap2M: 333824 kB +` + err := ioutil.WriteFile(meminfoFile, []byte(meminfoContents), 0444) + Expect(err).ToNot(HaveOccurred()) + }) + + It("returns correct memory info", func() { + mem := sigar.Mem{} + err := mem.Get() + Expect(err).ToNot(HaveOccurred()) + + Expect(mem.Total).To(BeNumerically("==", 374256*1024)) + Expect(mem.Free).To(BeNumerically("==", 274460*1024)) + }) + }) + + Describe("Swap", func() { + var meminfoFile string + BeforeEach(func() { + meminfoFile = procd + "/meminfo" + + meminfoContents := ` +MemTotal: 374256 kB +MemFree: 274460 kB +Buffers: 9764 kB +Cached: 38648 kB +SwapCached: 0 kB +Active: 33772 kB +Inactive: 31184 kB +Active(anon): 16572 kB +Inactive(anon): 552 kB +Active(file): 17200 kB +Inactive(file): 30632 kB +Unevictable: 0 kB +Mlocked: 0 kB +SwapTotal: 786428 kB +SwapFree: 786428 kB +Dirty: 0 kB +Writeback: 0 kB +AnonPages: 16564 kB +Mapped: 6612 kB +Shmem: 584 kB +Slab: 19092 kB +SReclaimable: 9128 kB +SUnreclaim: 9964 kB +KernelStack: 672 kB +PageTables: 1864 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 973556 kB +Committed_AS: 55880 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 21428 kB +VmallocChunk: 34359713596 kB +HardwareCorrupted: 0 kB +AnonHugePages: 0 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +DirectMap4k: 59328 kB +DirectMap2M: 333824 kB +` + err := ioutil.WriteFile(meminfoFile, []byte(meminfoContents), 0444) + Expect(err).ToNot(HaveOccurred()) + }) + + It("returns correct memory info", func() { + swap := sigar.Swap{} + err := swap.Get() + Expect(err).ToNot(HaveOccurred()) + + Expect(swap.Total).To(BeNumerically("==", 786428*1024)) + Expect(swap.Free).To(BeNumerically("==", 786428*1024)) + }) + }) +}) diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_suite_test.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_suite_test.go new file mode 100644 index 000000000..44287f631 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_suite_test.go @@ -0,0 +1,13 @@ +package sigar_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestGosigar(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Gosigar Suite") +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_unix.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_unix.go new file mode 100644 index 000000000..39f18784b --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_unix.go @@ -0,0 +1,26 @@ +// Copyright (c) 2012 VMware, Inc. + +// +build darwin freebsd linux netbsd openbsd + +package sigar + +import "syscall" + +func (self *FileSystemUsage) Get(path string) error { + stat := syscall.Statfs_t{} + err := syscall.Statfs(path, &stat) + if err != nil { + return err + } + + bsize := stat.Bsize / 512 + + self.Total = (uint64(stat.Blocks) * uint64(bsize)) >> 1 + self.Free = (uint64(stat.Bfree) * uint64(bsize)) >> 1 + self.Avail = (uint64(stat.Bavail) * uint64(bsize)) >> 1 + self.Used = self.Total - self.Free + self.Files = stat.Files + self.FreeFiles = stat.Ffree + + return nil +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_util.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_util.go new file mode 100644 index 000000000..a02df9419 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_util.go @@ -0,0 +1,22 @@ +// Copyright (c) 2012 VMware, Inc. + +package sigar + +import ( + "unsafe" +) + +func bytePtrToString(ptr *int8) string { + bytes := (*[10000]byte)(unsafe.Pointer(ptr)) + + n := 0 + for bytes[n] != 0 { + n++ + } + + return string(bytes[0:n]) +} + +func chop(buf []byte) []byte { + return buf[0 : len(buf)-1] +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows.go new file mode 100644 index 000000000..0c779d7c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows.go @@ -0,0 +1,100 @@ +// Copyright (c) 2012 VMware, Inc. + +package sigar + +// #include +// #include +import "C" + +import ( + "fmt" + "unsafe" +) + +func init() { +} + +func (self *LoadAverage) Get() error { + return nil +} + +func (self *Uptime) Get() error { + return nil +} + +func (self *Mem) Get() error { + var statex C.MEMORYSTATUSEX + statex.dwLength = C.DWORD(unsafe.Sizeof(statex)) + + succeeded := C.GlobalMemoryStatusEx(&statex) + if succeeded == C.FALSE { + lastError := C.GetLastError() + return fmt.Errorf("GlobalMemoryStatusEx failed with error: %d", int(lastError)) + } + + self.Total = uint64(statex.ullTotalPhys) + return nil +} + +func (self *Swap) Get() error { + return notImplemented() +} + +func (self *Cpu) Get() error { + return notImplemented() +} + +func (self *CpuList) Get() error { + return notImplemented() +} + +func (self *FileSystemList) Get() error { + return notImplemented() +} + +func (self *ProcList) Get() error { + return notImplemented() +} + +func (self *ProcState) Get(pid int) error { + return notImplemented() +} + +func (self *ProcMem) Get(pid int) error { + return notImplemented() +} + +func (self *ProcTime) Get(pid int) error { + return notImplemented() +} + +func (self *ProcArgs) Get(pid int) error { + return notImplemented() +} + +func (self *ProcExe) Get(pid int) error { + return notImplemented() +} + +func (self *FileSystemUsage) Get(path string) error { + var availableBytes C.ULARGE_INTEGER + var totalBytes C.ULARGE_INTEGER + var totalFreeBytes C.ULARGE_INTEGER + + pathChars := C.CString(path) + defer C.free(unsafe.Pointer(pathChars)) + + succeeded := C.GetDiskFreeSpaceEx((*C.CHAR)(pathChars), &availableBytes, &totalBytes, &totalFreeBytes) + if succeeded == C.FALSE { + lastError := C.GetLastError() + return fmt.Errorf("GetDiskFreeSpaceEx failed with error: %d", int(lastError)) + } + + self.Total = *(*uint64)(unsafe.Pointer(&totalBytes)) + return nil +} + +func notImplemented() error { + panic("Not Implemented") + return nil +} diff --git a/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows_test.go b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows_test.go new file mode 100644 index 000000000..868bdaab8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cloudfoundry/gosigar/sigar_windows_test.go @@ -0,0 +1,32 @@ +package sigar_test + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + sigar "github.com/cloudfoundry/gosigar" +) + +var _ = Describe("SigarWindows", func() { + Describe("Memory", func() { + It("gets the total memory", func() { + mem := sigar.Mem{} + err := mem.Get() + + Ω(err).ShouldNot(HaveOccurred()) + Ω(mem.Total).Should(BeNumerically(">", 0)) + }) + }) + + Describe("Disk", func() { + It("gets the total disk space", func() { + usage := sigar.FileSystemUsage{} + err := usage.Get(os.TempDir()) + + Ω(err).ShouldNot(HaveOccurred()) + Ω(usage.Total).Should(BeNumerically(">", 0)) + }) + }) +}) diff --git a/Makefile b/Makefile index 5c27c5d95..021420b5a 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ test: prepare docker-compose $(GOPATH)/bin/godep go test -v ./... test-short: prepare - $(GOPATH)/bin/godep go test -v -short ./... + $(GOPATH)/bin/godep go test -short ./... test-cleanup: docker-compose kill diff --git a/plugins/system/ps.go b/plugins/system/ps.go index 0bf67011b..1f06bafde 100644 --- a/plugins/system/ps.go +++ b/plugins/system/ps.go @@ -10,7 +10,6 @@ import ( "github.com/influxdb/telegraf/plugins/system/ps/cpu" "github.com/influxdb/telegraf/plugins/system/ps/disk" "github.com/influxdb/telegraf/plugins/system/ps/docker" - "github.com/influxdb/telegraf/plugins/system/ps/load" "github.com/influxdb/telegraf/plugins/system/ps/mem" "github.com/influxdb/telegraf/plugins/system/ps/net" ) @@ -24,7 +23,6 @@ type DockerContainerStat struct { } type PS interface { - LoadAvg() (*load.LoadAvgStat, error) CPUTimes(perCPU, totalCPU bool) ([]cpu.CPUTimesStat, error) DiskUsage() ([]*disk.DiskUsageStat, error) NetIO() ([]net.NetIOCountersStat, error) @@ -45,10 +43,6 @@ type systemPS struct { dockerClient *dc.Client } -func (s *systemPS) LoadAvg() (*load.LoadAvgStat, error) { - return load.LoadAvg() -} - func (s *systemPS) CPUTimes(perCPU, totalCPU bool) ([]cpu.CPUTimesStat, error) { var cpuTimes []cpu.CPUTimesStat if perCPU { diff --git a/plugins/system/system.go b/plugins/system/system.go index f3c3bc7e8..5d5c64e97 100644 --- a/plugins/system/system.go +++ b/plugins/system/system.go @@ -1,39 +1,48 @@ package system -import "github.com/influxdb/telegraf/plugins" +import ( + "github.com/cloudfoundry/gosigar" -type SystemStats struct { - ps PS -} + "github.com/influxdb/telegraf/plugins" +) + +type SystemStats struct{} func (_ *SystemStats) Description() string { - return "Read metrics about system load" + return "Read metrics about system load & uptime" } func (_ *SystemStats) SampleConfig() string { return "" } -func (s *SystemStats) add(acc plugins.Accumulator, +func (_ *SystemStats) add(acc plugins.Accumulator, name string, val float64, tags map[string]string) { if val >= 0 { acc.Add(name, val, tags) } } -func (s *SystemStats) Gather(acc plugins.Accumulator) error { - lv, err := s.ps.LoadAvg() - if err != nil { +func (_ *SystemStats) Gather(acc plugins.Accumulator) error { + loadavg := sigar.LoadAverage{} + if err := loadavg.Get(); err != nil { return err } - acc.Add("load1", lv.Load1, nil) - acc.Add("load5", lv.Load5, nil) - acc.Add("load15", lv.Load15, nil) + uptime := sigar.Uptime{} + if err := uptime.Get(); err != nil { + return err + } + + acc.Add("load1", loadavg.One, nil) + acc.Add("load5", loadavg.Five, nil) + acc.Add("load15", loadavg.Fifteen, nil) + acc.Add("uptime", uptime.Length, nil) + acc.Add("uptime_format", uptime.Format(), nil) return nil } func init() { plugins.Add("system", func() plugins.Plugin { - return &SystemStats{ps: &systemPS{}} + return &SystemStats{} }) } diff --git a/plugins/system/system_test.go b/plugins/system/system_test.go index 78c13834b..199141c4a 100644 --- a/plugins/system/system_test.go +++ b/plugins/system/system_test.go @@ -7,7 +7,6 @@ import ( "github.com/influxdb/telegraf/plugins/system/ps/cpu" "github.com/influxdb/telegraf/plugins/system/ps/disk" - "github.com/influxdb/telegraf/plugins/system/ps/load" "github.com/influxdb/telegraf/plugins/system/ps/mem" "github.com/influxdb/telegraf/plugins/system/ps/net" "github.com/influxdb/telegraf/testutil" @@ -22,14 +21,6 @@ func TestSystemStats_GenerateStats(t *testing.T) { var acc testutil.Accumulator - lv := &load.LoadAvgStat{ - Load1: 0.3, - Load5: 1.5, - Load15: 0.8, - } - - mps.On("LoadAvg").Return(lv, nil) - cts := cpu.CPUTimesStat{ CPU: "cpu0", User: 3.1, @@ -128,15 +119,6 @@ func TestSystemStats_GenerateStats(t *testing.T) { mps.On("SwapStat").Return(sms, nil) - ss := &SystemStats{ps: &mps} - - err := ss.Gather(&acc) - require.NoError(t, err) - - assert.True(t, acc.CheckValue("load1", 0.3)) - assert.True(t, acc.CheckValue("load5", 1.5)) - assert.True(t, acc.CheckValue("load15", 0.8)) - cs := NewCPUStats(&mps) cputags := map[string]string{ @@ -144,7 +126,7 @@ func TestSystemStats_GenerateStats(t *testing.T) { } preCPUPoints := len(acc.Points) - err = cs.Gather(&acc) + err := cs.Gather(&acc) require.NoError(t, err) numCPUPoints := len(acc.Points) - preCPUPoints