├── .travis.yml ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── amqp_output.go ├── bin └── metrix │ ├── Makefile │ └── metrix.go ├── connections.go ├── constants.go ├── cpu.go ├── cpu_test.go ├── df.go ├── df_test.go ├── disk.go ├── disk_test.go ├── elasticsearch.go ├── elasticsearch_test.go ├── files.go ├── files_test.go ├── fixtures ├── df.txt ├── df_inode.txt ├── diskstats_precise.txt ├── elasticsearch_status.json ├── free.txt ├── lsof.txt ├── netstat.txt ├── netstat_connections.txt ├── nginx.status ├── proc │ ├── 1 │ │ └── stat │ ├── 2 │ │ └── stat │ ├── 3 │ │ └── stat │ ├── 5 │ │ └── stat │ ├── 7 │ │ └── stat │ ├── 8 │ │ └── stat │ ├── 9 │ │ └── stat │ ├── 10 │ │ └── stat │ ├── 11 │ │ └── stat │ ├── 12 │ │ └── stat │ ├── 13 │ │ └── stat │ ├── 14 │ │ └── stat │ ├── 15 │ │ └── stat │ ├── 16 │ │ └── stat │ ├── 17 │ │ └── stat │ ├── 18 │ │ └── stat │ ├── 19 │ │ └── stat │ ├── 20 │ │ └── stat │ ├── 21 │ │ └── stat │ ├── 22 │ │ └── stat │ ├── 24 │ │ └── stat │ ├── 25 │ │ └── stat │ ├── 26 │ │ └── stat │ ├── 27 │ │ └── stat │ ├── 28 │ │ └── stat │ ├── 29 │ │ └── stat │ ├── 40 │ │ └── stat │ ├── 42 │ │ └── stat │ ├── 43 │ │ └── stat │ ├── 45 │ │ └── stat │ ├── 46 │ │ └── stat │ ├── 65 │ │ └── stat │ ├── 66 │ │ └── stat │ ├── 67 │ │ └── stat │ ├── 217 │ │ └── stat │ ├── 218 │ │ └── stat │ ├── 309 │ │ └── stat │ ├── 377 │ │ └── stat │ ├── 382 │ │ └── stat │ ├── 428 │ │ └── stat │ ├── 432 │ │ └── stat │ ├── 514 │ │ └── stat │ ├── 515 │ │ └── stat │ ├── 529 │ │ └── stat │ ├── 608 │ │ └── stat │ ├── 627 │ │ └── stat │ ├── 766 │ │ └── stat │ ├── 843 │ │ └── stat │ ├── 847 │ │ └── stat │ ├── 858 │ │ └── stat │ ├── 859 │ │ └── stat │ ├── 864 │ │ └── stat │ ├── 879 │ │ └── stat │ ├── 880 │ │ └── stat │ ├── 882 │ │ └── stat │ ├── 932 │ │ └── stat │ ├── 938 │ │ └── stat │ ├── 954 │ │ └── stat │ ├── 956 │ │ └── stat │ ├── 962 │ │ └── stat │ ├── 1310 │ │ └── stat │ ├── 1311 │ │ └── stat │ ├── 1712 │ │ └── stat │ ├── 1731 │ │ └── stat │ ├── 9934 │ │ └── stat │ ├── 10082 │ │ └── stat │ ├── 10162 │ │ └── stat │ ├── 10177 │ │ └── stat │ ├── 10243 │ │ └── stat │ ├── 10245 │ │ └── stat │ ├── diskstats │ ├── loadavg │ ├── meminfo │ ├── net │ │ ├── netstat │ │ └── snmp │ └── stat ├── redis_info.txt └── riak.json ├── free.go ├── free_test.go ├── graphite_test.go ├── influxdb_output.go ├── loadavg.go ├── loadavg_test.go ├── logger.go ├── main.go ├── memory.go ├── memory_test.go ├── metric.go ├── metric_handler.go ├── metrics.go ├── metrics_collection.go ├── metrix.go ├── metrix ├── Makefile ├── dbg.go ├── df.go ├── df_test.go ├── ec2metadata.go ├── ec2metadata_test.go ├── fixtures │ ├── df.txt │ ├── diskstats.txt │ ├── ec2metadata.txt │ ├── loadavg.txt │ ├── lsof.txt │ ├── meminfo.txt │ ├── proc_cmdline.txt │ ├── proc_stat.txt │ ├── stat.txt │ └── statm.txt ├── load.go ├── load_test.go ├── logger.go ├── lsof.go ├── lsof_test.go ├── map.go ├── meminfo.go ├── meminfo_test.go ├── net.go ├── net_test.go ├── proc_cmdline.go ├── proc_cmdline_test.go ├── proc_stat.go ├── proc_stat_test.go ├── snapshot.go ├── stat.go ├── stat_test.go └── util.go ├── metrix_test.go ├── net.go ├── net_darwin.go ├── net_darwin_test.go ├── net_linux.go ├── net_linux_test.go ├── net_test.go ├── nginx.go ├── nginx_test.go ├── opentsdb_test.go ├── optparse.go ├── optparse_test.go ├── output.go ├── pgbouncer.go ├── postgres.go ├── postgres_test.go ├── processes.go ├── processes_test.go ├── redis.go ├── redis_test.go ├── riak.go ├── riak_test.go ├── scripts └── release.sh ├── setup_test.go ├── suricata.go ├── table.go └── util.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | script: 4 | - go get -d -v -u -t ./... 5 | - go test -v ./... 6 | 7 | go: 8 | - 1.3 9 | 10 | notifications: 11 | email: 12 | - travis@dynport.de 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GIT_COMMIT = $(shell git rev-parse --short HEAD) 2 | GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES") 3 | BUILD_CMD = go build -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)" 4 | 5 | default: build vet test 6 | 7 | vet: 8 | go vet ./... 9 | 10 | test: 11 | go test ./... 12 | 13 | build: 14 | go get ./... 15 | 16 | all: clean test all 17 | ./bin/metrix --cpu --memory --net --df --disk --processes --files 18 | 19 | wip: ctags test 20 | 21 | ctags: 22 | go get github.com/jstemmer/gotags 23 | gotags `find . -name "*.go" | grep -v '/test/' | xargs` 2> /dev/null > tags.tmp && mv tags.tmp tags 24 | 25 | install_dependencies: 26 | go get -d -v ./... && go build -v ./... 27 | 28 | clean: 29 | rm -f bin/* 30 | 31 | release: 32 | GOOS=linux GOARCH=amd64 bash ./scripts/release.sh 33 | GOOS=darwin GOARCH=amd64 bash ./scripts/release.sh 34 | 35 | jenkins: clean install_dependencies test all 36 | PROC_ROOT=./fixtures ./bin/metrix --loadavg --disk --memory --processes --cpu 37 | ./bin/metrix --loadavg --disk --memory --processes --cpu --keys --files 38 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | redis: mkdir /tmp/metrix_redis && echo "dir /tmp/metrix_redis" | redis-server - 2 | es: elasticsearch -f -D es.config=/usr/local/opt/elasticsearch/config/elasticsearch.yml 3 | postgres: postgres -D /data/postgresql-9.3beta1/ 4 | -------------------------------------------------------------------------------- /amqp_output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/streadway/amqp" 9 | ) 10 | 11 | const AMQP_EXCHANGE = "metrix" 12 | 13 | func PublishMetricsWithAMQP(address string, metrics []*Metric, hostname string) (e error) { 14 | con, e := amqp.Dial("amqp://" + address) 15 | if e != nil { 16 | return e 17 | } 18 | defer con.Close() 19 | channel, e := con.Channel() 20 | if e != nil { 21 | return e 22 | } 23 | defer channel.Close() 24 | e = channel.ExchangeDeclare(AMQP_EXCHANGE, "fanout", false, false, false, false, nil) 25 | if e != nil { 26 | return e 27 | } 28 | started := time.Now() 29 | for _, m := range metrics { 30 | amqpKey := hostname + "." + m.Key 31 | b, e := json.Marshal(m) 32 | if e != nil { 33 | logger.Printf(e.Error()) 34 | continue 35 | } 36 | e = channel.Publish(AMQP_EXCHANGE, amqpKey, false, false, amqp.Publishing{ 37 | Body: b, 38 | ContentType: "application/json", 39 | Timestamp: time.Now(), 40 | }) 41 | if e != nil { 42 | logger.Printf("ERROR: error publishing " + e.Error()) 43 | } 44 | } 45 | fmt.Printf("sent %d metrics in %.06f\n", len(metrics), time.Now().Sub(started).Seconds()) 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /bin/metrix/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go get . 3 | -------------------------------------------------------------------------------- /bin/metrix/metrix.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/dynport/metrix/metrix" 11 | ) 12 | 13 | var logger = log.New(os.Stderr, "", 0) 14 | 15 | func main() { 16 | if e := run(); e != nil { 17 | logger.Fatal(e) 18 | } 19 | } 20 | 21 | var mapping = map[string]func() error{ 22 | "snapshot": snapshot, 23 | "disks": disks, 24 | "memory": memory, 25 | "stat": stat, 26 | "load": load, 27 | "proc-stats": procStats, 28 | } 29 | 30 | func benchmark(message string) func() { 31 | started := time.Now() 32 | logger.Printf("started %s", message) 33 | return func() { 34 | logger.Printf("finished %s in %.06f", message, time.Since(started).Seconds()) 35 | } 36 | } 37 | 38 | func snapshot() error { 39 | defer benchmark("create snapshot")() 40 | s := &metrix.Snapshot{} 41 | err := s.Load() 42 | if err != nil { 43 | return err 44 | } 45 | return s.Store("/data/metrix") 46 | } 47 | 48 | func procStats() error { 49 | stats, e := metrix.LoadProcStats() 50 | if e != nil { 51 | return e 52 | } 53 | for _, s := range stats { 54 | logger.Printf("%d: %s utime=%d stime=%d, rss=%d", s.Pid, s.Comm, s.Utime, s.Stime, s.RSS) 55 | } 56 | return nil 57 | } 58 | 59 | func load() error { 60 | l, e := metrix.LoadLoadAvg() 61 | if e != nil { 62 | return e 63 | } 64 | logger.Printf("1min: %.02f", l.Min1) 65 | logger.Printf("5min: %.02f", l.Min5) 66 | logger.Printf("15min: %.02f", l.Min15) 67 | return nil 68 | 69 | } 70 | 71 | func availableNames() []string { 72 | names := []string{} 73 | for n := range mapping { 74 | names = append(names, n) 75 | } 76 | return names 77 | } 78 | 79 | func run() error { 80 | if len(os.Args) < 2 { 81 | return fmt.Errorf("argument expected. available are %#v", availableNames()) 82 | } 83 | selected := []func() error{} 84 | for n, f := range mapping { 85 | if strings.HasPrefix(n, os.Args[1]) { 86 | selected = append(selected, f) 87 | } 88 | } 89 | if len(selected) == 1 { 90 | return selected[0]() 91 | } 92 | return fmt.Errorf("no handler found, knonw are %s", strings.Join(availableNames(), ", ")) 93 | } 94 | 95 | func memory() error { 96 | mem, e := metrix.LoadMeminfo() 97 | if e != nil { 98 | return e 99 | } 100 | logger.Printf("total: %d", mem.MemTotal) 101 | logger.Printf("free: %d", mem.MemFree) 102 | return nil 103 | } 104 | 105 | func disks() error { 106 | disks, e := metrix.LoadDisks() 107 | if e != nil { 108 | return e 109 | } 110 | for _, d := range disks { 111 | logger.Printf("%s: %d", d.Filesystem, d.Use) 112 | } 113 | return nil 114 | 115 | } 116 | 117 | func stat() error { 118 | stat, e := metrix.LoadStat() 119 | if e != nil { 120 | return e 121 | } 122 | logger.Printf("user=%d iowait=%d", stat.Cpu.User, stat.Cpu.IOWait) 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /connections.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "strconv" 4 | import "net" 5 | import "fmt" 6 | import "strings" 7 | import "os/exec" 8 | import "regexp" 9 | 10 | func parseHexIp(s string) (ip net.IP) { 11 | a, _ := strconv.ParseInt(s, 16, 64) 12 | return net.ParseIP(fmt.Sprintf("%d.%d.%d.%d", (a>>0)&255, (a>>8)&255, (a>>16)&255, (a>>24)&255)) 13 | } 14 | 15 | type Connection struct { 16 | Protocol string 17 | ReceiveQueue int 18 | SendQueue int 19 | LocalAddress Address 20 | RemoteAddress Address 21 | State string 22 | Pid int 23 | Name string 24 | } 25 | 26 | type Address struct { 27 | Ip string 28 | Port int 29 | } 30 | 31 | var socketStates = map[int]string{ 32 | 1: "ESTABLISHED", 33 | 2: "SYN_SENT", 34 | 3: "SYN_RECV", 35 | 4: "FIN_WAIT1", 36 | 5: "FIN_WAIT2", 37 | 6: "TIME_WAIT", 38 | 7: "CLOSE", 39 | 8: "CLOSE_WAIT", 40 | 9: "LAST_ACK", 41 | 10: "LISTEN", 42 | 11: "CLOSING", 43 | } 44 | 45 | func ReadConnections() (c []*Connection, err error) { 46 | out, err := exec.Command("netstat", "-tupna").Output() 47 | if len(out) > 0 { 48 | return ParseConnections(string(out)), nil 49 | } 50 | return 51 | } 52 | 53 | func ParseStringAddress(s string) (a Address) { 54 | chunks := strings.Split(s, ":") 55 | a = Address{} 56 | if port, err := strconv.Atoi(chunks[len(chunks)-1]); err == nil { 57 | a.Port = port 58 | } 59 | 60 | if len(chunks) == 2 { 61 | a.Ip = chunks[0] 62 | } else { 63 | a.Ip = "" 64 | } 65 | return 66 | } 67 | 68 | func ParseConnections(raw string) (connections []*Connection) { 69 | lines := strings.Split(strings.TrimSpace(raw), "\n") 70 | connections = make([]*Connection, len(lines)-2) 71 | 72 | splitRe := regexp.MustCompile("\\s+") 73 | 74 | for i, line := range lines[2:] { 75 | con := &Connection{} 76 | chunks := splitRe.Split(line, -1) 77 | if len(chunks) > 5 { 78 | con.Protocol = chunks[0] 79 | con.ReceiveQueue, _ = strconv.Atoi(chunks[1]) 80 | con.SendQueue, _ = strconv.Atoi(chunks[2]) 81 | con.LocalAddress = ParseStringAddress(chunks[3]) 82 | con.RemoteAddress = ParseStringAddress(chunks[4]) 83 | 84 | offset := 5 85 | if strings.HasPrefix(con.Protocol, "tcp") { 86 | con.State = chunks[5] 87 | offset++ 88 | } 89 | 90 | if len(chunks) > offset { 91 | pidAndName := strings.Split(chunks[offset], "/") 92 | if pid, err := strconv.Atoi(pidAndName[0]); err == nil { 93 | con.Pid = pid 94 | con.Name = pidAndName[1] 95 | } 96 | } 97 | } 98 | connections[i] = con 99 | } 100 | return 101 | } 102 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const timeFormat = "2006-01-02 15:04:05Z" 4 | const VERSION = "0.1.6" 5 | 6 | var ( 7 | GITCOMMIT string 8 | parser = NewOptParser() 9 | ) 10 | -------------------------------------------------------------------------------- /cpu.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | var statMapping = map[string]string{ 13 | "ctxt": "Ctxt", 14 | "btime": "Btime", 15 | "processes": "Processes", 16 | "procs_running": "ProcsRunning", 17 | "procs_blocked": "ProcsBlocked", 18 | } 19 | 20 | const ( 21 | cpuFieldUser = iota + 1 22 | cpuFieldNice 23 | cpuFieldSystem 24 | cpuFieldIdle 25 | cpuFieldIOWait 26 | cpuFieldIrq 27 | cpuFieldSoftIrq 28 | cpuFieldSteal 29 | cpuFieldGuest 30 | cpuFieldGuestNice 31 | ) 32 | 33 | var cpuLineMapping = map[int]string{ 34 | 1: "User", 35 | 2: "Nice", 36 | 3: "System", 37 | 4: "Idle", 38 | 5: "IOWait", 39 | 6: "IRC", 40 | 7: "SoftIRQ", 41 | } 42 | 43 | const CPU = "cpu" 44 | 45 | func init() { 46 | parser.Add(CPU, "true", "Collect cpu metrics") 47 | } 48 | 49 | type Cpu struct { 50 | Cpus []*CpuStat 51 | Ctxt int64 52 | Btime int64 53 | Processes int64 54 | ProcsRunning int64 55 | ProcsBlocked int64 56 | } 57 | 58 | func (c *Cpu) Load(b []byte) error { 59 | scanner := bufio.NewScanner(bytes.NewReader(b)) 60 | c.Cpus = []*CpuStat{} 61 | for scanner.Scan() { 62 | line := scanner.Text() 63 | parts := strings.Fields(line) 64 | if len(parts) < 2 { 65 | // continue 66 | } 67 | key := parts[0] 68 | if strings.HasPrefix(key, "cpu") { 69 | s := &CpuStat{Id: strings.TrimPrefix(key, "cpu")} 70 | for i, v := range parts[1:] { 71 | value, e := strconv.ParseInt(v, 10, 64) 72 | if e != nil { 73 | return e 74 | } 75 | switch i + 1 { 76 | case cpuFieldUser: 77 | s.User = value 78 | case cpuFieldNice: 79 | s.Nice = value 80 | case cpuFieldSystem: 81 | s.System = value 82 | case cpuFieldIdle: 83 | s.Idle = value 84 | case cpuFieldIOWait: 85 | s.IOWait = value 86 | case cpuFieldIrq: 87 | s.IRQ = value 88 | case cpuFieldSoftIrq: 89 | s.SoftIRQ = value 90 | case cpuFieldSteal: 91 | s.Steal = value 92 | case cpuFieldGuest: 93 | s.Guest = value 94 | case cpuFieldGuestNice: 95 | s.GuestNice = value 96 | } 97 | } 98 | c.Cpus = append(c.Cpus, s) 99 | } else { 100 | var e error 101 | switch key { 102 | case "ctxt": 103 | c.Ctxt, e = parseIntE(parts[1]) 104 | if e != nil { 105 | return e 106 | } 107 | case "btime": 108 | c.Btime, e = parseIntE(parts[1]) 109 | if e != nil { 110 | return e 111 | } 112 | case "processes": 113 | c.Processes, e = parseIntE(parts[1]) 114 | if e != nil { 115 | return e 116 | } 117 | case "procs_running": 118 | c.ProcsRunning, e = parseIntE(parts[1]) 119 | if e != nil { 120 | return e 121 | } 122 | case "procs_blocked": 123 | c.ProcsBlocked, e = parseIntE(parts[1]) 124 | if e != nil { 125 | return e 126 | } 127 | } 128 | } 129 | } 130 | return scanner.Err() 131 | } 132 | 133 | type CpuStat struct { 134 | Id string 135 | User int64 136 | Nice int64 137 | System int64 138 | Idle int64 139 | IOWait int64 140 | IRQ int64 141 | SoftIRQ int64 142 | Steal int64 143 | Guest int64 144 | GuestNice int64 145 | } 146 | 147 | func (*Cpu) Prefix() string { 148 | return "cpu" 149 | } 150 | 151 | func ProcRoot() string { 152 | return os.Getenv("PROC_ROOT") 153 | } 154 | 155 | func ReadProcFile(path string) (r string) { 156 | if b, e := ioutil.ReadFile(ProcRoot() + "/proc/" + path); e == nil { 157 | r = string(b) 158 | } 159 | return 160 | } 161 | 162 | func (cpu *Cpu) Collect(c *MetricsCollection) (e error) { 163 | str := ReadProcFile("stat") 164 | e = cpu.Load([]byte(str)) 165 | if e != nil { 166 | return e 167 | } 168 | 169 | values := map[string]int64{ 170 | "ctxt": cpu.Ctxt, 171 | "btime": cpu.Btime, 172 | "processes": cpu.Processes, 173 | "procs_running": cpu.ProcsRunning, 174 | "procs_blocked": cpu.ProcsBlocked, 175 | } 176 | for k, v := range values { 177 | c.Add(k, v) 178 | } 179 | 180 | for _, cpu := range cpu.Cpus { 181 | tags := map[string]string{} 182 | if cpu.Id != "" { 183 | tags["cpu_id"] = cpu.Id 184 | } else { 185 | tags["total"] = "true" 186 | } 187 | values := map[string]int64{ 188 | "user": cpu.User, 189 | "nice": cpu.Nice, 190 | "system": cpu.System, 191 | "idle": cpu.Idle, 192 | "io_wait": cpu.IOWait, 193 | "irq": cpu.IRQ, 194 | "soft_irq": cpu.SoftIRQ, 195 | "steal": cpu.Steal, 196 | "guest": cpu.Guest, 197 | "guest_nice": cpu.GuestNice, 198 | } 199 | for k, v := range values { 200 | c.AddWithTags(k, v, tags) 201 | } 202 | } 203 | return nil 204 | } 205 | -------------------------------------------------------------------------------- /cpu_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | ) 9 | 10 | func TestCpu(t *testing.T) { 11 | Convey("MetricHandler", t, func() { 12 | mh := &MetricHandler{} 13 | stats, e := mh.Collect(&Cpu{}) 14 | So(e, ShouldBeNil) 15 | So(len(stats), ShouldEqual, 25) 16 | sort.Sort(stats) 17 | 18 | So(stats[0].Key, ShouldEqual, "cpu.btime") 19 | 20 | user := stats[23] 21 | 22 | So(user.Key, ShouldEqual, "cpu.user") 23 | So(user.Tags["total"], ShouldEqual, "") 24 | So(user.Tags["cpu_id"], ShouldEqual, "0") 25 | 26 | userCpu := stats[24] 27 | So(userCpu.Key, ShouldEqual, "cpu.user") 28 | So(userCpu.Tags["total"], ShouldEqual, "true") 29 | So(userCpu.Tags["cpu_id"], ShouldEqual, "") 30 | 31 | Convey("Load", func() { 32 | c := &Cpu{} 33 | b := mustRead(t, "proc/stat") 34 | e := c.Load(b) 35 | So(e, ShouldBeNil) 36 | So(len(c.Cpus), ShouldEqual, 2) 37 | So(c.Cpus[0].Id, ShouldEqual, "") 38 | So(c.Cpus[0].User, ShouldEqual, 3700) 39 | So(c.Cpus[1].Id, ShouldEqual, "0") 40 | So(c.Cpus[1].User, ShouldEqual, 3701) 41 | 42 | So(c.Ctxt, ShouldEqual, 957584) 43 | So(c.Btime, ShouldEqual, 1372481686) 44 | So(c.Processes, ShouldEqual, 42476) 45 | So(c.ProcsRunning, ShouldEqual, 1) 46 | So(c.ProcsBlocked, ShouldEqual, 3) 47 | 48 | }) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /df.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "os/exec" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type Df struct { 11 | RawSpace []byte 12 | RawInode []byte 13 | } 14 | 15 | func (self *Df) Prefix() string { 16 | return "df" 17 | } 18 | 19 | const ( 20 | SPACE = "space" 21 | INODE = "inode" 22 | DF = "df" 23 | ) 24 | 25 | func init() { 26 | parser.Add(DF, "true", "Collect disk free space metrics") 27 | } 28 | 29 | var dfFlgaMapping = map[string]string{ 30 | SPACE: "-k", 31 | INODE: "-i", 32 | } 33 | 34 | func CollectDf(t string, raw []byte, c *MetricsCollection) (e error) { 35 | if len(raw) == 0 { 36 | if flag, ok := dfFlgaMapping[t]; ok { 37 | raw, e = ReadDf(flag) 38 | } else { 39 | return errors.New("no mapping for df key " + t + " defined") 40 | } 41 | } 42 | lines := strings.Split(strings.TrimSpace(string(raw)), "\n") 43 | for _, line := range lines[1:] { 44 | if !strings.HasPrefix(line, "/") { 45 | continue 46 | } 47 | chunks := strings.Fields(line) 48 | if len(chunks) >= 6 { 49 | tags := map[string]string{ 50 | "file_system": chunks[0], 51 | "mounted_on": chunks[5], 52 | } 53 | if v, e := strconv.ParseInt(chunks[1], 10, 64); e == nil { 54 | c.AddWithTags(t+".Total", v, tags) 55 | } 56 | if v, e := strconv.ParseInt(chunks[2], 10, 64); e == nil { 57 | c.AddWithTags(t+".Used", v, tags) 58 | } 59 | if v, e := strconv.ParseInt(chunks[3], 10, 64); e == nil { 60 | c.AddWithTags(t+".Available", v, tags) 61 | } 62 | if v, e := strconv.ParseInt(strings.Replace(chunks[4], "%", "", 1), 10, 64); e == nil { 63 | c.AddWithTags(t+".Use", v, tags) 64 | } 65 | } 66 | } 67 | return 68 | } 69 | 70 | func (self *Df) Collect(c *MetricsCollection) (e error) { 71 | e = CollectDf(SPACE, self.RawSpace, c) 72 | if e != nil { 73 | return 74 | } 75 | e = CollectDf(INODE, self.RawInode, c) 76 | return 77 | } 78 | 79 | func ReadDf(flag string) (b []byte, e error) { 80 | dbg.Print("reading df with", flag) 81 | b, e = exec.Command("df", flag).Output() 82 | if e != nil { 83 | return 84 | } 85 | if len(b) == 0 { 86 | e = errors.New("Reading df returned an empty string") 87 | } 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /df_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestDf(t *testing.T) { 10 | Convey("Df", t, func() { 11 | mh := new(MetricHandler) 12 | col := &Df{ 13 | RawSpace: readFile("fixtures/df.txt"), 14 | RawInode: readFile("fixtures/df_inode.txt"), 15 | } 16 | stats, e := mh.Collect(col) 17 | So(e, ShouldBeNil) 18 | 19 | So(len(stats), ShouldEqual, 8) 20 | 21 | So(stats[0].Key, ShouldEqual, "df.space.Total") 22 | So(stats[0].Value, ShouldEqual, int64(20511356)) 23 | So(stats[0].Tags["file_system"], ShouldEqual, "/dev/sda") 24 | So(stats[0].Tags["mounted_on"], ShouldEqual, "/") 25 | 26 | So(stats[4].Key, ShouldEqual, "df.inode.Total") 27 | So(stats[4].Value, ShouldEqual, int64(1310720)) 28 | So(stats[4].Tags["file_system"], ShouldEqual, "/dev/sda") 29 | So(stats[4].Tags["mounted_on"], ShouldEqual, "/") 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /disk.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const DISK = "disk" 10 | 11 | func init() { 12 | parser.Add(DISK, "true", "Collect disk usage metrics") 13 | } 14 | 15 | var diskStatFields = map[int]string{ 16 | 0: "ReadsCompleted", 17 | 1: "ReadsMerged", 18 | 2: "SectorsRead", 19 | 3: "MillisecondsRead", 20 | 4: "WritesCompleted", 21 | 5: "WritesMerged", 22 | 6: "SectorsWritten", 23 | 7: "MillisecondsWritten", 24 | 8: "IosInProgress", 25 | 9: "MillisecondsIO", 26 | 10: "WeightedMillisecondsIO", 27 | } 28 | 29 | type Disk struct { 30 | } 31 | 32 | func (self *Disk) Prefix() string { 33 | return "disk" 34 | } 35 | 36 | func (self *Disk) Collect(c *MetricsCollection) (e error) { 37 | str := ReadProcFile("diskstats") 38 | re := regexp.MustCompile("\\d+\\s+\\d+ (\\w+) (\\d+.*)") 39 | matches := re.FindAllStringSubmatch(str, -1) 40 | for _, m1 := range matches { 41 | name := m1[1] 42 | tags := map[string]string{"name": name} 43 | if strings.HasPrefix(name, "ram") || strings.HasPrefix(name, "loop") || strings.HasPrefix(name, "sr") { 44 | continue 45 | } 46 | chunks := strings.Split(m1[2], " ") 47 | for idx, v := range chunks { 48 | if i, e := strconv.ParseInt(v, 10, 64); e == nil { 49 | if k, ok := diskStatFields[idx]; ok { 50 | c.AddWithTags(k, i, tags) 51 | } 52 | } 53 | } 54 | } 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /disk_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestDiskStat(t *testing.T) { 10 | Convey("DiskStat", t, func() { 11 | mh := &MetricHandler{} 12 | disk := &Disk{} 13 | stats, _ := mh.Collect(disk) 14 | 15 | So(len(stats), ShouldEqual, 11) 16 | So(stats[0].Key, ShouldEqual, "disk.ReadsCompleted") 17 | So(stats[0].Value, ShouldEqual, int64(9748)) 18 | So(stats[0].Tags["name"], ShouldEqual, "sda") 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /elasticsearch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | const ELASTICSEARCH = "elasticsearch" 8 | 9 | func init() { 10 | parser.Add(ELASTICSEARCH, "http://127.0.0.1:9200/_status", "Collect ElasticSearch metrics") 11 | } 12 | 13 | type ElasticSearch struct { 14 | Url string 15 | RawStatus []byte 16 | } 17 | 18 | func (es *ElasticSearch) Prefix() string { 19 | return "elasticsearch" 20 | } 21 | 22 | func (es *ElasticSearch) Collect(c *MetricsCollection) (e error) { 23 | b, e := es.ReadStatus() 24 | if e != nil { 25 | return 26 | } 27 | 28 | s, e := es.ParseStatus(b) 29 | if e != nil { 30 | return 31 | } 32 | es.CollectMetricsFromStats(c, s) 33 | return 34 | } 35 | 36 | func (es *ElasticSearch) ReadStatus() (b []byte, e error) { 37 | if len(es.RawStatus) == 0 { 38 | es.RawStatus, e = FetchURL(es.Url) 39 | if e != nil { 40 | return 41 | } 42 | } 43 | b = es.RawStatus 44 | return 45 | } 46 | 47 | func (es *ElasticSearch) ParseStatus(b []byte) (ess *ElasticSearchStatus, e error) { 48 | ess = &ElasticSearchStatus{} 49 | e = json.Unmarshal(b, ess) 50 | if e != nil { 51 | return nil, e 52 | } 53 | return ess, nil 54 | } 55 | 56 | func (es *ElasticSearch) CollectMetricsFromStats(mc *MetricsCollection, s *ElasticSearchStatus) { 57 | mc.Add("shards.Total", s.Shards.Total) 58 | mc.Add("shards.Successful", s.Shards.Successful) 59 | mc.Add("shards.Failed", s.Shards.Failed) 60 | for name, index := range s.Indices { 61 | tags := map[string]string{"index_name": name} 62 | mc.AddWithTags("indices.index.SizeInBytes", index.Index.SizeInBytes, tags) 63 | mc.AddWithTags("indices.index.PrimarySizeInBytes", index.Index.PrimarySizeInBytes, tags) 64 | mc.AddWithTags("indices.translog.Operations", index.Translog.Operations, tags) 65 | mc.AddWithTags("indices.docs.NumDocs", index.Docs.NumDocs, tags) 66 | mc.AddWithTags("indices.docs.MaxDoc", index.Docs.MaxDoc, tags) 67 | mc.AddWithTags("indices.docs.DeletedDocs", index.Docs.DeletedDocs, tags) 68 | mc.AddWithTags("indices.merges.Current", index.Merges.Current, tags) 69 | mc.AddWithTags("indices.merges.CurrentDocs", index.Merges.CurrentDocs, tags) 70 | mc.AddWithTags("indices.merges.CurrentSizeInBytes", index.Merges.CurrentSizeInBytes, tags) 71 | mc.AddWithTags("indices.merges.Total", index.Merges.Total, tags) 72 | mc.AddWithTags("indices.merges.TotalTimeInMillis", index.Merges.TotalTimeInMillis, tags) 73 | mc.AddWithTags("indices.merges.TotalDocs", index.Merges.TotalDocs, tags) 74 | mc.AddWithTags("indices.merges.TotalSizeInBytes", index.Merges.TotalSizeInBytes, tags) 75 | mc.AddWithTags("indices.refresh.Total", index.Refresh.Total, tags) 76 | mc.AddWithTags("indices.refresh.TotalTimeInMillis", index.Refresh.TotalTimeInMillis, tags) 77 | mc.AddWithTags("indices.flush.Total", index.Flush.Total, tags) 78 | mc.AddWithTags("indices.flush.TotalTimeInMillis", index.Flush.TotalTimeInMillis, tags) 79 | 80 | } 81 | return 82 | } 83 | 84 | type ElasticSearchIndexMerges struct { 85 | Current int64 `json:"current"` 86 | CurrentDocs int64 `json:"current_docs"` 87 | CurrentSizeInBytes int64 `json:"current_size_in_bytes"` 88 | Total int64 `json:"total"` 89 | TotalTimeInMillis int64 `json:"total_time_in_millis"` 90 | TotalDocs int64 `json:"total_docs"` 91 | TotalSizeInBytes int64 `json:"total_size_in_bytes"` 92 | } 93 | 94 | type ElasticSearchFlushOrRefresh struct { 95 | Total int64 `json:"total"` 96 | TotalTimeInMillis int64 `json:"total_time_in_millis"` 97 | } 98 | 99 | type ElasticSearchDocs struct { 100 | NumDocs int64 `json:"num_docs"` 101 | MaxDoc int64 `json:"max_doc"` 102 | DeletedDocs int64 `json:"deleted_docs"` 103 | } 104 | 105 | type ElasticSearchTranslog struct { 106 | Operations int64 `json:"operations"` 107 | } 108 | 109 | type ElasticSearchIndexIndexStats struct { 110 | SizeInBytes int64 `json:"size_in_bytes"` 111 | PrimarySizeInBytes int64 `json:"primary_size_in_bytes"` 112 | } 113 | 114 | type ElasticSearchIndexStats struct { 115 | Translog ElasticSearchTranslog `json:"translog"` 116 | Index ElasticSearchIndexIndexStats `json:"index"` 117 | Docs ElasticSearchDocs `json:"docs"` 118 | Merges ElasticSearchIndexMerges `json:"merges"` 119 | Refresh ElasticSearchFlushOrRefresh `json:"refresh"` 120 | Flush ElasticSearchFlushOrRefresh `json:"flush"` 121 | } 122 | 123 | type ElasticSearchShards struct { 124 | Total int64 `json:"total"` 125 | Successful int64 `json:"successful"` 126 | Failed int64 `json:"failed"` 127 | } 128 | 129 | type ElasticSearchStatus struct { 130 | Shards ElasticSearchShards `json:"_shards"` 131 | Indices map[string]ElasticSearchIndexStats `json:"indices"` 132 | } 133 | -------------------------------------------------------------------------------- /elasticsearch_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestParseElasticSearch(t *testing.T) { 10 | Convey("ElasticSearch", t, func() { 11 | mh := &MetricHandler{} 12 | es := &ElasticSearch{RawStatus: readFile("fixtures/elasticsearch_status.json")} 13 | 14 | all, _ := mh.Collect(es) 15 | 16 | mapped := map[string]*Metric{} 17 | for _, m := range all { 18 | k := m.Key 19 | if name, ok := m.Tags["index_name"]; ok { 20 | k = name + "." + k 21 | } 22 | mapped[k] = m 23 | } 24 | So(mapped["elasticsearch.shards.Total"].Value, ShouldEqual, int64(20)) 25 | So(mapped["elasticsearch.shards.Successful"].Value, ShouldEqual, int64(10)) 26 | So(mapped["index.elasticsearch.indices.index.SizeInBytes"].Value, ShouldEqual, int64(2161)) 27 | So(mapped["index.elasticsearch.indices.merges.TotalDocs"].Value, ShouldEqual, int64(1)) 28 | So(len(mapped) > 5, ShouldBeTrue) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /files.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | FILES = "files" 11 | FILES_OPEN = "Open" 12 | ) 13 | 14 | var LSOF_CMD = `lsof | tail -n +2 | tr -s " " "` + "\t" + `" | cut -f 1,2` 15 | 16 | func init() { 17 | parser.Add(FILES, "true", "Collect open files") 18 | } 19 | 20 | type Files struct { 21 | RawStatus []byte 22 | } 23 | 24 | func (files *Files) fetch() (b []byte, e error) { 25 | if len(files.RawStatus) == 0 { 26 | args := []string{"-c"} 27 | args = append(args, LSOF_CMD) 28 | files.RawStatus, _ = exec.Command("bash", args...).Output() 29 | if len(files.RawStatus) == 0 { 30 | return b, fmt.Errorf("lsof returned empty result") 31 | } 32 | } 33 | return files.RawStatus, nil 34 | } 35 | 36 | func (f *Files) Collect(col *MetricsCollection) error { 37 | b, e := f.fetch() 38 | if e != nil { 39 | return e 40 | } 41 | stats := map[string]int64{} 42 | for _, line := range strings.Split(string(b), "\n") { 43 | stats[line]++ 44 | } 45 | for k, v := range stats { 46 | chunks := strings.SplitN(k, "\t", 2) 47 | if len(chunks) == 2 { 48 | name, pid := chunks[0], chunks[1] 49 | tags := map[string]string{"name": NormalizeProcessName(name), "pid": pid} 50 | col.AddWithTags(FILES_OPEN, v, tags) 51 | } 52 | } 53 | return nil 54 | } 55 | 56 | func (f *Files) Prefix() string { 57 | return FILES 58 | } 59 | -------------------------------------------------------------------------------- /files_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestFiles(t *testing.T) { 10 | Convey("Files", t, func() { 11 | files := &Files{RawStatus: readFile("fixtures/lsof.txt")} 12 | 13 | So(files.Prefix(), ShouldEqual, "files") 14 | 15 | mh := new(MetricHandler) 16 | stats, _ := mh.Collect(files) 17 | So(len(stats) > 4, ShouldBeTrue) 18 | 19 | So(len(stats), ShouldEqual, 74) 20 | 21 | names := map[string]int{} 22 | for _, s := range stats { 23 | names[s.Tags["name"]]++ 24 | } 25 | So(names["kworker/0"], ShouldEqual, 0) 26 | So(names["kworker"], ShouldEqual, 8) 27 | 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /fixtures/df.txt: -------------------------------------------------------------------------------- 1 | Filesystem 1K-blocks Used Available Use% Mounted on 2 | /dev/sda 20511356 2805436 16657344 15% / 3 | none 4 0 4 0% /sys/fs/cgroup 4 | udev 247536 4 247532 1% /dev 5 | tmpfs 50300 232 50068 1% /run 6 | none 5120 0 5120 0% /run/lock 7 | none 251488 0 251488 0% /run/shm 8 | none 102400 0 102400 0% /run/user 9 | -------------------------------------------------------------------------------- /fixtures/df_inode.txt: -------------------------------------------------------------------------------- 1 | Filesystem Inodes IUsed IFree IUse% Mounted on 2 | /dev/sda 1310720 96353 1214367 8% / 3 | none 62872 10 62862 1% /sys/fs/cgroup 4 | udev 61884 410 61474 1% /dev 5 | tmpfs 62872 300 62572 1% /run 6 | none 62872 1 62871 1% /run/lock 7 | none 62872 1 62871 1% /run/shm 8 | none 62872 1 62871 1% /run/user 9 | -------------------------------------------------------------------------------- /fixtures/diskstats_precise.txt: -------------------------------------------------------------------------------- 1 | 1 0 ram0 0 0 0 0 0 0 0 0 0 0 0 2 | 1 1 ram1 0 0 0 0 0 0 0 0 0 0 0 3 | 1 2 ram2 0 0 0 0 0 0 0 0 0 0 0 4 | 1 3 ram3 0 0 0 0 0 0 0 0 0 0 0 5 | 1 4 ram4 0 0 0 0 0 0 0 0 0 0 0 6 | 1 5 ram5 0 0 0 0 0 0 0 0 0 0 0 7 | 1 6 ram6 0 0 0 0 0 0 0 0 0 0 0 8 | 1 7 ram7 0 0 0 0 0 0 0 0 0 0 0 9 | 1 8 ram8 0 0 0 0 0 0 0 0 0 0 0 10 | 1 9 ram9 0 0 0 0 0 0 0 0 0 0 0 11 | 1 10 ram10 0 0 0 0 0 0 0 0 0 0 0 12 | 1 11 ram11 0 0 0 0 0 0 0 0 0 0 0 13 | 1 12 ram12 0 0 0 0 0 0 0 0 0 0 0 14 | 1 13 ram13 0 0 0 0 0 0 0 0 0 0 0 15 | 1 14 ram14 0 0 0 0 0 0 0 0 0 0 0 16 | 1 15 ram15 0 0 0 0 0 0 0 0 0 0 0 17 | 7 0 loop0 0 0 0 0 0 0 0 0 0 0 0 18 | 7 1 loop1 0 0 0 0 0 0 0 0 0 0 0 19 | 7 2 loop2 0 0 0 0 0 0 0 0 0 0 0 20 | 7 3 loop3 0 0 0 0 0 0 0 0 0 0 0 21 | 7 4 loop4 0 0 0 0 0 0 0 0 0 0 0 22 | 7 5 loop5 0 0 0 0 0 0 0 0 0 0 0 23 | 7 6 loop6 0 0 0 0 0 0 0 0 0 0 0 24 | 7 7 loop7 0 0 0 0 0 0 0 0 0 0 0 25 | 8 0 sda 76222 28536 4338395 631280 297119 34241 5072739 2114888 0 465528 2746384 26 | 8 1 sda1 443 2022 3511 5696 30 13 84 184 0 5612 5880 27 | 8 2 sda2 4 0 20 92 0 0 0 0 0 92 92 28 | 8 5 sda5 75746 26514 4334632 625360 297089 34228 5072655 2114704 0 464724 2740280 29 | 2 0 fd0 0 0 0 0 0 0 0 0 0 0 0 30 | 252 0 dm-0 63327 0 4024320 529128 297526 0 4801751 2162272 0 412304 2691400 31 | 252 1 dm-1 38762 0 310096 430736 33863 0 270904 1335764 0 87684 1766496 32 | -------------------------------------------------------------------------------- /fixtures/elasticsearch_status.json: -------------------------------------------------------------------------------- 1 | {"ok":true,"_shards":{"total":20,"successful":10,"failed":0},"indices":{"index":{"index":{"primary_size":"2.1kb","primary_size_in_bytes":2161,"size":"2.1kb","size_in_bytes":2161},"translog":{"operations":0},"docs":{"num_docs":1,"max_doc":1,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":1,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":5,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0},"shards":{"0":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":0,"index":"index"},"state":"STARTED","index":{"size":"79b","size_in_bytes":79},"translog":{"id":1370094060233,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"1":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":1,"index":"index"},"state":"STARTED","index":{"size":"79b","size_in_bytes":79},"translog":{"id":1370094060256,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"2":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":2,"index":"index"},"state":"STARTED","index":{"size":"1.8kb","size_in_bytes":1845},"translog":{"id":1370094060315,"operations":0},"docs":{"num_docs":1,"max_doc":1,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"3":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":3,"index":"index"},"state":"STARTED","index":{"size":"79b","size_in_bytes":79},"translog":{"id":1370094060357,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"4":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":4,"index":"index"},"state":"STARTED","index":{"size":"79b","size_in_bytes":79},"translog":{"id":1370094060406,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}]}},"football":{"index":{"primary_size":"2.1kb","primary_size_in_bytes":2234,"size":"2.1kb","size_in_bytes":2234},"translog":{"operations":0},"docs":{"num_docs":1,"max_doc":1,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":5,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0},"shards":{"0":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":0,"index":"football"},"state":"STARTED","index":{"size":"99b","size_in_bytes":99},"translog":{"id":1374616790017,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"1":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":1,"index":"football"},"state":"STARTED","index":{"size":"79b","size_in_bytes":79},"translog":{"id":1374616789937,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"2":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":2,"index":"football"},"state":"STARTED","index":{"size":"99b","size_in_bytes":99},"translog":{"id":1374616790064,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"3":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":3,"index":"football"},"state":"STARTED","index":{"size":"1.8kb","size_in_bytes":1878},"translog":{"id":1374616789985,"operations":0},"docs":{"num_docs":1,"max_doc":1,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}],"4":[{"routing":{"state":"STARTED","primary":true,"node":"LeF7X9-xQY-KpTdBXEqjjg","relocating_node":null,"shard":4,"index":"football"},"state":"STARTED","index":{"size":"79b","size_in_bytes":79},"translog":{"id":1374616790076,"operations":0},"docs":{"num_docs":0,"max_doc":0,"deleted_docs":0},"merges":{"current":0,"current_docs":0,"current_size":"0b","current_size_in_bytes":0,"total":0,"total_time":"0s","total_time_in_millis":0,"total_docs":0,"total_size":"0b","total_size_in_bytes":0},"refresh":{"total":1,"total_time":"0s","total_time_in_millis":0},"flush":{"total":0,"total_time":"0s","total_time_in_millis":0}}]}}}} 2 | -------------------------------------------------------------------------------- /fixtures/free.txt: -------------------------------------------------------------------------------- 1 | total used free shared buffers cached 2 | Mem: 8177440 7097488 1079952 0 1230276 1782336 3 | -/+ buffers/cache: 4084876 4092564 4 | Swap: 522236 22176 500060 5 | -------------------------------------------------------------------------------- /fixtures/netstat.txt: -------------------------------------------------------------------------------- 1 | Ip: 2 | 162673 total packets received 3 | 28083 forwarded 4 | 1 incoming packets discarded 5 | 134590 incoming packets delivered 6 | 160313 requests sent out 7 | Icmp: 8 | 44 ICMP messages received 9 | 2 input ICMP message failed. 10 | ICMP input histogram: 11 | destination unreachable: 3 12 | echo requests: 1 13 | echo replies: 40 14 | 172 ICMP messages sent 15 | 0 ICMP messages failed 16 | ICMP output histogram: 17 | destination unreachable: 131 18 | echo request: 40 19 | echo replies: 1 20 | IcmpMsg: 21 | InType0: 40 22 | InType3: 3 23 | InType8: 1 24 | OutType0: 1 25 | OutType3: 131 26 | OutType8: 40 27 | Tcp: 28 | 233 active connections openings 29 | 413 passive connection openings 30 | 5 failed connection attempts 31 | 2 connection resets received 32 | 1 connections established 33 | 133969 segments received 34 | 131397 segments send out 35 | 87 segments retransmited 36 | 7 bad segments received. 37 | 24 resets sent 38 | Udp: 39 | 574 packets received 40 | 3 packets to unknown port received. 41 | 0 packet receive errors 42 | 574 packets sent 43 | UdpLite: 44 | TcpExt: 45 | 1 resets received for embryonic SYN_RECV sockets 46 | 208 TCP sockets finished time wait in fast timer 47 | 686 delayed acks sent 48 | 3 delayed acks further delayed because of locked socket 49 | Quick ack mode was activated 3 times 50 | 199 packets directly queued to recvmsg prequeue. 51 | 7365 packet headers predicted 52 | 100212 acknowledgments not containing data payload received 53 | 16219 predicted acknowledgments 54 | 15 times recovered from packet loss by selective acknowledgements 55 | 6 congestion windows recovered without slow start after partial ack 56 | 2 timeouts after SACK recovery 57 | 38 fast retransmits 58 | 3 forward retransmits 59 | 8 retransmits in slow start 60 | 23 other TCP timeouts 61 | 3 DSACKs sent for old packets 62 | 3 DSACKs received 63 | 4 connections reset due to unexpected data 64 | 1 connections reset due to early user close 65 | 1 connections aborted due to timeout 66 | TCPDSACKIgnoredNoUndo: 2 67 | TCPSackShiftFallback: 264 68 | TCPDeferAcceptDrop: 21 69 | TCPRcvCoalesce: 508 70 | TCPOFOQueue: 45 71 | IpExt: 72 | InOctets: 400666043 73 | OutOctets: 667161104 74 | -------------------------------------------------------------------------------- /fixtures/netstat_connections.txt: -------------------------------------------------------------------------------- 1 | Active Internet connections (servers and established) 2 | Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name 3 | tcp 1 2 127.0.0.1:49155 0.0.0.0:* LISTEN 10747/docker 4 | tcp 0 0 127.0.0.1:49156 0.0.0.0:* LISTEN 10747/docker 5 | tcp 0 0 10.0.3.1:53 0.0.0.0:* LISTEN 3283/dnsmasq 6 | tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 659/sshd 7 | tcp 0 240 192.168.0.8:22 10.8.0.10:54235 ESTABLISHED 13128/1 8 | tcp6 0 0 :::4243 :::* LISTEN 10747/docker 9 | tcp6 0 0 fe80::146f:ecff:fe76:53 :::* LISTEN 3283/dnsmasq 10 | tcp6 0 0 :::22 :::* LISTEN 659/sshd 11 | tcp6 0 0 192.168.0.8:4243 10.8.0.10:61326 ESTABLISHED 10747/docker 12 | udp 0 0 0.0.0.0:53672 0.0.0.0:* 21535/rsyslogd 13 | udp 0 0 0.0.0.0:37821 0.0.0.0:* 705/dhclient 14 | udp 0 0 10.0.3.1:53 0.0.0.0:* 3283/dnsmasq 15 | udp 0 0 0.0.0.0:67 0.0.0.0:* 3283/dnsmasq 16 | udp 0 0 0.0.0.0:68 0.0.0.0:* 705/dhclient 17 | udp6 0 0 fe80::146f:ecff:fe76:53 :::* 3283/dnsmasq 18 | udp6 0 0 :::32832 :::* 705/dhclient 19 | -------------------------------------------------------------------------------- /fixtures/nginx.status: -------------------------------------------------------------------------------- 1 | Active connections: 10 2 | server accepts handled requests 3 | 20 30 40 4 | Reading: 50 Writing: 60 Waiting: 70 5 | -------------------------------------------------------------------------------- /fixtures/proc/1/stat: -------------------------------------------------------------------------------- 1 | 1 (init) S 0 1 1 0 -1 4219136 13398 233868 24 507 25 52 610 397 20 0 1 0 4 27394048 616 18446744073709551615 140666253873152 140666254103044 140734415909136 140734415904088 140666237678099 0 0 4096 536962595 18446744071580575321 0 0 17 0 0 0 5 0 0 140666256201248 140666256209328 140666257117184 140734415916906 140734415916920 140734415916920 140734415917037 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/10/stat: -------------------------------------------------------------------------------- 1 | 10 (rcu_sched) S 2 0 0 0 -1 2129984 0 0 0 0 27 41 0 0 20 0 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579844753 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/10082/stat: -------------------------------------------------------------------------------- 1 | 10082 (kworker/0:0) S 2 0 0 0 -1 69238880 0 0 0 0 0 26 0 0 20 0 1 0 241097 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579336855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/10162/stat: -------------------------------------------------------------------------------- 1 | 10162 (kworker/0:1) S 2 0 0 0 -1 69238880 0 0 0 0 0 6 0 0 20 0 1 0 271203 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579336855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/10177/stat: -------------------------------------------------------------------------------- 1 | 10177 (vi) S 1731 10177 1731 34817 10177 4218880 4139 20262 54 1 108 14 20 10 20 0 3 0 274334 86437888 3036 18446744073709551615 4194304 6193772 140737187728976 140737187725232 140372115498045 0 0 1166049021 704774146 18446744073709551615 0 0 17 0 0 0 6 0 0 8293712 8383964 17920000 140737187732915 140737187732926 140737187732926 140737187733484 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/10243/stat: -------------------------------------------------------------------------------- 1 | 10243 (ruby) R 10177 10177 1731 34817 10177 4219136 2157 0 0 0 4 2 0 0 20 0 2 0 293664 34545664 1404 18446744073709551615 4194304 4197132 140735058742928 140735058738424 140340095400717 0 2113665791 4096 33582663 18446744073709551615 0 0 17 0 0 0 0 0 0 6295040 6295656 31682560 140735058746742 140735058746755 140735058746755 140735058747370 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/10245/stat: -------------------------------------------------------------------------------- 1 | 10245 (find) Z 10243 10177 1731 34817 10177 4243468 432 0 0 0 0 0 0 0 20 0 1 0 293669 0 0 18446744073709551615 0 0 0 0 0 0 0 0 0 18446744071579232099 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/11/stat: -------------------------------------------------------------------------------- 1 | 11 (watchdog/0) S 2 0 0 0 -1 69239104 0 0 0 0 4 0 0 0 -100 0 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579393518 0 0 17 0 99 1 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/12/stat: -------------------------------------------------------------------------------- 1 | 12 (cpuset) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/13/stat: -------------------------------------------------------------------------------- 1 | 13 (khelper) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/1310/stat: -------------------------------------------------------------------------------- 1 | 1310 (docker) S 1 1309 1045 0 -1 4219136 2098 2130 0 0 6 2 0 0 20 0 7 0 24077 278233088 1296 18446744073709551615 4194368 9721568 140736928621424 140736928619624 4344793 0 0 0 2143420159 18446744073709551615 0 0 17 0 0 0 0 0 0 9723904 9833280 14680064 140736928628117 140736928628155 140736928628155 140736928628706 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/1311/stat: -------------------------------------------------------------------------------- 1 | 1311 (logger) S 1 1309 1045 0 -1 4218880 301 0 0 0 0 0 0 0 20 0 1 0 24077 7372800 179 18446744073709551615 4194304 4208132 140733276562608 140733276561080 139925183253232 0 0 6 0 18446744071580533641 0 0 17 0 0 0 0 0 0 6307344 6308852 10149888 140733276569014 140733276569031 140733276569031 140733276569576 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/14/stat: -------------------------------------------------------------------------------- 1 | 14 (kdevtmpfs) S 2 0 0 0 -1 2130240 0 0 0 0 0 0 0 0 20 0 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071583408420 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/15/stat: -------------------------------------------------------------------------------- 1 | 15 (netns) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/16/stat: -------------------------------------------------------------------------------- 1 | 16 (bdi-default) S 2 0 0 0 -1 10485824 0 0 0 0 0 0 0 0 20 0 1 0 6 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071580212761 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/17/stat: -------------------------------------------------------------------------------- 1 | 17 (kintegrityd) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 6 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/1712/stat: -------------------------------------------------------------------------------- 1 | 1712 (sshd) S 766 1712 1712 0 -1 4219136 1863 3854 0 0 121 320 0 0 20 0 1 0 149626 94420992 1418 18446744073709551615 139863425318912 139863425823452 140733342191568 140733342188600 139863395500563 0 0 4096 81926 18446744071580575321 0 0 17 0 0 0 0 0 0 139863427923528 139863427935120 139863435575296 140733342199653 140733342199674 140733342199674 140733342199785 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/1731/stat: -------------------------------------------------------------------------------- 1 | 1731 (bash) S 1712 1731 1731 34817 10177 4219136 22273 14545960 4 125 86 17 1553 739 20 0 1 0 149947 25169920 1470 18446744073709551615 4194304 5113252 140733404248768 140733404247464 139926258366794 0 65536 3686404 1266761467 18446744071579229522 0 0 17 0 0 0 0 0 0 7212528 7248560 38289408 140733404253738 140733404253744 140733404253744 140733404254190 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/18/stat: -------------------------------------------------------------------------------- 1 | 18 (kblockd) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 6 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/19/stat: -------------------------------------------------------------------------------- 1 | 19 (ata_sff) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 14 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/2/stat: -------------------------------------------------------------------------------- 1 | 2 (kthreadd) S 0 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 4 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579359820 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/20/stat: -------------------------------------------------------------------------------- 1 | 20 (khubd) S 2 0 0 0 -1 2097216 0 0 0 0 0 0 0 0 20 0 1 0 14 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071584054100 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/21/stat: -------------------------------------------------------------------------------- 1 | 21 (md) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 14 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/217/stat: -------------------------------------------------------------------------------- 1 | 217 (jbd2/sda-8) S 2 0 0 0 -1 2097216 0 0 0 0 0 13 0 0 20 0 1 0 85 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071581513542 0 0 17 0 0 0 27 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/218/stat: -------------------------------------------------------------------------------- 1 | 218 (ext4-dio-unwrit) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 85 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/22/stat: -------------------------------------------------------------------------------- 1 | 22 (devfreq_wq) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 14 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/24/stat: -------------------------------------------------------------------------------- 1 | 24 (khungtaskd) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 30 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579800546 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/25/stat: -------------------------------------------------------------------------------- 1 | 25 (kswapd0) S 2 0 0 0 -1 10750016 0 0 0 0 0 8 0 0 20 0 1 0 30 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071580172484 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/26/stat: -------------------------------------------------------------------------------- 1 | 26 (ksmd) S 2 0 0 0 -1 2097216 0 0 0 0 0 0 0 0 25 5 1 0 30 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071580388772 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/27/stat: -------------------------------------------------------------------------------- 1 | 27 (fsnotify_mark) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 30 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071580768753 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/28/stat: -------------------------------------------------------------------------------- 1 | 28 (ecryptfs-kthrea) S 2 0 0 0 -1 2097216 0 0 0 0 0 0 0 0 20 0 1 0 31 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071581624764 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/29/stat: -------------------------------------------------------------------------------- 1 | 29 (crypto) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 31 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/3/stat: -------------------------------------------------------------------------------- 1 | 3 (ksoftirqd/0) S 2 0 0 0 -1 69238848 0 0 0 0 0 14 0 0 20 0 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579393518 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/309/stat: -------------------------------------------------------------------------------- 1 | 309 (upstart-file-br) S 1 308 308 0 -1 4218944 130 0 0 0 2 2 0 0 20 0 1 0 140 15634432 110 18446744073709551615 140229385158656 140229385286620 140734069424496 140734069423736 140229373227539 0 0 1 81920 18446744071580575321 0 0 17 0 0 0 0 0 0 140229387386560 140229387391200 140229412999168 140734069432040 140734069432069 140734069432069 140734069432286 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/377/stat: -------------------------------------------------------------------------------- 1 | 377 (upstart-udev-br) S 1 376 376 0 -1 4218944 169 0 0 0 7 2 0 0 20 0 1 0 162 17731584 159 18446744073709551615 140304659156992 140304659222956 140735312271024 140735312270312 140304645059091 0 0 1 81920 18446744071580575321 0 0 17 0 0 0 0 0 0 140304661320720 140304661323984 140304671760384 140735312273175 140735312273204 140735312273204 140735312273374 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/382/stat: -------------------------------------------------------------------------------- 1 | 382 (udevd) S 1 382 382 0 -1 4219200 1541 44428 0 38 1 6 3 27 20 0 1 0 165 22024192 315 18446744073709551615 139795714461696 139795714597492 140734276394096 140734276386568 139795704919219 0 2147221247 0 0 18446744071580787619 0 0 17 0 0 0 8 0 0 139795716695184 139795716698192 139795726069760 140734276398916 140734276398937 140734276398937 140734276399084 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/40/stat: -------------------------------------------------------------------------------- 1 | 40 (kthrotld) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 31 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/42/stat: -------------------------------------------------------------------------------- 1 | 42 (scsi_eh_0) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 36 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071583675096 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/428/stat: -------------------------------------------------------------------------------- 1 | 428 (dbus-daemon) S 1 428 428 0 -1 4219200 288 65 0 0 5 2 0 0 20 0 1 0 196 24584192 260 18446744073709551615 4194304 4590404 140735536111648 140735536109592 140493615129779 0 0 0 16385 18446744071580787619 0 0 17 0 0 0 0 0 0 6690576 6694576 31633408 140735536119610 140735536119638 140735536119638 140735536119783 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/43/stat: -------------------------------------------------------------------------------- 1 | 43 (scsi_eh_1) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 36 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071583675096 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/432/stat: -------------------------------------------------------------------------------- 1 | 432 (rsyslogd) S 1 431 431 0 -1 4219200 501 0 7 0 4 2 0 0 20 0 6 0 206 414994432 423 18446744073709551615 4194304 4544324 140734360388192 140734360386464 140003199259187 0 0 16781830 1133601 18446744073709551615 0 0 17 0 0 0 2 0 0 6642392 6670304 17653760 140734360395589 140734360395602 140734360395602 140734360395749 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/45/stat: -------------------------------------------------------------------------------- 1 | 45 (binder) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 37 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/46/stat: -------------------------------------------------------------------------------- 1 | 46 (kworker/u:3) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 20 0 1 0 38 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579336855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/5/stat: -------------------------------------------------------------------------------- 1 | 5 (kworker/0:0H) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579336855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/514/stat: -------------------------------------------------------------------------------- 1 | 514 (udevd) S 382 382 382 0 -1 4219200 186 1786 0 0 0 0 0 0 20 0 1 0 245 22020096 211 18446744073709551615 139795714461696 139795714597492 140734276394096 140734276386120 139795704919219 0 2147221247 0 0 18446744071580787619 0 0 17 0 0 0 0 0 0 139795716695184 139795716698192 139795726069760 140734276398916 140734276398937 140734276398937 140734276399084 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/515/stat: -------------------------------------------------------------------------------- 1 | 515 (udevd) S 382 382 382 0 -1 4219200 167 0 0 0 0 0 0 0 20 0 1 0 245 22065152 203 18446744073709551615 139795714461696 139795714597492 140734276394096 140734276386120 139795704919219 0 2147221247 0 0 18446744071580787619 0 0 17 0 0 0 0 0 0 139795716695184 139795716698192 139795726069760 140734276398916 140734276398937 140734276398937 140734276399084 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/529/stat: -------------------------------------------------------------------------------- 1 | 529 (kpsmoused) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 256 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/608/stat: -------------------------------------------------------------------------------- 1 | 608 (upstart-socket-) S 1 607 607 0 -1 4218944 121 0 0 0 1 0 0 0 20 0 1 0 304 15622144 102 18446744073709551615 139763791265792 139763791386668 140734939626832 140734939626072 139763779334675 0 0 1 81920 18446744071580575321 0 0 17 0 0 0 0 0 0 139763793485552 139763793490096 139763823292416 140734939631342 140734939631373 140734939631373 140734939631580 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/627/stat: -------------------------------------------------------------------------------- 1 | 627 (kvm-irqfd-clean) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 324 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/65/stat: -------------------------------------------------------------------------------- 1 | 65 (deferwq) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 39 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/66/stat: -------------------------------------------------------------------------------- 1 | 66 (charger_manager) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 0 -20 1 0 39 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579331855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/67/stat: -------------------------------------------------------------------------------- 1 | 67 (kworker/u:4) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 20 0 1 0 52 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579336855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/7/stat: -------------------------------------------------------------------------------- 1 | 7 (kworker/u:0H) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579336855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/766/stat: -------------------------------------------------------------------------------- 1 | 766 (sshd) S 1 766 766 0 -1 4219136 971 102515 0 65 0 0 58 74 20 0 1 0 378 53506048 709 18446744073709551615 139900544647168 139900545151708 140734995814080 140734995811048 139900514828819 0 0 4096 81925 18446744071580575321 0 0 17 0 0 0 0 0 0 139900547251784 139900547263376 139900570157056 140734995820392 140734995820410 140734995820410 140734995820521 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/8/stat: -------------------------------------------------------------------------------- 1 | 8 (migration/0) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 -100 0 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579704621 0 0 17 0 99 1 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/843/stat: -------------------------------------------------------------------------------- 1 | 843 (getty) S 1 843 843 1028 843 4219136 407 0 2 0 0 0 0 0 20 0 1 0 392 16207872 244 18446744073709551615 4194304 4220196 140737123358688 140737123348040 140398999350000 0 0 6 0 18446744071583122271 0 0 17 0 0 0 0 0 0 6319632 6321492 17674240 140737123364645 140737123364671 140737123364671 140737123364844 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/847/stat: -------------------------------------------------------------------------------- 1 | 847 (getty) S 1 847 847 1029 847 4219136 412 0 0 0 0 0 0 0 20 0 1 0 393 16207872 242 18446744073709551615 4194304 4220196 140736813518592 140736813507944 139736262871792 0 0 6 0 18446744071583122271 0 0 17 0 0 0 0 0 0 6319632 6321492 18739200 140736813526821 140736813526847 140736813526847 140736813527020 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/858/stat: -------------------------------------------------------------------------------- 1 | 858 (getty) S 1 858 858 1026 858 4219136 406 0 0 0 0 0 0 0 20 0 1 0 395 16207872 243 18446744073709551615 4194304 4220196 140736917935456 140736917924808 140119164803824 0 0 6 0 18446744071583122271 0 0 17 0 0 0 0 0 0 6319632 6321492 35164160 140736917937957 140736917937983 140736917937983 140736917938156 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/859/stat: -------------------------------------------------------------------------------- 1 | 859 (getty) S 1 859 859 1027 859 4219136 407 0 0 0 0 0 0 0 20 0 1 0 396 16207872 243 18446744073709551615 4194304 4220196 140734775779440 140734775768792 140703233710832 0 0 6 0 18446744071583122271 0 0 17 0 0 0 0 0 0 6319632 6321492 39526400 140734775787301 140734775787327 140734775787327 140734775787500 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/864/stat: -------------------------------------------------------------------------------- 1 | 864 (getty) S 1 864 864 1030 864 4219136 405 0 0 0 0 0 0 0 20 0 1 0 397 16207872 243 18446744073709551615 4194304 4220196 140734772764656 140734772754008 140204077431536 0 0 6 0 18446744071583122271 0 0 17 0 0 0 0 0 0 6319632 6321492 9183232 140734772772645 140734772772671 140734772772671 140734772772844 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/879/stat: -------------------------------------------------------------------------------- 1 | 879 (acpid) S 1 879 879 0 -1 4218944 160 0 0 0 0 0 0 0 20 0 1 0 400 4481024 170 18446744073709551615 4194304 4236036 140733429612912 140733429611992 140661390111251 0 0 4096 16391 18446744071580575321 0 0 17 0 0 0 0 0 0 6336016 6337392 35840000 140733429616405 140733429616456 140733429616456 140733429616616 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/880/stat: -------------------------------------------------------------------------------- 1 | 880 (cron) S 1 880 880 0 -1 4218944 812 160111 0 22 0 1 17 20 20 0 1 0 400 21835776 256 18446744073709551615 4194304 4232388 140733892459504 140733892458744 140483543231776 0 0 0 65537 18446744071579378985 0 0 17 0 0 0 0 0 0 6331888 6333728 33746944 140733892460357 140733892460362 140733892460362 140733892460521 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/882/stat: -------------------------------------------------------------------------------- 1 | 882 (atd) S 1 882 882 0 -1 4218944 59 0 0 0 0 0 0 0 20 0 1 0 400 19582976 41 18446744073709551615 139654509834240 139654509852868 140734577181888 140734577181000 139654502255904 0 0 0 81923 18446744071579378985 0 0 17 0 0 0 0 0 0 139654511950552 139654511951904 139654530768896 140734577184584 140734577184588 140734577184588 140734577184746 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/9/stat: -------------------------------------------------------------------------------- 1 | 9 (rcu_bh) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579844753 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/932/stat: -------------------------------------------------------------------------------- 1 | 932 (getty) S 1 932 932 1025 932 4219136 421 0 0 0 0 0 0 0 20 0 1 0 412 16207872 242 18446744073709551615 4194304 4220196 140735375720000 140735375709352 140651447692016 0 0 6 0 18446744071583122271 0 0 17 0 0 0 0 0 0 6319632 6321492 31514624 140735375728395 140735375728421 140735375728421 140735375728620 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/938/stat: -------------------------------------------------------------------------------- 1 | 938 (dnsmasq) S 1 937 937 0 -1 4219200 197 0 0 0 0 0 0 0 20 0 1 0 415 26705920 228 18446744073709551615 4194304 4440892 140734564468688 140734564467704 140110754062867 0 0 4096 92673 18446744071580575321 0 0 17 0 0 0 0 0 0 6540768 6547176 8413184 140734564470098 140734564470417 140734564470417 140734564470758 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/954/stat: -------------------------------------------------------------------------------- 1 | 954 (flush-8:0) S 2 0 0 0 -1 10485824 0 0 0 0 0 19 0 0 20 0 1 0 646 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071580680188 0 0 17 0 0 0 3 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/956/stat: -------------------------------------------------------------------------------- 1 | 956 (kauditd) S 2 0 0 0 -1 2097216 0 0 0 0 0 0 0 0 20 0 1 0 957 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579707356 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/962/stat: -------------------------------------------------------------------------------- 1 | 962 (console-kit-dae) S 1 428 428 0 -1 4219136 2274 5079 22 2 2 2 0 0 20 0 65 0 1220 598454272 954 18446744073709551615 4194304 4330060 140735357807488 140735357806816 139670197035981 0 0 4096 66048 18446744073709551615 0 0 17 0 0 0 1 0 0 6430104 6433316 30523392 140735357812566 140735357812607 140735357812607 140735357812699 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/9934/stat: -------------------------------------------------------------------------------- 1 | 9934 (kworker/0:2) S 2 0 0 0 -1 69238880 0 0 0 0 0 46 0 0 20 0 1 0 210992 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579336855 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /fixtures/proc/diskstats: -------------------------------------------------------------------------------- 1 | 1 0 ram0 0 0 0 0 0 0 0 0 0 0 0 2 | 1 1 ram1 0 0 0 0 0 0 0 0 0 0 0 3 | 1 2 ram2 0 0 0 0 0 0 0 0 0 0 0 4 | 1 3 ram3 0 0 0 0 0 0 0 0 0 0 0 5 | 1 4 ram4 0 0 0 0 0 0 0 0 0 0 0 6 | 1 5 ram5 0 0 0 0 0 0 0 0 0 0 0 7 | 1 6 ram6 0 0 0 0 0 0 0 0 0 0 0 8 | 1 7 ram7 0 0 0 0 0 0 0 0 0 0 0 9 | 1 8 ram8 0 0 0 0 0 0 0 0 0 0 0 10 | 1 9 ram9 0 0 0 0 0 0 0 0 0 0 0 11 | 1 10 ram10 0 0 0 0 0 0 0 0 0 0 0 12 | 1 11 ram11 0 0 0 0 0 0 0 0 0 0 0 13 | 1 12 ram12 0 0 0 0 0 0 0 0 0 0 0 14 | 1 13 ram13 0 0 0 0 0 0 0 0 0 0 0 15 | 1 14 ram14 0 0 0 0 0 0 0 0 0 0 0 16 | 1 15 ram15 0 0 0 0 0 0 0 0 0 0 0 17 | 7 0 loop0 0 0 0 0 0 0 0 0 0 0 0 18 | 7 1 loop1 0 0 0 0 0 0 0 0 0 0 0 19 | 7 2 loop2 0 0 0 0 0 0 0 0 0 0 0 20 | 7 3 loop3 0 0 0 0 0 0 0 0 0 0 0 21 | 7 4 loop4 0 0 0 0 0 0 0 0 0 0 0 22 | 7 5 loop5 0 0 0 0 0 0 0 0 0 0 0 23 | 7 6 loop6 0 0 0 0 0 0 0 0 0 0 0 24 | 7 7 loop7 0 0 0 0 0 0 0 0 0 0 0 25 | 8 0 sda 9748 3699 293170 4064 2850 7475 1861584 122608 0 6868 126660 26 | 11 0 sr0 0 0 0 0 0 0 0 0 0 0 0 27 | -------------------------------------------------------------------------------- /fixtures/proc/loadavg: -------------------------------------------------------------------------------- 1 | 0.03 0.07 0.08 1/143 10123 2 | -------------------------------------------------------------------------------- /fixtures/proc/meminfo: -------------------------------------------------------------------------------- 1 | MemTotal: 502976 kB 2 | MemFree: 387824 kB 3 | Buffers: 21612 kB 4 | Cached: 31436 kB 5 | SwapCached: 0 kB 6 | Active: 61740 kB 7 | Inactive: 7852 kB 8 | Active(anon): 16568 kB 9 | Inactive(anon): 212 kB 10 | Active(file): 45172 kB 11 | Inactive(file): 7640 kB 12 | Unevictable: 0 kB 13 | Mlocked: 0 kB 14 | SwapTotal: 0 kB 15 | SwapFree: 0 kB 16 | Dirty: 4 kB 17 | Writeback: 0 kB 18 | AnonPages: 16560 kB 19 | Mapped: 11284 kB 20 | Shmem: 236 kB 21 | Slab: 34224 kB 22 | SReclaimable: 25572 kB 23 | SUnreclaim: 8652 kB 24 | KernelStack: 1144 kB 25 | PageTables: 1740 kB 26 | NFS_Unstable: 0 kB 27 | Bounce: 0 kB 28 | WritebackTmp: 0 kB 29 | CommitLimit: 251488 kB 30 | Committed_AS: 350856 kB 31 | VmallocTotal: 34359738367 kB 32 | VmallocUsed: 3052 kB 33 | VmallocChunk: 34359732840 kB 34 | HardwareCorrupted: 0 kB 35 | AnonHugePages: 0 kB 36 | HugePages_Total: 0 37 | HugePages_Free: 0 38 | HugePages_Rsvd: 0 39 | HugePages_Surp: 0 40 | Hugepagesize: 2048 kB 41 | DirectMap4k: 34804 kB 42 | DirectMap2M: 489472 kB 43 | -------------------------------------------------------------------------------- /fixtures/proc/net/netstat: -------------------------------------------------------------------------------- 1 | TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned OfoPruned OutOfWindowIcmps LockDroppedIcmps ArpFilter TW TWRecycled TWKilled PAWSPassive PAWSActive PAWSEstab DelayedACKs DelayedACKLocked DelayedACKLost ListenOverflows ListenDrops TCPPrequeued TCPDirectCopyFromBacklog TCPDirectCopyFromPrequeue TCPPrequeueDropped TCPHPHits TCPHPHitsToUser TCPPureAcks TCPHPAcks TCPRenoRecovery TCPSackRecovery TCPSACKReneging TCPFACKReorder TCPSACKReorder TCPRenoReorder TCPTSReorder TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo TCPLostRetransmit TCPRenoFailures TCPSackFailures TCPLossFailures TCPFastRetrans TCPForwardRetrans TCPSlowStartRetrans TCPTimeouts TCPRenoRecoveryFail TCPSackRecoveryFail TCPSchedulerFailed TCPRcvCollapsed TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv TCPAbortOnData TCPAbortOnClose TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger TCPAbortFailed TCPMemoryPressures TCPSACKDiscard TCPDSACKIgnoredOld TCPDSACKIgnoredNoUndo TCPSpuriousRTOs TCPMD5NotFound TCPMD5Unexpected TCPSackShifted TCPSackMerged TCPSackShiftFallback TCPBacklogDrop TCPMinTTLDrop TCPDeferAcceptDrop IPReversePathFilter TCPTimeWaitOverflow TCPReqQFullDoCookies TCPReqQFullDrop TCPRetransFail TCPRcvCoalesce TCPOFOQueue TCPOFODrop TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd 2 | TcpExt: 0 0 2 0 210 0 0 13 0 0 4208 0 0 0 0 12 37718 2 2138 0 0 16349 711126 22454433 400 561282 13391 94274 155821 0 17 0 0 0 0 4 12 4 40 448 0 0 1 27 17 12 44 1213 0 0 0 2555 2052 11 508 0 309 2898 0 180 0 6 0 0 1 117 4 0 0 0 0 1441 10 0 0 1458 0 0 0 6 198338 15942 0 11 446 445 0 0 0 0 0 3 | IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets 4 | IpExt: 41 0 45863 1586 164429 0 934529039 1037097012 4228142 139009 21904800 0 5 | -------------------------------------------------------------------------------- /fixtures/proc/net/snmp: -------------------------------------------------------------------------------- 1 | Ip: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates 2 | Ip: 2 64 1594741 0 0 0 0 0 1572047 2228197 5479 1154 0 390 78 0 0 0 0 3 | Icmp: InMsgs InErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskReps 4 | Icmp: 8043 192 8038 0 0 0 0 1 4 0 0 0 0 10066 0 10054 0 0 0 0 11 1 0 0 0 0 5 | IcmpMsg: InType0 InType3 InType8 OutType0 OutType3 OutType8 6 | IcmpMsg: 4 8038 1 1 10054 11 7 | Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts 8 | Tcp: 1 200 120000 -1 22169 280 175 2989 9 1017368 949859 2611 441 3802 9 | Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors 10 | Udp: 339484 6204 95 1265074 0 4071 11 | UdpLite: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors 12 | UdpLite: 0 0 0 0 0 0 13 | -------------------------------------------------------------------------------- /fixtures/proc/stat: -------------------------------------------------------------------------------- 1 | cpu 3700 3 4695 238696 94 0 175 7 0 0 2 | cpu0 3701 3 4695 238696 94 0 175 7 0 0 3 | intr 72774 40 9 0 0 0 0 2 0 1 0 0 23252 144 0 12602 2479 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 | ctxt 957584 5 | btime 1372481686 6 | processes 42476 7 | procs_running 1 8 | procs_blocked 3 9 | softirq 196747 0 31941 12458 10226 13877 0 3 0 22 128220 10 | -------------------------------------------------------------------------------- /fixtures/redis_info.txt: -------------------------------------------------------------------------------- 1 | # Server 2 | redis_version:2.6.14 3 | redis_git_sha1:00000000 4 | redis_git_dirty:0 5 | redis_mode:standalone 6 | os:Darwin 12.4.0 x86_64 7 | arch_bits:64 8 | multiplexing_api:kqueue 9 | gcc_version:4.2.1 10 | process_id:56704 11 | run_id:e67df8cf687d22728fd6bf9ceaed10ab2bd8a49f 12 | tcp_port:6379 13 | uptime_in_seconds:178 14 | uptime_in_days:0 15 | hz:10 16 | lru_clock:1160324 17 | 18 | # Clients 19 | connected_clients:1 20 | client_longest_output_list:0 21 | client_biggest_input_buf:0 22 | blocked_clients:0 23 | 24 | # Memory 25 | used_memory:1065744 26 | used_memory_human:1.02M 27 | used_memory_rss:1658880 28 | used_memory_peak:1014432 29 | used_memory_peak_human:990.66K 30 | used_memory_lua:31744 31 | mem_fragmentation_ratio:1.56 32 | mem_allocator:libc 33 | 34 | # Persistence 35 | loading:0 36 | rdb_changes_since_last_save:0 37 | rdb_bgsave_in_progress:0 38 | rdb_last_save_time:1374751867 39 | rdb_last_bgsave_status:ok 40 | rdb_last_bgsave_time_sec:-1 41 | rdb_current_bgsave_time_sec:-1 42 | aof_enabled:0 43 | aof_rewrite_in_progress:0 44 | aof_rewrite_scheduled:0 45 | aof_last_rewrite_time_sec:-1 46 | aof_current_rewrite_time_sec:-1 47 | aof_last_bgrewrite_status:ok 48 | 49 | # Stats 50 | total_connections_received:3 51 | total_commands_processed:1 52 | instantaneous_ops_per_sec:0 53 | rejected_connections:0 54 | expired_keys:0 55 | evicted_keys:0 56 | keyspace_hits:0 57 | keyspace_misses:0 58 | pubsub_channels:0 59 | pubsub_patterns:0 60 | latest_fork_usec:0 61 | 62 | # Replication 63 | role:master 64 | connected_slaves:0 65 | 66 | # CPU 67 | used_cpu_sys:0.03 68 | used_cpu_user:0.03 69 | used_cpu_sys_children:0.00 70 | used_cpu_user_children:0.00 71 | 72 | # Keyspace 73 | -------------------------------------------------------------------------------- /fixtures/riak.json: -------------------------------------------------------------------------------- 1 | {"riak_kv_stat_ts":1374395439,"vnode_gets":241,"vnode_gets_total":5396577,"vnode_puts":1,"vnode_puts_total":192275,"vnode_index_reads":0,"vnode_index_reads_total":36271,"vnode_index_writes":1,"vnode_index_writes_total":117418,"vnode_index_writes_postings":0,"vnode_index_writes_postings_total":4,"vnode_index_deletes":0,"vnode_index_deletes_total":0,"vnode_index_deletes_postings":0,"vnode_index_deletes_postings_total":0,"node_gets":72,"node_gets_total":1463889,"node_get_fsm_siblings_mean":1,"node_get_fsm_siblings_median":1,"node_get_fsm_siblings_95":1,"node_get_fsm_siblings_99":1,"node_get_fsm_siblings_100":1,"node_get_fsm_objsize_mean":738,"node_get_fsm_objsize_median":560,"node_get_fsm_objsize_95":1290,"node_get_fsm_objsize_99":1290,"node_get_fsm_objsize_100":1290,"node_get_fsm_time_mean":15639,"node_get_fsm_time_median":4385,"node_get_fsm_time_95":61395,"node_get_fsm_time_99":65222,"node_get_fsm_time_100":69890,"node_puts":1,"node_puts_total":30466,"node_put_fsm_time_mean":0,"node_put_fsm_time_median":0,"node_put_fsm_time_95":0,"node_put_fsm_time_99":0,"node_put_fsm_time_100":0,"read_repairs":0,"read_repairs_total":2499,"coord_redirs_total":3157,"executing_mappers":0,"precommit_fail":0,"postcommit_fail":0,"pbc_active":133,"pbc_connects":0,"pbc_connects_total":2115,"node_get_fsm_active":0,"node_get_fsm_active_60s":72,"node_get_fsm_in_rate":6,"node_get_fsm_out_rate":6,"node_get_fsm_rejected":0,"node_get_fsm_rejected_60s":0,"node_get_fsm_rejected_total":0,"node_put_fsm_active":0,"node_put_fsm_active_60s":1,"node_put_fsm_in_rate":0,"node_put_fsm_out_rate":0,"node_put_fsm_rejected":0,"node_put_fsm_rejected_60s":0,"node_put_fsm_rejected_total":0,"read_repairs_primary_outofdate_one":0,"read_repairs_primary_outofdate_count":709,"read_repairs_primary_notfound_one":0,"read_repairs_primary_notfound_count":340,"read_repairs_fallback_outofdate_one":0,"read_repairs_fallback_outofdate_count":201,"read_repairs_fallback_notfound_one":0,"read_repairs_fallback_notfound_count":1661,"leveldb_read_block_error":"undefined","riak_pipe_stat_ts":1374395438,"pipeline_active":0,"pipeline_create_count":20,"pipeline_create_one":0,"pipeline_create_error_count":0,"pipeline_create_error_one":0,"cpu_nprocs":313,"cpu_avg1":274,"cpu_avg5":115,"cpu_avg15":72,"mem_total":1043484672,"mem_allocated":980062208,"nodename":"riak@192.168.0.17","connected_nodes":["riak@192.168.0.16","riak@192.168.0.18","riak@192.168.0.21","riak@192.168.0.20"],"sys_driver_version":"2.0","sys_global_heaps_size":0,"sys_heap_type":"private","sys_logical_processors":1,"sys_otp_release":"R15B01","sys_process_count":1105,"sys_smp_support":true,"sys_system_version":"Erlang R15B01 (erts-5.9.1) [source] [64-bit] [smp:1:1] [async-threads:64] [kernel-poll:true]","sys_system_architecture":"x86_64-pc-linux-gnu","sys_threads_enabled":true,"sys_thread_pool_size":64,"sys_wordsize":8,"ring_members":["riak@192.168.0.16","riak@192.168.0.17","riak@192.168.0.18","riak@192.168.0.20","riak@192.168.0.21"],"ring_num_partitions":64,"ring_ownership":"[{'riak@192.168.0.20',4},\n {'riak@192.168.0.21',16},\n {'riak@192.168.0.16',13},\n {'riak@192.168.0.17',16},\n {'riak@192.168.0.18',15}]","ring_creation_size":64,"storage_backend":"riak_cs_kv_multi_backend","erlydtl_version":"0.7.0","riak_control_version":"1.3.0","cluster_info_version":"1.2.3","riak_search_version":"1.3.0","merge_index_version":"1.3.0","riak_kv_version":"1.3.2","sidejob_version":"0.2.0","riak_api_version":"1.3.1","riak_pipe_version":"1.3.2","riak_core_version":"1.3.2","bitcask_version":"1.6.2","basho_stats_version":"1.0.3","webmachine_version":"1.9.3","mochiweb_version":"1.5.1p3","inets_version":"5.9","erlang_js_version":"1.2.2","runtime_tools_version":"1.8.8","os_mon_version":"2.2.9","riak_sysmon_version":"1.1.3","ssl_version":"5.0.1","public_key_version":"0.15","crypto_version":"2.1","sasl_version":"2.2.1","lager_version":"1.2.2","syslog_version":"1.0.1","syntax_tools_version":"1.6.8","compiler_version":"4.8.1","stdlib_version":"1.18.1","kernel_version":"2.15.1","memory_total":101819824,"memory_processes":12670452,"memory_processes_used":12659664,"memory_system":89149372,"memory_atom":512601,"memory_atom_used":506879,"memory_binary":20104960,"memory_code":11436717,"memory_ets":6818808,"riak_core_stat_ts":1374395440,"ignored_gossip_total":0,"rings_reconciled_total":795,"rings_reconciled":0,"gossip_received":7,"rejected_handoffs":8,"handoff_timeouts":0,"dropped_vnode_requests_total":0,"converge_delay_min":0,"converge_delay_max":0,"converge_delay_mean":0,"converge_delay_last":0,"rebalance_delay_min":0,"rebalance_delay_max":0,"rebalance_delay_mean":0,"rebalance_delay_last":0,"riak_kv_vnodes_running":19,"riak_kv_vnodeq_min":0,"riak_kv_vnodeq_median":0,"riak_kv_vnodeq_mean":0,"riak_kv_vnodeq_max":0,"riak_kv_vnodeq_total":0,"riak_pipe_vnodes_running":12,"riak_pipe_vnodeq_min":0,"riak_pipe_vnodeq_median":0,"riak_pipe_vnodeq_mean":0,"riak_pipe_vnodeq_max":0,"riak_pipe_vnodeq_total":0} -------------------------------------------------------------------------------- /free.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "regexp" 7 | ) 8 | 9 | const ( 10 | FREE = "free" 11 | FREE_MEM_TOTAL = "mem.total" 12 | FREE_MEM_USED = "mem.used" 13 | FREE_MEM_FREE = "mem.free" 14 | FREE_MEM_BUFFERS = "mem.buffers" 15 | FREE_MEM_CACHED = "mem.cached" 16 | FREE_SWAP_TOTAL = "swap.total" 17 | FREE_SWAP_USED = "swap.used" 18 | FREE_SWAP_FREE = "swap.free" 19 | ) 20 | 21 | var FREE_MEMORY_REGEXP = regexp.MustCompile("Mem:\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)") 22 | var FREE_SWAP_REGEXP = regexp.MustCompile("Swap:\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)") 23 | 24 | func init() { 25 | parser.Add(FREE, "true", "Collect free metrics") 26 | } 27 | 28 | type Free struct { 29 | RawStatus []byte 30 | } 31 | 32 | func (free *Free) fetch() (b []byte, e error) { 33 | if len(free.RawStatus) == 0 { 34 | free.RawStatus, _ = exec.Command("free").Output() 35 | if len(free.RawStatus) == 0 { 36 | return b, fmt.Errorf("free returned empty output") 37 | } 38 | } 39 | return free.RawStatus, nil 40 | } 41 | 42 | func (free *Free) Collect(c *MetricsCollection) error { 43 | s, e := free.fetch() 44 | if e != nil { 45 | return e 46 | } 47 | raw := string(s) 48 | parts := FREE_MEMORY_REGEXP.FindStringSubmatch(raw) 49 | if len(parts) > 0 { 50 | c.Add(FREE_MEM_TOTAL, parseInt64(parts[1])) 51 | c.Add(FREE_MEM_USED, parseInt64(parts[2])) 52 | c.Add(FREE_MEM_FREE, parseInt64(parts[3])) 53 | c.Add(FREE_MEM_BUFFERS, parseInt64(parts[5])) 54 | c.Add(FREE_MEM_CACHED, parseInt64(parts[6])) 55 | } 56 | 57 | parts = FREE_SWAP_REGEXP.FindStringSubmatch(raw) 58 | if len(parts) > 0 { 59 | c.Add(FREE_SWAP_TOTAL, parseInt64(parts[1])) 60 | c.Add(FREE_SWAP_USED, parseInt64(parts[2])) 61 | c.Add(FREE_SWAP_FREE, parseInt64(parts[3])) 62 | } 63 | return nil 64 | } 65 | 66 | func (free *Free) Prefix() string { 67 | return "free" 68 | } 69 | -------------------------------------------------------------------------------- /free_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | var freeResult = map[string]int{ 10 | "free.mem.total": 8177440, 11 | "free.mem.used": 7097488, 12 | "free.mem.free": 1079952, 13 | "free.mem.buffers": 1230276, 14 | "free.mem.cached": 1782336, 15 | "free.swap.total": 522236, 16 | "free.swap.used": 22176, 17 | "free.swap.free": 500060, 18 | } 19 | 20 | func TestFree(t *testing.T) { 21 | Convey("Free", t, func() { 22 | mh := new(MetricHandler) 23 | free := &Free{RawStatus: readFile("fixtures/free.txt")} 24 | stats, _ := mh.Collect(free) 25 | agg := aggregateStats(stats) 26 | for k, v := range freeResult { 27 | So(agg[k], ShouldEqual, v) 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /graphite_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | ) 9 | 10 | func TestGraphite(t *testing.T) { 11 | Convey("TestGraphite", t, func() { 12 | theTime := time.Unix(11, 0) 13 | m := &Metric{Key: "metric", Value: 10} 14 | So(m.Graphite(theTime, "test.host"), ShouldEqual, "metrix.hosts.test.host.metric 10 11") 15 | 16 | m = &Metric{Key: "os.cpu.User", Value: 10, Tags: map[string]string{"cpu_id": "1"}} 17 | So(m.Graphite(theTime, "test.host"), ShouldEqual, "metrix.hosts.test.host.os.cpu1.User 10 11") 18 | 19 | m = &Metric{Key: "disk.WeightedMillisecondsIO", Value: int64(10), Tags: map[string]string{"name": "sda"}} 20 | So(m.Graphite(theTime, "test.host"), ShouldEqual, "metrix.hosts.test.host.disk.sda.WeightedMillisecondsIO 10 11") 21 | 22 | m = &Metric{Key: "df.inodes.Use", Value: 10, Tags: map[string]string{"file_system": "/dev/sda"}} 23 | So(m.Graphite(theTime, "test.host"), ShouldEqual, "metrix.hosts.test.host.df.dev.sda.inodes.Use 10 11") 24 | 25 | m = &Metric{Key: "processes.Utime", Value: 10, Tags: map[string]string{"name": "init", "pid": "10"}} 26 | So(m.Graphite(theTime, "test.host"), ShouldEqual, "metrix.hosts.test.host.processes.init.10.Utime 10 11") 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /influxdb_output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "net/url" 7 | "syscall" 8 | 9 | influxClient "github.com/influxdb/influxdb/client" 10 | ) 11 | 12 | 13 | 14 | func PublishMetricsWithInfluxDB(address string, metrics []*Metric, hostname string) (e error) { 15 | 16 | started := time.Now() 17 | var tv syscall.Timeval 18 | syscall.Gettimeofday(&tv) 19 | taken := (int64(tv.Sec)*1e3 + int64(tv.Usec)/1e3) 20 | series := []*influxClient.Series{} 21 | //TODO, test address componets ... 22 | u, err := url.Parse("addmissing://"+address) 23 | if err != nil { 24 | return err 25 | } 26 | p, _ := u.User.Password() 27 | 28 | c, err := influxClient.NewClient(&influxClient.ClientConfig{ 29 | Username: u.User.Username(), 30 | Password: p, 31 | Host: u.Host, 32 | Database: u.Path[1:], 33 | }) 34 | if err != nil { 35 | return err 36 | } 37 | for _, m := range metrics { 38 | columns := []string{"time", "value", "host"} 39 | _points := []interface{}{taken, m.Value, hostname} 40 | 41 | for k,v := range m.Tags{ 42 | columns = append(columns,k) 43 | _points = append(_points,v) 44 | } 45 | points := [][]interface{}{_points} 46 | series = append(series, &influxClient.Series{ 47 | Name: m.Key, 48 | Columns: columns, 49 | Points: points, 50 | }) 51 | } 52 | if len(series) > 0 { 53 | if err := c.WriteSeries(series); err != nil { 54 | return err 55 | } else { 56 | fmt.Printf("sent %d metrics in %.06f to influxdb://%s/%s \n", len(series), time.Now().Sub(started).Seconds(),u.Host,u.Path[1:]) 57 | } 58 | } 59 | return nil 60 | } -------------------------------------------------------------------------------- /loadavg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | const LOADAVG = "loadavg" 9 | 10 | func init() { 11 | parser.Add(LOADAVG, "true", "Collect loadvg metrics") 12 | } 13 | 14 | type LoadAvg struct { 15 | } 16 | 17 | func (l *LoadAvg) Prefix() string { 18 | return "load" 19 | } 20 | 21 | func (l *LoadAvg) Collect(c *MetricsCollection) (e error) { 22 | s := ReadProcFile("loadavg") 23 | chunks := strings.Split(s, " ") 24 | if len(chunks) >= 3 { 25 | if v, e := l.ParseLoadAvg(chunks[0]); e == nil { 26 | c.Add("Load1m", v) 27 | } 28 | if v, e := l.ParseLoadAvg(chunks[1]); e == nil { 29 | c.Add("Load5m", v) 30 | } 31 | if v, e := l.ParseLoadAvg(chunks[2]); e == nil { 32 | c.Add("Load15m", v) 33 | } 34 | } 35 | return 36 | } 37 | 38 | func (l *LoadAvg) ParseLoadAvg(s string) (i int64, e error) { 39 | if f, e := strconv.ParseFloat(s, 64); e == nil { 40 | i = int64(f * 100) 41 | } 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /loadavg_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestLoadAvg(t *testing.T) { 10 | Convey("TestLoadAvg", t, func() { 11 | mh := &MetricHandler{} 12 | stats, _ := mh.Collect(&LoadAvg{}) 13 | So(len(stats), ShouldEqual, 3) 14 | So(stats[0].Value, ShouldEqual, 3) 15 | So(stats[1].Value, ShouldEqual, 7) 16 | So(stats[2].Value, ShouldEqual, 8) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | var logger = log.New(os.Stderr, "", 0) 11 | 12 | func logError(e error) { 13 | logger.Printf("ERROR: %q", e) 14 | } 15 | 16 | func debugStream() io.Writer { 17 | if os.Getenv("DEBUG") == "true" { 18 | return os.Stderr 19 | } 20 | return ioutil.Discard 21 | } 22 | 23 | var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile) 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "os" 4 | 5 | type Config struct { 6 | OpenTSDBUrl string `json:"opentsdb_url"` 7 | } 8 | 9 | var OUTPUTS = []string{"graphite", "opentsdb", "amqp","influxdb"} 10 | 11 | func processCollector(mh *MetricHandler, output *OutputHandler, c MetricCollector) (e error) { 12 | all, e := mh.Collect(c) 13 | if e != nil { 14 | return 15 | } 16 | e = output.WriteMetrics(all) 17 | return 18 | } 19 | 20 | const ( 21 | KEYS = "keys" 22 | HELP = "help" 23 | 24 | HOSTNAME = "hostname" 25 | 26 | OPENTSDB = "opentsdb" 27 | GRAPHITE = "graphite" 28 | AMQP = "amqp" 29 | INFLUXDB = "influxdb" 30 | ) 31 | 32 | func init() { 33 | parser.Add(HELP, "true", "Print this usage page") 34 | parser.Add(KEYS, "true", "Only list all known keys") 35 | parser.AddKey(OPENTSDB, "Report metrics to OpenTSDB host.\nEXAMPLE: opentsdb.host:4242") 36 | parser.AddKey(AMQP, "Report metrics to AMQP host.\nEXAMPLE: amqp.host:5672") 37 | parser.AddKey(INFLUXDB, "Report metrics to InfluxDB host.\nEXAMPLE: user:password@influxdb.host:8086/database") 38 | parser.AddKey(GRAPHITE, "Report metrics to Graphite host.\nEXAMPLE: graphite.host:2003") 39 | parser.AddKey(HOSTNAME, "Hostname to be used for tagging. If blank the local hostname is used") 40 | } 41 | 42 | func main() { 43 | if e := parser.ProcessAll(os.Args[1:]); e != nil { 44 | logError(e) 45 | os.Exit(1) 46 | } 47 | 48 | output := &OutputHandler{ 49 | OpenTSDBAddress: parser.Get(OPENTSDB), 50 | GraphiteAddress: parser.Get(GRAPHITE), 51 | AmqpAddress: parser.Get(AMQP), 52 | InfluxDBAddress: parser.Get(INFLUXDB), 53 | } 54 | collectors := map[string]MetricCollector{ 55 | CPU: &Cpu{}, // migrated 56 | LOADAVG: &LoadAvg{}, // migrated 57 | MEMORY: &Memory{}, // migrated 58 | NET: &Net{}, // migrated 59 | FREE: &Free{}, // migrated 60 | DF: &Df{}, // migrated 61 | PROCESSES: &Processes{}, 62 | FILES: &Files{}, 63 | ELASTICSEARCH: &ElasticSearch{Url: parser.Get(ELASTICSEARCH)}, 64 | REDIS: &Redis{Address: parser.Get(REDIS)}, 65 | DISK: &Disk{}, 66 | RIAK: &Riak{Address: parser.Get(RIAK)}, 67 | PGBOUNCER: &PgBouncer{Address: parser.Get(PGBOUNCER)}, 68 | POSTGRES: &PostgreSQLStats{Uri: parser.Get(POSTGRES)}, 69 | NGINX: &Nginx{Address: parser.Get(NGINX)}, 70 | SURICATA: &Suricata{SocketName: parser.Get(SURICATA)}, 71 | } 72 | 73 | mh := &MetricHandler{} 74 | 75 | if parser.Get(HELP) == "true" || len(parser.Values) == 0 { 76 | parser.PrintDefaults() 77 | os.Exit(0) 78 | } 79 | 80 | for key, _ := range parser.Values { 81 | if collector, ok := collectors[key]; ok { 82 | if e := processCollector(mh, output, collector); e != nil { 83 | logger.Print("ERROR: processong collector", key, e.Error()) 84 | } 85 | } else { 86 | isOutput := false 87 | for _, out := range OUTPUTS { 88 | if out == key { 89 | isOutput = true 90 | break 91 | } 92 | } 93 | if !isOutput { 94 | logger.Printf("ERROR: no collector found for %q", key) 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "log" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const MEMORY = "memory" 12 | 13 | func init() { 14 | parser.Add(MEMORY, "true", "Collect memory metrics") 15 | } 16 | 17 | type Memory struct { 18 | MemTotal int64 19 | MemFree int64 20 | Buffers int64 21 | Cached int64 22 | SwapCached int64 23 | Active int64 24 | Inactive int64 25 | ActiveAnon int64 26 | InactiveAnon int64 27 | ActiveFile int64 28 | InactiveFile int64 29 | Unevictable int64 30 | Mlocked int64 31 | SwapTotal int64 32 | SwapFree int64 33 | Dirty int64 34 | Writeback int64 35 | AnonPages int64 36 | Mapped int64 37 | Shmem int64 38 | Slab int64 39 | SReclaimable int64 40 | SUnreclaim int64 41 | KernelStack int64 42 | PageTables int64 43 | NFS_Unstable int64 44 | Bounce int64 45 | WritebackTmp int64 46 | CommitLimit int64 47 | Committed_AS int64 48 | VmallocTotal int64 49 | VmallocUsed int64 50 | VmallocChunk int64 51 | HardwareCorrupted int64 52 | AnonHugePages int64 53 | HugePages_Total int64 54 | HugePages_Free int64 55 | HugePages_Rsvd int64 56 | HugePages_Surp int64 57 | Hugepagesize int64 58 | DirectMap4k int64 59 | DirectMap2M int64 60 | } 61 | 62 | func (m *Memory) Prefix() string { 63 | return "memory" 64 | } 65 | 66 | func (m *Memory) Load(b []byte) error { 67 | scanner := bufio.NewScanner(bytes.NewReader(b)) 68 | for scanner.Scan() { 69 | line := scanner.Text() 70 | parts := strings.SplitN(line, ":", 2) 71 | if len(parts) == 2 { 72 | key := parts[0] 73 | valueString := strings.Fields(parts[1])[0] 74 | value, e := strconv.ParseInt(valueString, 10, 64) 75 | if e != nil { 76 | log.Printf("error parsing %q to int64: %s", valueString, e) 77 | continue 78 | } 79 | switch key { 80 | case "MemTotal": 81 | m.MemTotal = value 82 | case "MemFree": 83 | m.MemFree = value 84 | case "Buffers": 85 | m.Buffers = value 86 | case "Cached": 87 | m.Cached = value 88 | case "SwapCached": 89 | m.SwapCached = value 90 | case "Active": 91 | m.Active = value 92 | case "Inactive": 93 | m.Inactive = value 94 | case "Active(anon)": 95 | m.ActiveAnon = value 96 | case "Inactive(anon)": 97 | m.InactiveAnon = value 98 | case "Active(file)": 99 | m.ActiveFile = value 100 | case "Inactive(file)": 101 | m.InactiveFile = value 102 | case "Unevictable": 103 | m.Unevictable = value 104 | case "Mlocked": 105 | m.Mlocked = value 106 | case "SwapTotal": 107 | m.SwapTotal = value 108 | case "SwapFree": 109 | m.SwapFree = value 110 | case "Dirty": 111 | m.Dirty = value 112 | case "Writeback": 113 | m.Writeback = value 114 | case "AnonPages": 115 | m.AnonPages = value 116 | case "Mapped": 117 | m.Mapped = value 118 | case "Shmem": 119 | m.Shmem = value 120 | case "Slab": 121 | m.Slab = value 122 | case "SReclaimable": 123 | m.SReclaimable = value 124 | case "SUnreclaim": 125 | m.SUnreclaim = value 126 | case "KernelStack": 127 | m.KernelStack = value 128 | case "PageTables": 129 | m.PageTables = value 130 | case "NFS_Unstable": 131 | m.NFS_Unstable = value 132 | case "Bounce": 133 | m.Bounce = value 134 | case "WritebackTmp": 135 | m.WritebackTmp = value 136 | case "CommitLimit": 137 | m.CommitLimit = value 138 | case "Committed_AS": 139 | m.Committed_AS = value 140 | case "VmallocTotal": 141 | m.VmallocTotal = value 142 | case "VmallocUsed": 143 | m.VmallocUsed = value 144 | case "VmallocChunk": 145 | m.VmallocChunk = value 146 | case "HardwareCorrupted": 147 | m.HardwareCorrupted = value 148 | case "AnonHugePages": 149 | m.AnonHugePages = value 150 | case "HugePages_Total": 151 | m.HugePages_Total = value 152 | case "HugePages_Free": 153 | m.HugePages_Free = value 154 | case "HugePages_Rsvd": 155 | m.HugePages_Rsvd = value 156 | case "HugePages_Surp": 157 | m.HugePages_Surp = value 158 | case "Hugepagesize": 159 | m.Hugepagesize = value 160 | case "DirectMap4k": 161 | m.DirectMap4k = value 162 | case "DirectMap2M": 163 | m.DirectMap2M = value 164 | default: 165 | log.Printf("ERROR: key=%q unknown", key) 166 | } 167 | } 168 | } 169 | return scanner.Err() 170 | } 171 | 172 | func (m *Memory) Collect(c *MetricsCollection) (e error) { 173 | s := ReadProcFile("meminfo") 174 | e = m.Load([]byte(s)) 175 | if e != nil { 176 | return e 177 | } 178 | 179 | values := map[string]int64{ 180 | "mem_total": m.MemTotal, 181 | "mem_free": m.MemFree, 182 | "buffers": m.Buffers, 183 | "cached": m.Cached, 184 | "swap_cached": m.SwapCached, 185 | "active": m.Active, 186 | "inactive": m.Inactive, 187 | "active_anon": m.ActiveAnon, 188 | "inactive_anon": m.InactiveAnon, 189 | "active_file": m.ActiveFile, 190 | "inactive_file": m.InactiveFile, 191 | "unevictable": m.Unevictable, 192 | "mlocked": m.Mlocked, 193 | "swap_total": m.SwapTotal, 194 | "swap_free": m.SwapFree, 195 | "dirty": m.Dirty, 196 | "writeback": m.Writeback, 197 | "anon_pages": m.AnonPages, 198 | "mapped": m.Mapped, 199 | "shmem": m.Shmem, 200 | "slab": m.Slab, 201 | "s_reclaimable": m.SReclaimable, 202 | "s_unreclaim": m.SUnreclaim, 203 | "kernel_stack": m.KernelStack, 204 | "page_tables": m.PageTables, 205 | "nfs_unstable": m.NFS_Unstable, 206 | "bounce": m.Bounce, 207 | "writeback_tmp": m.WritebackTmp, 208 | "commit_limit": m.CommitLimit, 209 | "committed_as": m.Committed_AS, 210 | "vmalloc_total": m.VmallocTotal, 211 | "vmalloc_used": m.VmallocUsed, 212 | "vmalloc_chunk": m.VmallocChunk, 213 | "hardware_corrupted": m.HardwareCorrupted, 214 | "anon_huge_pages": m.AnonHugePages, 215 | "hugepages_total": m.HugePages_Total, 216 | "hugepages_free": m.HugePages_Free, 217 | "hugepages_rsvd": m.HugePages_Rsvd, 218 | "hugepages_surp": m.HugePages_Surp, 219 | "hugepagesize": m.Hugepagesize, 220 | "direct_map4k": m.DirectMap4k, 221 | "direct_map2_m": m.DirectMap2M, 222 | } 223 | 224 | for k, v := range values { 225 | c.Add(k, v) 226 | } 227 | return nil 228 | } 229 | -------------------------------------------------------------------------------- /memory_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "sort" 6 | "testing" 7 | 8 | . "github.com/smartystreets/goconvey/convey" 9 | ) 10 | 11 | func aggregateStats(stats []*Metric) map[string]int64 { 12 | agg := map[string]int64{} 13 | for _, stat := range stats { 14 | agg[stat.Key] = stat.Value 15 | } 16 | return agg 17 | } 18 | 19 | func TestMemory(t *testing.T) { 20 | Convey("Memory", t, func() { 21 | m := &Memory{} 22 | mh := &MetricHandler{} 23 | stats, _ := mh.Collect(m) 24 | 25 | So(len(stats) > 10, ShouldBeTrue) 26 | So(len(stats), ShouldEqual, 42) 27 | 28 | sort.Sort(stats) 29 | So(stats[0].Key, ShouldEqual, "memory.active") 30 | So(stats[0].Value, ShouldEqual, 61740) 31 | 32 | agg := aggregateStats(stats) 33 | 34 | So(agg["memory.committed_as"], ShouldEqual, 350856) 35 | 36 | Convey("Load", func() { 37 | b := mustRead(t, "proc/meminfo") 38 | m := &Memory{} 39 | e := m.Load(b) 40 | So(e, ShouldBeNil) 41 | So(m.MemTotal, ShouldEqual, 502976) 42 | So(m.Cached, ShouldEqual, 31436) 43 | }) 44 | }) 45 | } 46 | 47 | func mustRead(t *testing.T, p string) []byte { 48 | b, e := ioutil.ReadFile("fixtures/" + p) 49 | if e != nil { 50 | t.Fatal(e) 51 | } 52 | return b 53 | } 54 | -------------------------------------------------------------------------------- /metric_handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type MetricHandler struct { 4 | } 5 | 6 | type MetricCollector interface { 7 | Prefix() string 8 | Collect(*MetricsCollection) error 9 | } 10 | 11 | func (h *MetricHandler) Collect(c MetricCollector) (Metrics, error) { 12 | mc := &MetricsCollection{Prefix: c.Prefix()} 13 | e := c.Collect(mc) 14 | return mc.Metrics, e 15 | } 16 | -------------------------------------------------------------------------------- /metrics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | type MetricsMap map[string]int64 10 | 11 | type Metrics []*Metric 12 | 13 | func (list Metrics) Len() int { 14 | return len(list) 15 | } 16 | 17 | func (list Metrics) Swap(a, b int) { 18 | list[a], list[b] = list[b], list[a] 19 | } 20 | 21 | func (list Metrics) Less(a, b int) bool { 22 | if list[a].Key == list[b].Key { 23 | return flattenTags(list[a].Tags) < flattenTags(list[b].Tags) 24 | } else { 25 | return list[a].Key < list[b].Key 26 | } 27 | } 28 | 29 | func flattenTags(tags map[string]string) string { 30 | out := []string{} 31 | for k, v := range tags { 32 | out = append(out, fmt.Sprintf("%s=%s", k, v)) 33 | } 34 | sort.Strings(out) 35 | return strings.Join(out, " ") 36 | } 37 | -------------------------------------------------------------------------------- /metrics_collection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "strconv" 4 | 5 | type MetricsCollection struct { 6 | Prefix string 7 | Metrics Metrics 8 | Mapping map[string]string 9 | } 10 | 11 | func (m *MetricsCollection) AddSingularMappings(mappings []string) { 12 | for _, k := range mappings { 13 | m.AddSingularMapping(k) 14 | } 15 | } 16 | 17 | func (m *MetricsCollection) AddSingularMapping(from string) { 18 | m.AddMapping(from, from) 19 | } 20 | 21 | func (m *MetricsCollection) AddMapping(from, to string) { 22 | if m.Mapping == nil { 23 | m.Mapping = map[string]string{} 24 | } 25 | m.Mapping[from] = to 26 | } 27 | 28 | func (m *MetricsCollection) AddWithTags(key string, v int64, tags map[string]string) (e error) { 29 | if realKey, ok := m.Mapping[key]; ok { 30 | key = realKey 31 | } 32 | m.Metrics = append(m.Metrics, &Metric{Key: m.Prefix + "." + key, Value: v, Tags: tags}) 33 | return 34 | } 35 | 36 | func (m *MetricsCollection) MustAddString(key, value string) error { 37 | i, e := strconv.ParseInt(value, 10, 64) 38 | if e != nil { 39 | return e 40 | } 41 | return m.Add(key, i) 42 | } 43 | 44 | func (m *MetricsCollection) Add(key string, v int64) (e error) { 45 | return m.AddWithTags(key, v, map[string]string{}) 46 | } 47 | -------------------------------------------------------------------------------- /metrix.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | ) 7 | 8 | type Metrix struct { 9 | Collectors map[string]*MetricCollector 10 | } 11 | 12 | func (self *Metrix) AddCollector(key string, col *MetricCollector) { 13 | self.Collectors[key] = col 14 | } 15 | 16 | func (m *Metrix) ProcRoot() (s string) { 17 | return os.Getenv("PROC_ROOT") 18 | } 19 | 20 | func (m *Metrix) ReadProcFile(path string) (r string) { 21 | if b, e := ioutil.ReadFile(m.ProcRoot() + "/proc/" + path); e == nil { 22 | r = string(b) 23 | } 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /metrix/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go get . 3 | -------------------------------------------------------------------------------- /metrix/dbg.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func debugStream() io.Writer { 11 | if os.Getenv("DEBUG") == "true" { 12 | return os.Stderr 13 | } 14 | return ioutil.Discard 15 | } 16 | 17 | var dbg = log.New(debugStream(), "[DEBUG] ", log.Lshortfile) 18 | -------------------------------------------------------------------------------- /metrix/df.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | 7 | "os/exec" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type Disk struct { 13 | Filesystem string `json:"filesystem,omitempty"` 14 | Blocks int64 `json:"blocks,omitempty"` 15 | Used int64 `json:"used,omitempty"` 16 | Available int64 `json:"available,omitempty"` 17 | Use int `json:"use,omitempty"` 18 | MountedOn string `json:"mounted_on,omitempty"` 19 | } 20 | 21 | func LoadDisks() ([]*Disk, error) { 22 | defer benchmark("load disks")() 23 | c := exec.Command("df", "-k") 24 | out, e := c.StdoutPipe() 25 | if e != nil { 26 | return nil, e 27 | } 28 | defer out.Close() 29 | e = c.Start() 30 | if e != nil { 31 | return nil, e 32 | } 33 | return ParseDf(out) 34 | } 35 | 36 | const ( 37 | fieldDiskFilesystem = iota 38 | fieldDiskBlocks 39 | fieldDiskUsed 40 | fieldDiskAvailable 41 | fieldDiskUse 42 | fieldDiskMountedOn 43 | ) 44 | 45 | func ParseDf(in io.Reader) ([]*Disk, error) { 46 | scanner := bufio.NewScanner(in) 47 | disks := []*Disk{} 48 | for scanner.Scan() { 49 | fields := strings.Fields(scanner.Text()) 50 | if fields[0] == "Filesystem" { 51 | // header 52 | continue 53 | } 54 | if len(fields) != 6 { 55 | dbg.Printf("expected 6 fields, got %d", len(fields)) 56 | continue 57 | } 58 | d := &Disk{} 59 | var e error 60 | e = func() error { 61 | for i, v := range fields { 62 | switch i { 63 | case fieldDiskFilesystem: 64 | d.Filesystem = v 65 | case fieldDiskBlocks: 66 | if d.Blocks, e = parseInt64(v); e != nil { 67 | return e 68 | } 69 | case fieldDiskUsed: 70 | if d.Used, e = parseInt64(v); e != nil { 71 | return e 72 | } 73 | case fieldDiskAvailable: 74 | if d.Available, e = parseInt64(v); e != nil { 75 | return e 76 | } 77 | case fieldDiskUse: 78 | if strings.HasSuffix(v, "%") { 79 | d.Use, e = strconv.Atoi(strings.TrimSuffix(v, "%")) 80 | if e != nil { 81 | return e 82 | } 83 | } 84 | case fieldDiskMountedOn: 85 | d.MountedOn = v 86 | } 87 | } 88 | return nil 89 | }() 90 | if e != nil { 91 | logger.Printf("ERROR: %q", e) 92 | } else { 93 | disks = append(disks, d) 94 | } 95 | } 96 | return disks, scanner.Err() 97 | } 98 | 99 | func parseInt64(s string) (int64, error) { 100 | return strconv.ParseInt(s, 10, 64) 101 | } 102 | -------------------------------------------------------------------------------- /metrix/df_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestParseDf(t *testing.T) { 11 | expect := expect.New(t) 12 | f, e := os.Open("fixtures/df.txt") 13 | expect(e).ToBeNil() 14 | defer f.Close() 15 | 16 | disks, e := ParseDf(f) 17 | expect(e).ToBeNil() 18 | expect(disks).ToHaveLength(7) 19 | expect(disks[0].Filesystem).ToEqual("/dev/xvda1") 20 | expect(disks[0].Blocks).ToEqual(51466360) 21 | expect(disks[0].Used).ToEqual(32923572) 22 | expect(disks[0].Available).ToEqual(16346296) 23 | expect(disks[0].Use).ToEqual(67) 24 | expect(disks[0].MountedOn).ToEqual("/") 25 | } 26 | -------------------------------------------------------------------------------- /metrix/ec2metadata.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | func LoadEc2Metadata() (*Ec2Metadata, error) { 11 | c := exec.Command("ec2metadata") 12 | out, err := c.StdoutPipe() 13 | if err != nil { 14 | return nil, err 15 | } 16 | defer out.Close() 17 | err = c.Start() 18 | if err != nil { 19 | return nil, err 20 | } 21 | em := &Ec2Metadata{} 22 | if err := em.Load(out); err != nil { 23 | return nil, err 24 | } 25 | return em, nil 26 | } 27 | 28 | type Ec2Metadata struct { 29 | AvailabilityZone string `json:"availability_zone,omitempty"` 30 | InstanceId string `json:"instance_id,omitempty"` 31 | InstanceType string `json:"instance_type,omitempty"` 32 | AmiId string `json:"ami_id,omitempty"` 33 | } 34 | 35 | func (e *Ec2Metadata) Load(in io.Reader) error { 36 | scanner := bufio.NewScanner(in) 37 | for scanner.Scan() { 38 | parts := strings.SplitN(scanner.Text(), ": ", 2) 39 | if len(parts) == 2 { 40 | key, value := parts[0], parts[1] 41 | switch key { 42 | case "availability-zone": 43 | e.AvailabilityZone = value 44 | case "instance-id": 45 | e.InstanceId = value 46 | case "ami-id": 47 | e.AmiId = value 48 | } 49 | } 50 | } 51 | return scanner.Err() 52 | } 53 | -------------------------------------------------------------------------------- /metrix/ec2metadata_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestEc2Metadata(t *testing.T) { 11 | expect := expect.New(t) 12 | f, err := os.Open("fixtures/ec2metadata.txt") 13 | expect(err).ToBeNil() 14 | defer f.Close() 15 | 16 | em := &Ec2Metadata{} 17 | err = em.Load(f) 18 | expect(err).ToBeNil() 19 | expect(em.AvailabilityZone).ToEqual("eu-west-1a") 20 | expect(em.InstanceId).ToEqual("i-72d0fa30") 21 | expect(em.AmiId).ToEqual("ami-905e81e7") 22 | } 23 | -------------------------------------------------------------------------------- /metrix/fixtures/df.txt: -------------------------------------------------------------------------------- 1 | Filesystem 1K-blocks Used Available Use% Mounted on 2 | /dev/xvda1 51466360 32923572 16346296 67% / 3 | none 4 0 4 0% /sys/fs/cgroup 4 | udev 3824544 8 3824536 1% /dev 5 | tmpfs 765952 352 765600 1% /run 6 | none 5120 0 5120 0% /run/lock 7 | none 3829748 0 3829748 0% /run/shm 8 | none 102400 0 102400 0% /run/user 9 | -------------------------------------------------------------------------------- /metrix/fixtures/diskstats.txt: -------------------------------------------------------------------------------- 1 | 1 0 ram0 0 0 0 0 0 0 0 0 0 0 0 2 | 1 1 ram1 0 0 0 0 0 0 0 0 0 0 0 3 | 1 2 ram2 0 0 0 0 0 0 0 0 0 0 0 4 | 1 3 ram3 0 0 0 0 0 0 0 0 0 0 0 5 | 1 4 ram4 0 0 0 0 0 0 0 0 0 0 0 6 | 1 5 ram5 0 0 0 0 0 0 0 0 0 0 0 7 | 1 6 ram6 0 0 0 0 0 0 0 0 0 0 0 8 | 1 7 ram7 0 0 0 0 0 0 0 0 0 0 0 9 | 1 8 ram8 0 0 0 0 0 0 0 0 0 0 0 10 | 1 9 ram9 0 0 0 0 0 0 0 0 0 0 0 11 | 1 10 ram10 0 0 0 0 0 0 0 0 0 0 0 12 | 1 11 ram11 0 0 0 0 0 0 0 0 0 0 0 13 | 1 12 ram12 0 0 0 0 0 0 0 0 0 0 0 14 | 1 13 ram13 0 0 0 0 0 0 0 0 0 0 0 15 | 1 14 ram14 0 0 0 0 0 0 0 0 0 0 0 16 | 1 15 ram15 0 0 0 0 0 0 0 0 0 0 0 17 | 7 0 loop0 0 0 0 0 0 0 0 0 0 0 0 18 | 7 1 loop1 0 0 0 0 0 0 0 0 0 0 0 19 | 7 2 loop2 0 0 0 0 0 0 0 0 0 0 0 20 | 7 3 loop3 0 0 0 0 0 0 0 0 0 0 0 21 | 7 4 loop4 0 0 0 0 0 0 0 0 0 0 0 22 | 7 5 loop5 0 0 0 0 0 0 0 0 0 0 0 23 | 7 6 loop6 0 0 0 0 0 0 0 0 0 0 0 24 | 7 7 loop7 0 0 0 0 0 0 0 0 0 0 0 25 | 2 0 fd0 0 0 0 0 0 0 0 0 0 0 0 26 | 11 0 sr0 0 0 0 0 0 0 0 0 0 0 0 27 | 8 0 sda 19082 3880 738216 7548 24972 23399 2094164 11708 0 5980 19048 28 | 8 1 sda1 614 3840 33050 168 7 1 28 0 0 132 168 29 | 8 2 sda2 2 0 4 0 0 0 0 0 0 0 0 30 | 8 5 sda5 18281 40 703682 7348 24965 23398 2094136 11708 0 5948 18848 31 | 252 0 dm-0 18067 0 701250 7396 48368 0 2094136 21208 0 5940 28604 32 | 252 1 dm-1 224 0 1792 36 0 0 0 0 0 36 36 33 | -------------------------------------------------------------------------------- /metrix/fixtures/ec2metadata.txt: -------------------------------------------------------------------------------- 1 | ami-id: ami-905e81e7 2 | ami-launch-index: 0 3 | ami-manifest-path: (unknown) 4 | ancestor-ami-ids: unavailable 5 | availability-zone: eu-west-1a 6 | block-device-mapping: ami 7 | ephemeral0 8 | root 9 | instance-action: none 10 | instance-id: i-72d0fa30 11 | instance-type: m3.xlarge 12 | local-hostname: ip-172-31-2-96.eu-west-1.compute.internal 13 | local-ipv4: 172.31.2.96 14 | kernel-id: aki-52a34525 15 | mac: unavailable 16 | profile: default-paravirtual 17 | product-codes: unavailable 18 | public-hostname: unavailable 19 | public-ipv4: 54.72.42.158 20 | public-keys: ['ssh-rsa somekey rmorgan'] 21 | ramdisk-id: unavailable 22 | reserveration-id: unavailable 23 | security-groups: some-security-group 24 | user-data: unavailable 25 | -------------------------------------------------------------------------------- /metrix/fixtures/loadavg.txt: -------------------------------------------------------------------------------- 1 | 0.00 0.01 0.05 1/258 8518 2 | -------------------------------------------------------------------------------- /metrix/fixtures/meminfo.txt: -------------------------------------------------------------------------------- 1 | MemTotal: 4041052 kB 2 | MemFree: 3383412 kB 3 | Buffers: 66848 kB 4 | Cached: 430620 kB 5 | SwapCached: 0 kB 6 | Active: 349876 kB 7 | Inactive: 170268 kB 8 | Active(anon): 23068 kB 9 | Inactive(anon): 516 kB 10 | Active(file): 326808 kB 11 | Inactive(file): 169752 kB 12 | Unevictable: 0 kB 13 | Mlocked: 0 kB 14 | SwapTotal: 1044476 kB 15 | SwapFree: 1044476 kB 16 | Dirty: 12 kB 17 | Writeback: 0 kB 18 | AnonPages: 22708 kB 19 | Mapped: 6676 kB 20 | Shmem: 904 kB 21 | Slab: 67472 kB 22 | SReclaimable: 45244 kB 23 | SUnreclaim: 22228 kB 24 | KernelStack: 2536 kB 25 | PageTables: 2452 kB 26 | NFS_Unstable: 0 kB 27 | Bounce: 0 kB 28 | WritebackTmp: 0 kB 29 | CommitLimit: 3065000 kB 30 | Committed_AS: 52288 kB 31 | VmallocTotal: 34359738367 kB 32 | VmallocUsed: 183504 kB 33 | VmallocChunk: 34359522812 kB 34 | HardwareCorrupted: 0 kB 35 | AnonHugePages: 8192 kB 36 | HugePages_Total: 0 37 | HugePages_Free: 0 38 | HugePages_Rsvd: 0 39 | HugePages_Surp: 0 40 | Hugepagesize: 2048 kB 41 | DirectMap4k: 69504 kB 42 | DirectMap2M: 3076096 kB 43 | DirectMap1G: 3145728 kB 44 | -------------------------------------------------------------------------------- /metrix/fixtures/proc_cmdline.txt: -------------------------------------------------------------------------------- 1 | /opt/postgresql-9.3.5/bin/postgres-D/data/postgres -------------------------------------------------------------------------------- /metrix/fixtures/proc_stat.txt: -------------------------------------------------------------------------------- 1 | 1153 (cron) S 1 1153 1153 0 -1 1077960768 32334 4614275 0 207 17 115 3549 2293 20 0 1 0 1301 24223744 262 18446744073709551615 1 1 0 0 0 0 0 0 65537 18446744073709551615 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | -------------------------------------------------------------------------------- /metrix/fixtures/stat.txt: -------------------------------------------------------------------------------- 1 | cpu 13586 1381 32308 62395759 762 20203 0 0 0 0 2 | cpu0 4488 363 23009 15584731 201 2108 0 0 0 0 3 | cpu1 3308 358 4899 15589446 134 17605 0 0 0 0 4 | cpu2 2784 556 2647 15609981 146 389 0 0 0 0 5 | cpu3 3005 102 1751 15611600 280 99 0 0 0 0 6 | intr 2925373 50 10 0 0 0 0 2 0 1 0 0 0 152 0 0 0 0 48407 156 193070 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 76243 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 | ctxt 4087538 8 | btime 1412216124 9 | processes 20491 10 | procs_running 1 11 | procs_blocked 0 12 | softirq 4378358 1 1972606 31184 191878 126092 0 63 1903441 151 152942 13 | -------------------------------------------------------------------------------- /metrix/fixtures/statm.txt: -------------------------------------------------------------------------------- 1 | 5914 262 198 10 0 99 0 2 | -------------------------------------------------------------------------------- /metrix/load.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | fieldLoad1 = iota 14 | fieldLoad5 15 | fieldLoad15 16 | fieldLoadEntities 17 | fieldLoadMostRecentPid 18 | ) 19 | 20 | type LoadAvg struct { 21 | Min1 float64 `json:"min1,omitempty"` 22 | Min5 float64 `json:"min5,omitempty"` 23 | Min15 float64 `json:"min15,omitempty"` 24 | RunnableEntities int64 `json:"runnable_entities,omitempty"` 25 | ExistingEntities int64 `json:"existing_entities,omitempty"` 26 | MostRecentPid int `json:"most_recent_pid,omitempty"` 27 | } 28 | 29 | func LoadLoadAvg() (*LoadAvg, error) { 30 | defer benchmark("load loadavg")() 31 | f, e := os.Open("/proc/loadavg") 32 | if e != nil { 33 | return nil, e 34 | } 35 | defer f.Close() 36 | l := &LoadAvg{} 37 | return l, l.Load(f) 38 | } 39 | 40 | func (l *LoadAvg) Load(in io.Reader) error { 41 | b, e := ioutil.ReadAll(in) 42 | if e != nil { 43 | return e 44 | } 45 | fields := strings.Fields(string(b)) 46 | if len(fields) != 5 { 47 | return fmt.Errorf("expected 5 fields, found %d", len(fields)) 48 | } 49 | l.Min1, e = strconv.ParseFloat(fields[fieldLoad1], 64) 50 | if e != nil { 51 | return e 52 | } 53 | l.Min5, e = strconv.ParseFloat(fields[fieldLoad5], 64) 54 | if e != nil { 55 | return e 56 | } 57 | l.Min15, e = strconv.ParseFloat(fields[fieldLoad15], 64) 58 | if e != nil { 59 | return e 60 | } 61 | parts := strings.Split(fields[fieldLoadEntities], "/") 62 | if len(parts) == 2 { 63 | l.RunnableEntities, e = parseInt64(parts[0]) 64 | if e != nil { 65 | return e 66 | } 67 | l.ExistingEntities, e = parseInt64(parts[1]) 68 | if e != nil { 69 | return e 70 | } 71 | } 72 | l.MostRecentPid, e = strconv.Atoi(fields[fieldLoadMostRecentPid]) 73 | return e 74 | } 75 | -------------------------------------------------------------------------------- /metrix/load_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestLoadAVG(t *testing.T) { 11 | expect := expect.New(t) 12 | f, e := os.Open("fixtures/loadavg.txt") 13 | expect(e).ToBeNil() 14 | defer f.Close() 15 | l := &LoadAvg{} 16 | expect(l.Load(f)).ToBeNil() 17 | expect(l.Min1).ToEqual(0.00) 18 | expect(l.Min5).ToEqual(0.01) 19 | expect(l.Min15).ToEqual(0.05) 20 | expect(l.RunnableEntities).ToEqual(1) 21 | expect(l.ExistingEntities).ToEqual(258) 22 | expect(l.MostRecentPid).ToEqual(8518) 23 | } 24 | -------------------------------------------------------------------------------- /metrix/logger.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | var logger = log.New(os.Stderr, "", 0) 9 | -------------------------------------------------------------------------------- /metrix/lsof.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "sort" 7 | ) 8 | 9 | type OpenFiles struct { 10 | Files []*File 11 | } 12 | 13 | type File struct { 14 | FileAccessMode string 15 | FileName string 16 | FileType string 17 | FileLockStatus string 18 | FileDescriptor string 19 | FileInodeNumber string 20 | FileDeviceNumber string 21 | LinkCount string 22 | FileSize string 23 | FileFlags string 24 | ParentProcessId string 25 | ProcessLoginName string 26 | ProcessId string 27 | ProcessGroupId string 28 | ProcessUserId string 29 | ProcessCommandName string 30 | FileOffset string 31 | FileDeciceCharacterCode string 32 | TcpInformation string 33 | ProtocolName string 34 | TaskId string 35 | } 36 | 37 | const ( 38 | fileAccessMode = iota + 1 39 | fileName 40 | fileType 41 | fileLockStatus 42 | fileDescriptor 43 | fileInodeNumber 44 | fileDeviceNumber 45 | linkCount 46 | fileSize 47 | fileFlags 48 | parentProcessId 49 | processLoginName 50 | processId 51 | processGroupId 52 | processUserId 53 | processCommandName 54 | fileOffset 55 | fileDeciceCharacterCode 56 | tcpInformation 57 | protocolName 58 | taskId 59 | ) 60 | 61 | var fileMapping = map[string]int{ 62 | "a": fileAccessMode, 63 | "n": fileName, 64 | "t": fileType, 65 | "l": fileLockStatus, 66 | "f": fileDescriptor, 67 | "i": fileInodeNumber, 68 | "D": fileDeviceNumber, 69 | "k": linkCount, 70 | "s": fileSize, 71 | "G": fileFlags, 72 | "R": parentProcessId, 73 | "L": processLoginName, 74 | "p": processId, 75 | "g": processGroupId, 76 | "u": processUserId, 77 | "c": processCommandName, 78 | "o": fileOffset, 79 | "d": fileDeciceCharacterCode, 80 | "T": tcpInformation, 81 | "P": protocolName, 82 | "K": taskId, 83 | } 84 | 85 | // use this with "lsof -F" 86 | func (r *OpenFiles) Load(in io.Reader) error { 87 | scanner := bufio.NewScanner(in) 88 | stats := Map{} 89 | var f *File 90 | for scanner.Scan() { 91 | line := scanner.Text() 92 | first := line[0] 93 | rest := line[1:] 94 | switch fileMapping[string(first)] { 95 | case processId: 96 | f = &File{ProcessId: rest} 97 | r.Files = append(r.Files, f) 98 | case parentProcessId: 99 | f.ParentProcessId = rest 100 | case fileAccessMode: 101 | f.FileAccessMode = rest 102 | case fileName: 103 | f.FileName = rest 104 | case fileType: 105 | f.FileType = rest 106 | case fileLockStatus: 107 | f.FileLockStatus = rest 108 | case fileDescriptor: 109 | f.FileDescriptor = rest 110 | case fileInodeNumber: 111 | f.FileInodeNumber = rest 112 | case fileDeviceNumber: 113 | f.FileDeviceNumber = rest 114 | case linkCount: 115 | f.LinkCount = rest 116 | case fileSize: 117 | f.FileSize = rest 118 | case fileFlags: 119 | f.FileFlags = rest 120 | case processLoginName: 121 | f.ProcessLoginName = rest 122 | case processGroupId: 123 | f.ProcessGroupId = rest 124 | case processUserId: 125 | f.ProcessUserId = rest 126 | case processCommandName: 127 | f.ProcessCommandName = rest 128 | case fileOffset: 129 | f.FileOffset = rest 130 | case fileDeciceCharacterCode: 131 | f.FileDeciceCharacterCode = rest 132 | case tcpInformation: 133 | f.TcpInformation = rest 134 | case protocolName: 135 | f.ProtocolName = rest 136 | case taskId: 137 | f.TaskId = rest 138 | default: 139 | logger.Printf("do not know how to handle %q", string(first)) 140 | } 141 | } 142 | e := scanner.Err() 143 | if e != nil { 144 | return e 145 | } 146 | values := stats.Values() 147 | sort.Sort(sort.Reverse(values)) 148 | for _, v := range values { 149 | logger.Printf("%s: %d", v.Key, v.Value) 150 | } 151 | return nil 152 | } 153 | -------------------------------------------------------------------------------- /metrix/lsof_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestOpenFiles(t *testing.T) { 11 | expect := expect.New(t) 12 | f, e := os.Open("fixtures/lsof.txt") 13 | expect(e).ToBeNil() 14 | defer f.Close() 15 | 16 | files := &OpenFiles{} 17 | expect(files.Load(f)).ToBeNil() 18 | expect(files.Files).ToNotBeNil() 19 | expect(files.Files).ToHaveLength(265) 20 | 21 | selectFile := func(pid string) *File { 22 | for _, f := range files.Files { 23 | if f.ProcessId == pid { 24 | return f 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | file := selectFile("1") 31 | expect(file).ToNotBeNil() 32 | expect(file.FileInodeNumber).ToEqual("6964") 33 | expect(file.FileName).ToEqual("/dev/ptmx") 34 | } 35 | -------------------------------------------------------------------------------- /metrix/map.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import "sort" 4 | 5 | type Value struct { 6 | Key string 7 | Value int 8 | } 9 | 10 | type Map map[string]*Value 11 | 12 | type Values []*Value 13 | 14 | func (list Values) TopN(n int) Values { 15 | sort.Sort(sort.Reverse(list)) 16 | out := make(Values, 0, n) 17 | for _, v := range list { 18 | out = append(out, v) 19 | if len(out) >= n { 20 | break 21 | } 22 | } 23 | return out 24 | } 25 | 26 | func (list Values) Len() int { 27 | return len(list) 28 | } 29 | 30 | func (list Values) Swap(a, b int) { 31 | list[a], list[b] = list[b], list[a] 32 | } 33 | 34 | func (list Values) Less(a, b int) bool { 35 | return list[a].Value < list[b].Value 36 | } 37 | 38 | func (m Map) Values() Values { 39 | values := make(Values, 0, len(m)) 40 | for _, v := range m { 41 | values = append(values, v) 42 | } 43 | return values 44 | } 45 | 46 | func (m Map) Inc(key string) int { 47 | return m.IncBy(key, 1) 48 | } 49 | 50 | func (m Map) IncBy(key string, value int) int { 51 | if m[key] == nil { 52 | m[key] = &Value{Key: key} 53 | } 54 | m[key].Value += value 55 | return m[key].Value 56 | } 57 | -------------------------------------------------------------------------------- /metrix/meminfo.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func LoadMeminfo() (*Meminfo, error) { 11 | defer benchmark("load meminfo")() 12 | f, e := os.Open("/proc/meminfo") 13 | if e != nil { 14 | return nil, e 15 | } 16 | defer f.Close() 17 | m := &Meminfo{} 18 | return m, m.Load(f) 19 | } 20 | 21 | type Meminfo struct { 22 | MemTotal int64 `json:"mem_total,omitempty"` 23 | MemFree int64 `json:"mem_free,omitempty"` 24 | Buffers int64 `json:"buffers,omitempty"` 25 | Cached int64 `json:"cached,omitempty"` 26 | SwapCached int64 `json:"swap_cached,omitempty"` 27 | Active int64 `json:"active,omitempty"` 28 | Inactive int64 `json:"inactive,omitempty"` 29 | ActiveAnon int64 `json:"active_anon,omitempty"` 30 | InactiveAnon int64 `json:"inactive_anon,omitempty"` 31 | ActiveFile int64 `json:"active_file,omitempty"` 32 | InactiveFile int64 `json:"inactive_file,omitempty"` 33 | Unevictable int64 `json:"unevictable,omitempty"` 34 | Mlocked int64 `json:"mlocked,omitempty"` 35 | SwapTotal int64 `json:"swap_total,omitempty"` 36 | SwapFree int64 `json:"swap_free,omitempty"` 37 | Dirty int64 `json:"dirty,omitempty"` 38 | Writeback int64 `json:"writeback,omitempty"` 39 | AnonPages int64 `json:"anon_pages,omitempty"` 40 | Mapped int64 `json:"mapped,omitempty"` 41 | Shmem int64 `json:"shmem,omitempty"` 42 | Slab int64 `json:"slab,omitempty"` 43 | SReclaimable int64 `json:"s_reclaimable,omitempty"` 44 | SUnreclaim int64 `json:"s_unreclaim,omitempty"` 45 | KernelStack int64 `json:"kernel_stack,omitempty"` 46 | PageTables int64 `json:"page_tables,omitempty"` 47 | NFS_Unstable int64 `json:"nfs_unstable,omitempty"` 48 | Bounce int64 `json:"bounce,omitempty"` 49 | WritebackTmp int64 `json:"writeback_tmp,omitempty"` 50 | CommitLimit int64 `json:"commit_limit,omitempty"` 51 | Committed_AS int64 `json:"committed_as,omitempty"` 52 | VmallocTotal int64 `json:"vmalloc_total,omitempty"` 53 | VmallocUsed int64 `json:"vmalloc_used,omitempty"` 54 | VmallocChunk int64 `json:"vmalloc_chunk,omitempty"` 55 | HardwareCorrupted int64 `json:"hardware_corrupted,omitempty"` 56 | AnonHugePages int64 `json:"anon_huge_pages,omitempty"` 57 | HugePages_Total int64 `json:"huge_pages_total,omitempty"` 58 | HugePages_Free int64 `json:"huge_pages_free,omitempty"` 59 | HugePages_Rsvd int64 `json:"huge_pages_rsvd,omitempty"` 60 | HugePages_Surp int64 `json:"huge_pages_surp,omitempty"` 61 | Hugepagesize int64 `json:"hugepagesize,omitempty"` 62 | DirectMap4k int64 `json:"direct_map4k,omitempty"` 63 | DirectMap2M int64 `json:"direct_map2_m,omitempty"` 64 | DirectMap1G int64 `json:"direct_map1_g,omitempty"` 65 | } 66 | 67 | func (m *Meminfo) Load(in io.Reader) error { 68 | scanner := bufio.NewScanner(in) 69 | for scanner.Scan() { 70 | fields := strings.Fields(scanner.Text()) 71 | if len(fields) >= 2 { 72 | value, e := parseInt64(fields[1]) 73 | if e != nil { 74 | dbg.Printf("unable to parse %q in %q as int64", fields[1], scanner.Text()) 75 | continue 76 | } 77 | switch fields[0] { 78 | case "MemTotal:": 79 | m.MemTotal = value 80 | case "MemFree:": 81 | m.MemFree = value 82 | case "Buffers:": 83 | m.Buffers = value 84 | case "Cached:": 85 | m.Cached = value 86 | case "SwapCached:": 87 | m.SwapCached = value 88 | case "Active:": 89 | m.Active = value 90 | case "Inactive:": 91 | m.Inactive = value 92 | case "Active(anon):": 93 | m.ActiveAnon = value 94 | case "Inactive(anon):": 95 | m.InactiveAnon = value 96 | case "Active(file):": 97 | m.ActiveFile = value 98 | case "Inactive(file):": 99 | m.InactiveFile = value 100 | case "Unevictable:": 101 | m.Unevictable = value 102 | case "Mlocked:": 103 | m.Mlocked = value 104 | case "SwapTotal:": 105 | m.SwapTotal = value 106 | case "SwapFree:": 107 | m.SwapFree = value 108 | case "Dirty:": 109 | m.Dirty = value 110 | case "Writeback:": 111 | m.Writeback = value 112 | case "AnonPages:": 113 | m.AnonPages = value 114 | case "Mapped:": 115 | m.Mapped = value 116 | case "Shmem:": 117 | m.Shmem = value 118 | case "Slab:": 119 | m.Slab = value 120 | case "SReclaimable:": 121 | m.SReclaimable = value 122 | case "SUnreclaim:": 123 | m.SUnreclaim = value 124 | case "KernelStack:": 125 | m.KernelStack = value 126 | case "PageTables:": 127 | m.PageTables = value 128 | case "NFS_Unstable:": 129 | m.NFS_Unstable = value 130 | case "Bounce:": 131 | m.Bounce = value 132 | case "WritebackTmp:": 133 | m.WritebackTmp = value 134 | case "CommitLimit:": 135 | m.CommitLimit = value 136 | case "Committed_AS:": 137 | m.Committed_AS = value 138 | case "VmallocTotal:": 139 | m.VmallocTotal = value 140 | case "VmallocUsed:": 141 | m.VmallocUsed = value 142 | case "VmallocChunk:": 143 | m.VmallocChunk = value 144 | case "HardwareCorrupted:": 145 | m.HardwareCorrupted = value 146 | case "AnonHugePages:": 147 | m.AnonHugePages = value 148 | case "HugePages_Total:": 149 | m.HugePages_Total = value 150 | case "HugePages_Free:": 151 | m.HugePages_Free = value 152 | case "HugePages_Rsvd:": 153 | m.HugePages_Rsvd = value 154 | case "HugePages_Surp:": 155 | m.HugePages_Surp = value 156 | case "Hugepagesize:": 157 | m.Hugepagesize = value 158 | case "DirectMap4k:": 159 | m.DirectMap4k = value 160 | case "DirectMap2M:": 161 | m.DirectMap2M = value 162 | case "DirectMap1G:": 163 | m.DirectMap1G = value 164 | } 165 | } 166 | } 167 | return scanner.Err() 168 | 169 | } 170 | -------------------------------------------------------------------------------- /metrix/meminfo_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestMeminfo(t *testing.T) { 11 | expect := expect.New(t) 12 | f, e := os.Open("fixtures/meminfo.txt") 13 | expect(e).ToBeNil() 14 | defer f.Close() 15 | 16 | m := &Meminfo{} 17 | e = m.Load(f) 18 | expect(e).ToBeNil() 19 | expect(m.MemTotal).ToEqual(4041052) 20 | expect(m.VmallocTotal).ToEqual(34359738367) 21 | expect(m.InactiveFile).ToEqual(169752) 22 | expect(m.DirectMap1G).ToEqual(3145728) 23 | } 24 | -------------------------------------------------------------------------------- /metrix/net.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type NetCon struct { 12 | Slot int 13 | LocalIP net.IP 14 | LocalPort int 15 | RemoteIP net.IP 16 | RemotePort int 17 | Status string 18 | UUID string 19 | Timeout int 20 | } 21 | 22 | var socketStates = map[string]string{ 23 | "01": "ESTABLISHED", 24 | "02": "SYN_SENT", 25 | "03": "SYN_RECV", 26 | "04": "FIN_WAIT1", 27 | "05": "FIN_WAIT2", 28 | "06": "TIME_WAIT", 29 | "07": "CLOSE", 30 | "08": "CLOSE_WAIT", 31 | "09": "LAST_ACK", 32 | "0A": "LISTEN", 33 | "0B": "CLOSING", 34 | } 35 | 36 | const ( 37 | fieldNetSlot = iota 38 | fieldNetLocalAddress 39 | fieldNetRemoteAddress 40 | fieldNetSocketState 41 | fieldTxQueue 42 | fieldRxQueue 43 | fieldTr 44 | fieldTmWhen 45 | fieldRetrnSmt 46 | fieldUUID 47 | fieldTimeout 48 | ) 49 | 50 | func ParseNetCon(raw string) (c *NetCon, err error) { 51 | c = &NetCon{} 52 | fields := strings.Fields(raw) 53 | for i, f := range fields { 54 | switch i { 55 | case fieldNetSlot: 56 | c.Slot, err = strconv.Atoi(strings.TrimSuffix(f, ":")) 57 | if err != nil { 58 | return nil, fmt.Errorf("parsing slot from %q: %s", f, err) 59 | } 60 | case fieldNetLocalAddress: 61 | c.LocalIP, c.LocalPort, err = parseAddress(f) 62 | if err != nil { 63 | return nil, fmt.Errorf("error parsing local address: %s", err) 64 | } 65 | case fieldNetRemoteAddress: 66 | c.RemoteIP, c.RemotePort, err = parseAddress(f) 67 | if err != nil { 68 | return nil, fmt.Errorf("error parsing remote address: %s", err) 69 | } 70 | case fieldNetSocketState: 71 | c.Status = socketStates[f] 72 | case fieldUUID: 73 | c.UUID = f 74 | case fieldTimeout: 75 | c.Timeout, err = strconv.Atoi(f) 76 | if err != nil { 77 | return nil, fmt.Errorf("error converting timeout %q to string: %s", f, err) 78 | } 79 | 80 | } 81 | } 82 | return c, nil 83 | } 84 | 85 | func parseAddress(s string) (ip net.IP, port int, err error) { 86 | parts := strings.Split(s, ":") 87 | if len(parts) != 2 { 88 | return nil, 0, fmt.Errorf("unable to split %q into 2 parts", s) 89 | } 90 | bs := make([]byte, 4) 91 | i, err := strconv.ParseInt(parts[0], 16, 64) 92 | if err != nil { 93 | return nil, 0, fmt.Errorf("error parsing ip %q: %s", parts[0], err) 94 | } 95 | binary.LittleEndian.PutUint32(bs, uint32(i)) 96 | port64, err := strconv.ParseInt(parts[1], 16, 64) 97 | if err != nil { 98 | return nil, 0, fmt.Errorf("error parsing port %q: %s", parts[1], err) 99 | } 100 | return net.IP(bs), int(port64), nil 101 | } 102 | -------------------------------------------------------------------------------- /metrix/net_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/dynport/dgtk/tskip/assert" 7 | ) 8 | 9 | // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 10 | // sl: slot 11 | // st: socket state 12 | // tx_queue: outgoing data queue in terms of kernel memory usage 13 | // rx_queue: incoming data queue in terms of kernel memory usage 14 | // uid: process id 15 | func TestParseNetLine(t *testing.T) { 16 | // tcp 0 0 192.168.178.23:38963 193.219.128.49:6697 ESTABLISHED 2932/irssi 17 | raw := "3: 17B2A8C0:9833 3180DBC1:1A29 01 00000000:00000000 02:0007756C 00000000 1000 0 32802 2 0000000000000000 26 4 30 10 -1" 18 | nc, err := ParseNetCon(raw) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | a := assert.New(t) 23 | a.Equal(nc.Slot, 3) 24 | a.Equal(nc.LocalIP.String(), "192.168.178.23") 25 | a.Equal(nc.LocalPort, 38963) 26 | a.Equal(nc.RemoteIP.String(), "193.219.128.49") 27 | a.Equal(nc.RemotePort, 6697) 28 | a.Equal(nc.Status, "ESTABLISHED") 29 | a.Equal(nc.UUID, "32802") 30 | a.Equal(nc.Timeout, 2) 31 | } 32 | -------------------------------------------------------------------------------- /metrix/proc_cmdline.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func LoadProcCmdlines() ([]*ProcCmdline, error) { 14 | defer benchmark("load proc cmdlines")() 15 | out := []*ProcCmdline{} 16 | var err error 17 | err = eachProcDir(func(dir string) error { 18 | p := &ProcCmdline{} 19 | localPath := dir + "/cmdline" 20 | p.Pid, err = strconv.Atoi(path.Base(dir)) 21 | if err != nil { 22 | return err 23 | } 24 | f, err := os.Open(localPath) 25 | if err != nil { 26 | return err 27 | } 28 | defer f.Close() 29 | 30 | stat, err := f.Stat() 31 | if err != nil { 32 | return err 33 | } 34 | p.StartedAt = stat.ModTime().UTC() 35 | err = p.Load(f) 36 | if err != nil { 37 | return err 38 | } 39 | if p.Cmd != "" { 40 | out = append(out, p) 41 | } 42 | return nil 43 | }) 44 | return out, err 45 | } 46 | 47 | type ProcCmdline struct { 48 | Pid int `json:"pid,omitempty"` 49 | Cmd string `json:"cmd,omitempty"` 50 | Args []string `json:"args,omitempty"` 51 | StartedAt time.Time `json:"started_at,omitempty"` 52 | } 53 | 54 | func (p *ProcCmdline) Load(in io.Reader) error { 55 | b, err := ioutil.ReadAll(in) 56 | if err != nil { 57 | return err 58 | } 59 | fields := strings.Split(string(b), "\x00") 60 | for i, s := range fields { 61 | if s == "" { 62 | continue 63 | } 64 | if i == 0 { 65 | p.Cmd = s 66 | } else { 67 | p.Args = append(p.Args, s) 68 | } 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /metrix/proc_cmdline_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestProcCmdline(t *testing.T) { 11 | expect := expect.New(t) 12 | f, err := os.Open("fixtures/proc_cmdline.txt") 13 | expect(err).ToBeNil() 14 | defer f.Close() 15 | 16 | cmd := &ProcCmdline{} 17 | err = cmd.Load(f) 18 | expect(err).ToBeNil() 19 | expect(cmd.Cmd).ToEqual("/opt/postgresql-9.3.5/bin/postgres") 20 | expect(cmd.Args).ToHaveLength(2) 21 | expect(cmd.Args[0]).ToEqual("-D") 22 | expect(cmd.Args[1]).ToEqual("/data/postgres") 23 | } 24 | -------------------------------------------------------------------------------- /metrix/proc_stat.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | fieldProcStatPid = iota 15 | fieldProcStatComm 16 | fieldProcStatState 17 | fieldProcStatPpid 18 | fieldProcStatPgrp 19 | fieldProcStatSession // %d (6) The session ID of the process. 20 | fieldProcStatTtyNr // %d (7) The controlling terminal of the process. (The minor device number is contained in the combination of bits 31 21 | fieldProcStatTpgid // %d (8) The ID of the foreground process group of the controlling terminal of the process. 22 | fieldProcStatFlags // %u (%lu before Linux 2.6.22) 23 | fieldProcStatMinflt // %lu (10) The number of minor faults the process has made which have not required loading a memory page from disk. 24 | fieldProcStatCminflt // %lu (11) The number of minor faults that the process's waited-for children have made. 25 | fieldProcStatMajflt // %lu (12) The number of major faults the process has made which have required loading a memory page from disk. 26 | fieldProcStatCmajflt // %lu (13) The number of major faults that the process's waited-for children have made. 27 | fieldProcStatUtime // %lu (14) Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by 28 | fieldProcStatStime // %lu (15) Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by 29 | fieldProcStatCutime // %ld (16) Amount of time that this process's waited-for children have been scheduled in user mode, measured in clock 30 | fieldProcStatCstime // %ld (17) Amount of time that this process's waited-for children have been scheduled in kernel mode, measured in clock 31 | fieldProcStatPriority // %ld 32 | fieldProcStatNice // %ld (19) The nice value (see setpriority(2)), a value in the range 19 (low priority) to -20 (high priority). 33 | fieldProcStatNumThreads // %ld 34 | fieldProcStatItRealValue // %ld 35 | fieldProcStatStartTime // %llu (was %lu before Linux 2.6) 36 | fieldProcStatVSize // %lu (23) Virtual memory size in bytes. 37 | fieldProcStatRSS // %ld (24) Resident Set Size: number of pages the process has in real memory. This is just the pages which count 38 | fieldProcStatRSSlim // %lu (25) Current soft limit in bytes on the rss of the process; see the description of RLIMIT_RSS in getrlimit(2). 39 | fieldProcStatStartCode // %lu (26) The address above which program text can run. 40 | fieldProcStatEndCode // %lu (27) The address below which program text can run. 41 | fieldProcStatStartStack // %lu (28) The address of the start (i.e., bottom) of the stack. 42 | fieldProcStatKstkesp // %lu (29) The current value of ESP (stack pointer), as found in the kernel stack page for the process. 43 | fieldProcStatKstkeip // %lu (30) The current EIP (instruction pointer). 44 | fieldProcStatSignal // %lu (31) The bitmap of pending signals, displayed as a decimal number. Obsolete, because it does not provide infor‐ 45 | fieldProcStatBlocked // %lu (32) The bitmap of blocked signals, displayed as a decimal number. Obsolete, because it does not provide infor‐ 46 | fieldProcStatSigignore // %lu (33) The bitmap of ignored signals, displayed as a decimal number. 47 | fieldProcStatSigcatch // %lu (34) The bitmap of caught signals, displayed as a decimal number. Obsolete, because it does not provide informa‐ 48 | fieldProcStatWchan // %lu (35) This is the "channel" in which the process is waiting. It is the address of a system call, and can be 49 | fieldProcStatNswap // %lu (36) Number of pages swapped (not maintained). 50 | fieldProcStatCnswap // %lu (37) Cumulative nswap for child processes (not maintained). 51 | fieldProcStatExit_signal // %d (since Linux 2.1.22) (38) Signal to be sent to parent when we die. 52 | fieldProcStatProcessor // %d (since Linux 2.2.8) (39) CPU number last executed on. 53 | fieldProcStatRt_priority // %u (since Linux 2.5.19; was %lu before Linux 2.6.22) (40) Real-time scheduling priority, 54 | fieldProcStatPolicy // %u (since Linux 2.5.19; was %lu before Linux 2.6.22) (41) Scheduling policy (see sched_setscheduler(2)). 55 | fieldProcStatDelayacct_blkio_ticks // %llu (since Linux 2.6.18) (42) Aggregated block I/O delays, measured in clock ticks (centiseconds). 56 | fieldProcStatGuest_time // %lu (since Linux 2.6.24) (43) Guest time of the process (time spent running a virtual CPU for a guest operating 57 | fieldProcStatCguest_time // %ld (since Linux 2.6.24) (44) Guest time of the process's children, measured in clock ticks 58 | ) 59 | 60 | func numeric(s string) bool { 61 | if len(s) == 0 { 62 | return false 63 | } 64 | for _, r := range s { 65 | if r < '0' || r > '9' { 66 | return false 67 | } 68 | } 69 | return true 70 | } 71 | 72 | func eachProcDir(fun func(p string) error) error { 73 | files, err := filepath.Glob("/proc/*") 74 | if err != nil { 75 | return err 76 | } 77 | for _, f := range files { 78 | if numeric(path.Base(f)) { 79 | err := fun(f) 80 | if err != nil { 81 | logger.Printf("ERROR: %q", err) 82 | } 83 | } 84 | } 85 | return nil 86 | } 87 | 88 | func LoadProcStats() ([]*ProcStat, error) { 89 | defer benchmark("load proc stat")() 90 | out := []*ProcStat{} 91 | 92 | err := eachProcDir(func(f string) error { 93 | p, err := LoadProcStat(f + "/stat") 94 | if err != nil { 95 | return err 96 | } 97 | out = append(out, p) 98 | return nil 99 | }) 100 | return out, err 101 | } 102 | 103 | func LoadProcStat(path string) (*ProcStat, error) { 104 | f, e := os.Open(path) 105 | if e != nil { 106 | return nil, e 107 | } 108 | p := &ProcStat{} 109 | return p, p.Load(f) 110 | } 111 | 112 | type ProcStat struct { 113 | Pid int64 `json:"pid,omitempty,omitempty"` 114 | Comm string `json:"comm,omitempty,omitempty"` 115 | State string `json:"state,omitempty,omitempty"` 116 | Ppid int64 `json:"ppid,omitempty,omitempty"` 117 | Pgrp int64 `json:"pgrp,omitempty,omitempty"` 118 | //Session int64 `json:"//session,omitempty"` 119 | //TtyNr int64 `json:"//tty_nr,omitempty"` 120 | //Tpgid int64 `json:"//tpgid,omitempty"` 121 | //Flags int64 `json:"//flags,omitempty"` 122 | Minflt int64 `json:"minflt,omitempty,omitempty"` 123 | Cminflt int64 `json:"cminflt,omitempty,omitempty"` 124 | Majflt int64 `json:"majflt,omitempty,omitempty"` 125 | Cmajflt int64 `json:"cmajflt,omitempty,omitempty"` 126 | Utime int64 `json:"utime,omitempty,omitempty"` 127 | Stime int64 `json:"stime,omitempty,omitempty"` 128 | Cutime int64 `json:"cutime,omitempty,omitempty"` 129 | Cstime int64 `json:"cstime,omitempty,omitempty"` 130 | //Priority int64 `json:"//priority,omitempty"` 131 | //Nice int64 `json:"//nice,omitempty"` 132 | NumThreads int64 `json:"num_threads,omitempty,omitempty"` 133 | VSize int64 `json:"v_size,omitempty,omitempty"` 134 | RSS int64 `json:"rss,omitempty,omitempty"` 135 | RSSlim int64 `json:"rs_slim,omitempty,omitempty"` 136 | StatStartTime int64 `json:"stat_start_time,omitempty"` 137 | StatDelayacctBlkioTicks int64 `json:"stat_delayacct_blkio_ticks,omitempty"` 138 | } 139 | 140 | func (p *ProcStat) Load(in io.Reader) error { 141 | b, e := ioutil.ReadAll(in) 142 | if e != nil { 143 | return e 144 | } 145 | for i, f := range strings.Fields(string(b)) { 146 | switch i { 147 | case fieldProcStatComm: 148 | p.Comm = f 149 | case fieldProcStatState: 150 | p.State = f 151 | default: 152 | value, e := strconv.ParseInt(f, 10, 64) 153 | if e != nil { 154 | continue 155 | } 156 | switch i { 157 | case fieldProcStatPid: 158 | p.Pid = value 159 | case fieldProcStatPpid: 160 | p.Ppid = value 161 | case fieldProcStatPgrp: 162 | p.Pgrp = value 163 | case fieldProcStatMinflt: 164 | p.Minflt = value 165 | case fieldProcStatCminflt: 166 | p.Cminflt = value 167 | case fieldProcStatMajflt: 168 | p.Majflt = value 169 | case fieldProcStatCmajflt: 170 | p.Cmajflt = value 171 | case fieldProcStatUtime: 172 | p.Utime = value 173 | case fieldProcStatStime: 174 | p.Stime = value 175 | case fieldProcStatCutime: 176 | p.Cutime = value 177 | case fieldProcStatCstime: 178 | p.Cstime = value 179 | case fieldProcStatNumThreads: 180 | p.NumThreads = value 181 | case fieldProcStatRSS: 182 | p.RSS = value 183 | case fieldProcStatDelayacct_blkio_ticks: 184 | p.StatDelayacctBlkioTicks = value 185 | case fieldProcStatRSSlim: 186 | p.RSSlim = value 187 | case fieldProcStatStartTime: 188 | p.StatStartTime = value 189 | case fieldProcStatVSize: 190 | p.VSize = value 191 | } 192 | } 193 | } 194 | return nil 195 | } 196 | -------------------------------------------------------------------------------- /metrix/proc_stat_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestParseProcStat(t *testing.T) { 11 | expect := expect.New(t) 12 | f, e := os.Open("fixtures/proc_stat.txt") 13 | expect(e).ToBeNil() 14 | defer f.Close() 15 | p := &ProcStat{} 16 | expect(p).ToNotBeNil() 17 | expect(p.Load(f)).ToBeNil() 18 | expect(p.Pid).ToEqual(1153) 19 | expect(p.Comm).ToEqual("(cron)") 20 | expect(p.State).ToEqual("S") 21 | expect(p.Ppid).ToEqual(1) 22 | expect(p.Pgrp).ToEqual(1153) 23 | expect(p.Utime).ToEqual(17) 24 | expect(p.Stime).ToEqual(115) 25 | expect(p.Cutime).ToEqual(3549) 26 | expect(p.Cstime).ToEqual(2293) 27 | expect(p.NumThreads).ToEqual(1) 28 | expect(p.RSS).ToEqual(262) 29 | expect(p.VSize).ToEqual(24223744) 30 | expect(p.RSSlim).ToEqual(0) 31 | expect(p.StatStartTime).ToEqual(1301) 32 | } 33 | 34 | func TestNumeric(t *testing.T) { 35 | expect := expect.New(t) 36 | expect(numeric("1234")).ToEqual(true) 37 | expect(numeric("1")).ToEqual(true) 38 | expect(numeric("")).ToEqual(false) 39 | expect(numeric("12a")).ToEqual(false) 40 | } 41 | -------------------------------------------------------------------------------- /metrix/snapshot.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "compress/gzip" 5 | "encoding/json" 6 | "os" 7 | "path" 8 | "time" 9 | ) 10 | 11 | type Snapshot struct { 12 | Disks []*Disk `json:"disks,omitempty"` 13 | Ec2Metadata *Ec2Metadata `json:"ec2_metadata,omitempty"` 14 | Hostname string `json:"hostname,omitempty"` 15 | LoadAvg *LoadAvg `json:"load_avg,omitempty"` 16 | Meminfo *Meminfo `json:"meminfo,omitempty"` 17 | ProcCmdlines []*ProcCmdline `json:"proc_cmdlines,omitempty"` 18 | ProcStats []*ProcStat `json:"proc_stats,omitempty"` 19 | Stat *Stat `json:"stat,omitempty"` 20 | TakenAt time.Time `json:"taken_at,omitempty"` 21 | } 22 | 23 | func (s *Snapshot) Load() error { 24 | funcs := []func() error{ 25 | s.loadEc2Metadata, 26 | s.loadDisks, 27 | s.loadLoadAvg, 28 | s.loadMeminfo, 29 | s.loadProcStats, 30 | s.loadProcCmdlines, 31 | s.loadStat, 32 | s.loadTakenAt, 33 | s.loadHostname, 34 | } 35 | for _, f := range funcs { 36 | if err := f(); err != nil { 37 | return err 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | func (s *Snapshot) loadHostname() (err error) { 44 | s.Hostname, err = os.Hostname() 45 | return err 46 | } 47 | 48 | func (s Snapshot) loadTakenAt() error { 49 | s.TakenAt = time.Now().UTC() 50 | return nil 51 | } 52 | 53 | func (s Snapshot) Store(rootDir string) error { 54 | var err error 55 | if s.Hostname == "" { 56 | s.Hostname, err = os.Hostname() 57 | if err != nil { 58 | return err 59 | } 60 | } 61 | localPath := rootDir + "/snapshots/" + time.Now().UTC().Format("2006/01/02/15/2006-01-02T150405") + "-" + s.Hostname + ".json.gz" 62 | logger.Printf("writing snapshot to %s", localPath) 63 | tmpPath := localPath + ".tmp" 64 | err = os.MkdirAll(path.Dir(tmpPath), 0755) 65 | if err != nil { 66 | return err 67 | } 68 | f, err := os.Create(tmpPath) 69 | if err != nil { 70 | return err 71 | } 72 | defer f.Close() 73 | gz := gzip.NewWriter(f) 74 | defer gz.Close() 75 | enc := json.NewEncoder(gz) 76 | err = enc.Encode(s) 77 | if err != nil { 78 | return err 79 | } 80 | return os.Rename(tmpPath, localPath) 81 | } 82 | 83 | func (s *Snapshot) loadEc2Metadata() (err error) { 84 | defer benchmark("load ec2 metadata")() 85 | em, err := LoadEc2Metadata() 86 | if err != nil { 87 | dbg.Printf("ERROR loading ec2 metadata: %q", err) 88 | } else { 89 | s.Ec2Metadata = em 90 | } 91 | return nil 92 | } 93 | 94 | func (s *Snapshot) loadProcCmdlines() (err error) { 95 | s.ProcCmdlines, err = LoadProcCmdlines() 96 | return err 97 | } 98 | 99 | func (s *Snapshot) loadProcStats() (err error) { 100 | s.ProcStats, err = LoadProcStats() 101 | return err 102 | } 103 | 104 | func (s *Snapshot) loadMeminfo() (err error) { 105 | s.Meminfo, err = LoadMeminfo() 106 | return err 107 | } 108 | 109 | func (s *Snapshot) loadLoadAvg() (err error) { 110 | s.LoadAvg, err = LoadLoadAvg() 111 | return err 112 | } 113 | 114 | func (s *Snapshot) loadDisks() (err error) { 115 | s.Disks, err = LoadDisks() 116 | return err 117 | } 118 | 119 | func (s *Snapshot) loadStat() (err error) { 120 | s.Stat, err = LoadStat() 121 | return err 122 | } 123 | -------------------------------------------------------------------------------- /metrix/stat.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | fieldStatUser = iota 13 | fieldStatNice 14 | fieldStatSystem 15 | fieldStatIdle 16 | fieldStatIOWait 17 | fieldStatIRQ 18 | fieldStatSoftIRQ 19 | fieldStatSteal 20 | fieldStatGuest 21 | fieldStatQuestNice 22 | ) 23 | 24 | type Stat struct { 25 | Cpu *CpuStat `json:"cpu,omitempty"` 26 | Cpus []*CpuStat `json:"cpus,omitempty"` 27 | ContextSwitches int64 `json:"context_switches,omitempty"` 28 | BootTime int64 `json:"boot_time,omitempty"` 29 | Processes int64 `json:"processes,omitempty"` 30 | ProcessRunning int64 `json:"process_running,omitempty"` 31 | ProcessesBlocked int64 `json:"processes_blocked,omitempty"` 32 | } 33 | 34 | func LoadStat() (*Stat, error) { 35 | defer benchmark("load stat")() 36 | f, e := os.Open("/proc/stat") 37 | if e != nil { 38 | return nil, e 39 | } 40 | defer f.Close() 41 | s := &Stat{} 42 | return s, s.Load(f) 43 | } 44 | 45 | func (s *Stat) Load(in io.Reader) error { 46 | scanner := bufio.NewScanner(in) 47 | for scanner.Scan() { 48 | fields := strings.Fields(scanner.Text()) 49 | switch { 50 | case fields[0] == "cpu": 51 | s.Cpu = &CpuStat{} 52 | e := s.Cpu.Load(fields[1:]) 53 | if e != nil { 54 | return e 55 | } 56 | case strings.HasPrefix(fields[0], "cpu"): 57 | id, e := strconv.Atoi(strings.TrimPrefix(fields[0], "cpu")) 58 | if e != nil { 59 | return e 60 | } 61 | stat := &CpuStat{Id: id} 62 | e = stat.Load(fields[1:]) 63 | if e != nil { 64 | return e 65 | } 66 | s.Cpus = append(s.Cpus, stat) 67 | default: 68 | v, e := parseInt64(fields[1]) 69 | if e == nil { 70 | switch fields[0] { 71 | case "ctxt": 72 | s.ContextSwitches = v 73 | case "btime": 74 | s.BootTime = v 75 | case "processes": 76 | s.Processes = v 77 | case "procs_running": 78 | s.ProcessRunning = v 79 | case "procs_blocked": 80 | s.ProcessesBlocked = v 81 | } 82 | } 83 | } 84 | } 85 | return scanner.Err() 86 | } 87 | 88 | type CpuStat struct { 89 | Id int `json:"id,omitempty"` 90 | User int64 `json:"user,omitempty"` 91 | Nice int64 `json:"nice,omitempty"` 92 | System int64 `json:"system,omitempty"` 93 | Idle int64 `json:"idle,omitempty"` 94 | IOWait int64 `json:"io_wait,omitempty"` 95 | IRQ int64 `json:"irq,omitempty"` 96 | SoftIRQ int64 `json:"soft_irq,omitempty"` 97 | Steal int64 `json:"steal,omitempty"` 98 | Guest int64 `json:"guest,omitempty"` 99 | QuestNice int64 `json:"quest_nice,omitempty"` 100 | } 101 | 102 | func (stat *CpuStat) Load(parts []string) error { 103 | for i, raw := range parts { 104 | v, e := parseInt64(raw) 105 | if e != nil { 106 | return e 107 | } 108 | switch i { 109 | case fieldStatUser: 110 | stat.User = v 111 | case fieldStatNice: 112 | stat.Nice = v 113 | case fieldStatSystem: 114 | stat.System = v 115 | case fieldStatIdle: 116 | stat.Idle = v 117 | case fieldStatIOWait: 118 | stat.IOWait = v 119 | case fieldStatIRQ: 120 | stat.IRQ = v 121 | case fieldStatSoftIRQ: 122 | stat.SoftIRQ = v 123 | case fieldStatSteal: 124 | stat.Steal = v 125 | case fieldStatGuest: 126 | stat.Guest = v 127 | case fieldStatQuestNice: 128 | stat.QuestNice = v 129 | } 130 | 131 | } 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /metrix/stat_test.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/dynport/dgtk/expect" 8 | ) 9 | 10 | func TestStat(t *testing.T) { 11 | expect := expect.New(t) 12 | f, e := os.Open("fixtures/stat.txt") 13 | expect(e).ToBeNil() 14 | defer f.Close() 15 | 16 | s := &Stat{} 17 | e = s.Load(f) 18 | expect(e).ToBeNil() 19 | expect(s.Cpu).ToNotBeNil() 20 | expect(s.Cpu.User).ToEqual(13586) 21 | expect(s.Cpu.System).ToEqual(32308) 22 | expect(s.Cpu.IOWait).ToEqual(762) 23 | expect(len(s.Cpus)).ToEqual(4) 24 | expect(s.Cpus[0].User).ToEqual(4488) 25 | expect(s.Cpus[0].System).ToEqual(23009) 26 | expect(s.BootTime).ToEqual(1412216124) 27 | expect(s.ContextSwitches).ToEqual(4087538) 28 | expect(s.Processes).ToEqual(20491) 29 | } 30 | -------------------------------------------------------------------------------- /metrix/util.go: -------------------------------------------------------------------------------- 1 | package metrix 2 | 3 | import "time" 4 | 5 | func benchmark(message string) func() { 6 | started := time.Now() 7 | logger.Printf("started %s", message) 8 | return func() { 9 | logger.Printf("finished %s in %.06f", message, time.Since(started).Seconds()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /metrix_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | ) 9 | 10 | func TestRiakStatus(t *testing.T) { 11 | Convey("RiakStatus", t, func() { 12 | m := &Riak{} 13 | raw := readFile("fixtures/riak.json") 14 | status, e := m.ParseRiakStatus(raw) 15 | if e != nil { 16 | t.Fatal(e.Error()) 17 | } 18 | So(status.VNodeGets, ShouldEqual, int64(241)) 19 | So(status.NodePutFsmActive, ShouldEqual, int64(0)) 20 | So(status.CpuAvg1, ShouldEqual, int64(274)) 21 | 22 | So(len(status.ConnectedNodes), ShouldEqual, 4) 23 | So(status.ConnectedNodes[0], ShouldEqual, "riak@192.168.0.16") 24 | 25 | So(len(status.RingMembers), ShouldEqual, 5) 26 | So(status.RingMembers[0], ShouldEqual, "riak@192.168.0.16") 27 | 28 | }) 29 | } 30 | 31 | func TestSerializeMetric(t *testing.T) { 32 | Convey("SerializeMetric", t, func() { 33 | m := &Metric{} 34 | b, e := json.Marshal(m) 35 | So(e, ShouldBeNil) 36 | So(string(b), ShouldNotContainSubstring, "Tags") 37 | 38 | m.Tags = map[string]string{"a": "b"} 39 | b, e = json.Marshal(m) 40 | So(e, ShouldBeNil) 41 | So(string(b), ShouldContainSubstring, "Tags") 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // Parses the self documenting format of the linux net statistics interface. 9 | // The interface is in /proc/net and contains two consecutive lines starting 10 | // with the same prefix like "prefix:". The first line contains the header 11 | // and the second line the actual signed 64 bit values. 12 | // A value < 0 means, that this statistic is not supported. 13 | func parse2lines(headers, values string) map[string]int64 { 14 | keys := strings.Fields(headers) 15 | vals := strings.Fields(values) 16 | result := make(map[string]int64, len(keys)) 17 | 18 | if len(keys) != len(vals) || len(keys) <= 1 || keys[0] != vals[0] { 19 | return result 20 | } 21 | 22 | // strip the ":" of "foo:" ... 23 | topic := keys[0][:len(keys[0])-1] 24 | // .. and just get the actual header entries and values 25 | keys = keys[1:] 26 | vals = vals[1:] 27 | 28 | for i, k := range keys { 29 | if v, e := strconv.ParseInt(vals[i], 10, 64); e == nil && v >= 0 { 30 | result[topic+"."+k] = v 31 | } 32 | } 33 | return result 34 | } 35 | -------------------------------------------------------------------------------- /net_darwin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "os/exec" 6 | "regexp" 7 | "strconv" 8 | ) 9 | 10 | const NET = "net" 11 | 12 | func init() { 13 | parser.Add(NET, "true", "Collect network metrics") 14 | } 15 | 16 | var mapStatMapping = map[string]string{ 17 | "total packets received": "ip.TotalPacketsReceived", 18 | "forwarded": "ip.Forwarded", 19 | "incoming packets discarded": "ip.IncomingPacketsDiscarded", 20 | "incoming packets delivered": "ip.IncomingPacketsDelivered", 21 | "requests sent out": "ip.RequestsSentOut", 22 | "active connections openings": "tcp.ActiveConnectionsOpenings", 23 | "passive connection openings": "tcp.PassiveConnectionsOpenings", 24 | "failed connection attempts": "tcp.FailedConnectionAttempts", 25 | "connection resets received": "tcp.ConnectionResetsReceived", 26 | "connections established": "tcp.ConnectionsEstablished", 27 | "segments received": "tcp.SegmentsReceived", 28 | "segments send out": "tcp.SegmentsSendOut", 29 | "segments retransmited": "tcp.SegmentsTransmitted", 30 | "bad segments received.": "tcp.BadSegmentsReceived", 31 | "resets sent": "tcp.ResetsSent", 32 | "packets received": "udp.PacketsReceived", 33 | "packets to unknown port received.": "udp.PacketsToUnknownPortRecived", 34 | "packet receive errors": "udp.PacketReceiveErrors", 35 | "packets sent": "udp.PacketsSent", 36 | } 37 | 38 | type Net struct { 39 | RawStatus []byte 40 | } 41 | 42 | func (self *Net) fetch() (b []byte, e error) { 43 | if len(self.RawStatus) == 0 { 44 | self.RawStatus, _ = exec.Command("netstat", "-s").Output() 45 | if len(self.RawStatus) == 0 { 46 | e = errors.New("netstat returned empty output") 47 | return 48 | } 49 | } 50 | b = self.RawStatus 51 | return 52 | } 53 | 54 | func (self *Net) Prefix() string { 55 | return "net" 56 | } 57 | 58 | func (self *Net) Collect(c *MetricsCollection) (e error) { 59 | s, e := self.fetch() 60 | if e != nil { 61 | return 62 | } 63 | raw := string(s) 64 | re := regexp.MustCompile("(\\d+) ([\\w\\ \\.]+)") 65 | for _, v := range re.FindAllStringSubmatch(raw, -1) { 66 | if value, e := strconv.ParseInt(v[1], 10, 64); e == nil { 67 | if k, ok := mapStatMapping[v[2]]; ok { 68 | c.Add(k, value) 69 | } 70 | } 71 | } 72 | 73 | re = regexp.MustCompile("(\\w+): (\\d+)") 74 | for _, v := range re.FindAllStringSubmatch(raw, -1) { 75 | if value, e := strconv.ParseInt(v[2], 10, 64); e == nil { 76 | switch v[1] { 77 | case "InOctets": 78 | c.Add("ip.InOctets", value) 79 | case "OutOctets": 80 | c.Add("ip.OutOctets", value) 81 | } 82 | } 83 | } 84 | return 85 | } 86 | -------------------------------------------------------------------------------- /net_darwin_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestNet(t *testing.T) { 10 | Convey("Net", t, func() { 11 | mh := new(MetricHandler) 12 | net := &Net{RawStatus: readFile("fixtures/netstat.txt")} 13 | stats, _ := mh.Collect(net) 14 | So(len(stats) > 4, ShouldBeTrue) 15 | So(len(stats), ShouldEqual, 21) 16 | So(stats[0].Key, ShouldEqual, "net.ip.TotalPacketsReceived") 17 | So(stats[0].Value, ShouldEqual, int64(162673)) 18 | So(stats[20].Key, ShouldEqual, "net.ip.OutOctets") 19 | So(stats[20].Value, ShouldEqual, int64(667161104)) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /net_linux.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | ) 7 | 8 | const NET = "net" 9 | 10 | func init() { 11 | parser.Add(NET, "true", "Collect network metrics") 12 | } 13 | 14 | var mapStatMapping = map[string]string{ 15 | "Ip.InReceives": "ip.TotalPacketsReceived", 16 | "Ip.ForwDatagrams": "ip.Forwarded", 17 | "Ip.InDiscards": "ip.IncomingPacketsDiscarded", 18 | "Ip.InDelivers": "ip.IncomingPacketsDelivered", 19 | "Ip.OutRequests": "ip.RequestsSentOut", 20 | "Tcp.ActiveOpens": "tcp.ActiveConnectionsOpenings", 21 | "Tcp.PassiveOpens": "tcp.PassiveConnectionsOpenings", 22 | "Tcp.AttemptFails": "tcp.FailedConnectionAttempts", 23 | "Tcp.EstabResets": "tcp.ConnectionResetsReceived", 24 | "Tcp.CurrEstab": "tcp.ConnectionsEstablished", 25 | "Tcp.InSegs": "tcp.SegmentsReceived", 26 | "Tcp.OutSegs": "tcp.SegmentsSendOut", 27 | "Tcp.RetransSegs": "tcp.SegmentsTransmitted", 28 | "Tcp.InErrs": "tcp.BadSegmentsReceived", 29 | "Tcp.OutRsts": "tcp.ResetsSent", 30 | "Udp.InDatagrams": "udp.PacketsReceived", 31 | "Udp.NoPorts.": "udp.PacketsToUnknownPortRecived", 32 | "Udp.InErrors": "udp.PacketReceiveErrors", 33 | "Udp.OutDatagrams": "udp.PacketsSent", 34 | "IpExt.InOctets": "ip.InOctets", 35 | "IpExt.OutOctets": "ip.OutOctets", 36 | } 37 | 38 | type Net struct { 39 | RawStatus []byte 40 | } 41 | 42 | func (self *Net) fetch(key string) (b []byte, e error) { 43 | return ioutil.ReadFile(ProcRoot() + "/proc/net/" + key) 44 | } 45 | 46 | func (self *Net) Prefix() string { 47 | return "net" 48 | } 49 | 50 | func (self *Net) collect2LineFile(c *MetricsCollection, name string) (e error) { 51 | b, e := self.fetch(name) 52 | if e != nil { 53 | return e 54 | } 55 | raw := string(b) 56 | lines := strings.Split(raw, "\n") 57 | for i := 0; i < len(lines)-1; i += 2 { 58 | for k, v := range parse2lines(lines[i], lines[i+1]) { 59 | if mapped, ok := mapStatMapping[k]; ok { 60 | c.Add(mapped, v) 61 | } 62 | } 63 | } 64 | return nil 65 | } 66 | 67 | func (self *Net) Collect(c *MetricsCollection) error { 68 | for _, name := range []string{"snmp", "netstat"} { 69 | if e := self.collect2LineFile(c, name); e != nil { 70 | return e 71 | } 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /net_linux_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestNet(t *testing.T) { 10 | SkipConvey("Net", t, func() { 11 | t.Skip("not implemented yet") 12 | //return 13 | //mh := new(MetricHandler) 14 | //net := &Net{} 15 | //stats, _ := mh.Collect(net) 16 | //So(len(stats), ShouldBeGreaterThan, 4) 17 | //So(len(stats), ShouldEqual, 21) 18 | 19 | //So(stats[0].Key, ShouldEqual, "net.ip.TotalPacketsReceived") 20 | //So(stats[0].Value, ShouldEqual, int64(162673)) 21 | 22 | //So(stats[20].Key, ShouldEqual, "net.ip.OutOctets") 23 | //So(stats[20].Value, ShouldEqual, int64(667161104)) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /net_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestParse2lines(t *testing.T) { 10 | Convey("Parse2Lines", t, func() { 11 | header := "Ip: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates" 12 | value := "Ip: 2 64 1594741 0 0 0 0 0 1572047 2228197 5479 1154 0 390 78 0 0 0 0" 13 | want := map[string]int64{ 14 | "Ip.Forwarding": 2, 15 | "Ip.DefaultTTL": 64, 16 | 17 | "Ip.InReceives": 1594741, 18 | "Ip.InHdrErrors": 0, 19 | "Ip.InAddrErrors": 0, 20 | 21 | "Ip.ForwDatagrams": 0, 22 | 23 | "Ip.InUnknownProtos": 0, 24 | "Ip.InDiscards": 0, 25 | "Ip.InDelivers": 1572047, 26 | 27 | "Ip.OutRequests": 2228197, 28 | "Ip.OutDiscards": 5479, 29 | "Ip.OutNoRoutes": 1154, 30 | 31 | "Ip.ReasmTimeout": 0, 32 | "Ip.ReasmReqds": 390, 33 | "Ip.ReasmOKs": 78, 34 | "Ip.ReasmFails": 0, 35 | 36 | "Ip.FragOKs": 0, 37 | "Ip.FragFails": 0, 38 | "Ip.FragCreates": 0, 39 | } 40 | 41 | got := parse2lines(header, value) 42 | for k, v := range want { 43 | So(got[k], ShouldEqual, v) 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /nginx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | nginxActiveConnectionsPrefix = "Active connections: " 12 | nginxSecondLineState = "secondLine" 13 | NGINX = "nginx" 14 | ) 15 | 16 | func init() { 17 | parser.Add(NGINX, "http://127.0.0.1:8080", "Collect nginx metrics") 18 | } 19 | 20 | type Nginx struct { 21 | Address string 22 | Raw []byte 23 | 24 | ActiveConnections int64 25 | Accepts int64 26 | Handled int64 27 | Requests int64 28 | Reading int64 29 | Writing int64 30 | Waiting int64 31 | } 32 | 33 | func (n *Nginx) Metrics() []*Metric { 34 | return []*Metric{ 35 | {Key: "ActiveConnections", Value: n.ActiveConnections}, 36 | } 37 | } 38 | 39 | func parseIntE(s string) (int64, error) { 40 | return strconv.ParseInt(s, 10, 64) 41 | } 42 | 43 | func (nginx *Nginx) Load(raw []byte) error { 44 | var e error 45 | scanner := bufio.NewScanner(bytes.NewReader(raw)) 46 | state := "" 47 | for scanner.Scan() { 48 | txt := strings.TrimSpace(scanner.Text()) 49 | if strings.HasPrefix(txt, nginxActiveConnectionsPrefix) { 50 | nginx.ActiveConnections, e = parseIntE(strings.TrimPrefix(txt, nginxActiveConnectionsPrefix)) 51 | if e != nil { 52 | return e 53 | } 54 | } else if strings.HasPrefix(txt, "server accepts") { 55 | state = nginxSecondLineState 56 | } else if state == nginxSecondLineState { 57 | fields := strings.Fields(txt) 58 | if len(fields) == 3 { 59 | nginx.Accepts, e = parseIntE(fields[0]) 60 | if e != nil { 61 | return e 62 | } 63 | nginx.Handled, e = parseIntE(fields[1]) 64 | if e != nil { 65 | return e 66 | } 67 | nginx.Requests, e = parseIntE(fields[2]) 68 | if e != nil { 69 | return e 70 | } 71 | } 72 | state = "" 73 | } else if strings.HasPrefix(txt, "Reading") { 74 | fields := strings.Fields(txt) 75 | last := "" 76 | for _, f := range fields { 77 | switch last { 78 | case "Reading:": 79 | nginx.Reading, e = parseIntE(f) 80 | if e != nil { 81 | return e 82 | } 83 | case "Writing:": 84 | nginx.Writing, e = parseIntE(f) 85 | if e != nil { 86 | return e 87 | } 88 | case "Waiting:": 89 | nginx.Waiting, e = parseIntE(f) 90 | if e != nil { 91 | return e 92 | } 93 | } 94 | last = f 95 | } 96 | 97 | } 98 | 99 | } 100 | return scanner.Err() 101 | } 102 | 103 | func (self *Nginx) Prefix() string { 104 | return "nginx" 105 | } 106 | 107 | func (nginx *Nginx) Collect(c *MetricsCollection) error { 108 | var e error 109 | if len(nginx.Raw) == 0 { 110 | nginx.Raw, e = FetchURL(nginx.Address) 111 | if e != nil { 112 | return e 113 | } 114 | } 115 | e = nginx.Load(nginx.Raw) 116 | if e != nil { 117 | return e 118 | } 119 | h := MetricsMap{ 120 | "ActiveConnections": nginx.ActiveConnections, 121 | "Accepts": nginx.Accepts, 122 | "Handled": nginx.Handled, 123 | "Requests": nginx.Requests, 124 | "Reading": nginx.Reading, 125 | "Writing": nginx.Writing, 126 | "Waiting": nginx.Waiting, 127 | } 128 | for k, v := range h { 129 | c.Add(k, v) 130 | } 131 | return nil 132 | } 133 | -------------------------------------------------------------------------------- /nginx_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | ) 9 | 10 | func TestParseNginx(t *testing.T) { 11 | Convey("Nginx", t, func() { 12 | mh := &MetricHandler{} 13 | nginx := &Nginx{Raw: readFile("fixtures/nginx.status")} 14 | 15 | all, e := mh.Collect(nginx) 16 | sort.Sort(all) 17 | So(e, ShouldBeNil) 18 | 19 | So(len(all), ShouldEqual, 7) 20 | 21 | So(all[1].Key, ShouldEqual, "nginx.ActiveConnections") 22 | So(all[1].Value, ShouldEqual, 10) 23 | 24 | So(all[5].Key, ShouldEqual, "nginx.Waiting") 25 | So(all[5].Value, ShouldEqual, 70) 26 | 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /opentsdb_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | ) 9 | 10 | func TestOpenTSDB(t *testing.T) { 11 | Convey("Opentsdb", t, func() { 12 | theTime := time.Unix(11, 0) 13 | m := &Metric{Key: "metric", Value: int64(10)} 14 | So(m.OpenTSDB(theTime, "test.host"), ShouldEqual, "put metric 11 10 host=test.host") 15 | 16 | m = &Metric{Key: "os.cpu.User", Value: int64(10), Tags: map[string]string{"cpu_id": "1"}} 17 | So(m.OpenTSDB(theTime, "test.host"), ShouldEqual, "put os.cpu.User 11 10 host=test.host cpu_id=1") 18 | 19 | m = &Metric{Key: "os.cpu.User", Value: int64(10), Tags: map[string]string{"name": "(kworker/0:2)"}} 20 | So(m.OpenTSDB(theTime, "test.host"), ShouldEqual, "put os.cpu.User 11 10 host=test.host name=kworker_0_2") 21 | 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /optparse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | var optParseRegex = regexp.MustCompile("^[\\-]{1,2}(.*)") 12 | 13 | const ( 14 | FINISHED = "finished" 15 | ) 16 | 17 | type OptParser struct { 18 | CurrentKey, CurrentValue string 19 | KnownKeys []string 20 | Defaults map[string]string 21 | Values map[string]string 22 | Descriptions map[string]string 23 | } 24 | 25 | func (self *OptParser) AddDescription(key, description string) { 26 | if self.Descriptions == nil { 27 | self.Descriptions = map[string]string{key: description} 28 | } else { 29 | self.Descriptions[key] = description 30 | } 31 | } 32 | 33 | func (self *OptParser) PrintDefaults() { 34 | table := NewTable() 35 | table.Add([]string{"USAGE: " + os.Args[0]}) 36 | for _, key := range self.KnownKeys { 37 | value := self.Defaults[key] 38 | line := []string{" --" + key} 39 | ds := strings.Split(self.Descriptions[key], "\n") 40 | line = append(line, ds[0]) 41 | table.Add(line) 42 | 43 | for _, d := range ds[1:] { 44 | table.Add([]string{"", d}) 45 | } 46 | 47 | if value != "" && value != "true" { 48 | table.Add([]string{"", "DEFAULT: " + value}) 49 | } 50 | 51 | if len(ds) > 1 || (value != "" && value != "true") { 52 | table.Add([]string{}) 53 | } 54 | } 55 | lines := table.Lines() 56 | fmt.Println(strings.Join(lines, "\n")) 57 | } 58 | 59 | func (self *OptParser) AddKey(key, usage string) { 60 | self.KnownKeys = append(self.KnownKeys, key) 61 | self.AddDescription(key, usage) 62 | } 63 | 64 | func (self *OptParser) Add(key, defaultValue, usage string) { 65 | if self.Defaults == nil { 66 | self.Defaults = map[string]string{} 67 | } 68 | self.Defaults[key] = defaultValue 69 | self.AddKey(key, usage) 70 | } 71 | 72 | func NewOptParser() *OptParser { 73 | return &OptParser{ 74 | Defaults: map[string]string{}, 75 | Values: map[string]string{}, 76 | } 77 | } 78 | 79 | func (self *OptParser) ProcessAll(args []string) (e error) { 80 | self.Values = map[string]string{} 81 | for _, arg := range args { 82 | if e = self.Process(arg); e != nil { 83 | return 84 | } 85 | } 86 | self.ProcessKey(FINISHED) 87 | return 88 | } 89 | 90 | func (self *OptParser) Get(key string) string { 91 | return self.Values[key] 92 | } 93 | 94 | func (self *OptParser) Process(arg string) (e error) { 95 | matches := optParseRegex.FindStringSubmatch(arg) 96 | if len(matches) == 2 { 97 | e = self.ProcessKey(matches[1]) 98 | } else { 99 | e = self.ProcessValue(arg) 100 | } 101 | return 102 | } 103 | 104 | func (self *OptParser) ProcessKey(key string) (e error) { 105 | if self.CurrentKey != "" { 106 | if defaultValue, ok := self.Defaults[self.CurrentKey]; ok { 107 | self.Values[self.CurrentKey] = defaultValue 108 | } else if self.CurrentKey != FINISHED { 109 | e = errors.New("no value set for key " + self.CurrentKey) 110 | } 111 | } 112 | parts := strings.Split(key, "=") 113 | if len(parts) > 1 { 114 | self.CurrentKey = parts[0] 115 | e = self.ProcessValue(strings.Join(parts[1:], "=")) 116 | } else { 117 | self.CurrentKey = key 118 | } 119 | return 120 | } 121 | 122 | func (self *OptParser) ProcessValue(value string) (e error) { 123 | if self.CurrentKey != "" { 124 | self.Values[self.CurrentKey] = value 125 | } else { 126 | e = errors.New("unable to process " + value) 127 | } 128 | self.CurrentKey = "" 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /optparse_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestOptParse(t *testing.T) { 10 | Convey("OptParse", t, func() { 11 | parser := &OptParser{} 12 | parser.Add("redis", "127.0.0.1:6379", "") 13 | parser.AddKey("postgres", "") 14 | 15 | parser.ProcessAll([]string{"--redis"}) 16 | v := parser.Get("redis") 17 | So(v, ShouldEqual, "127.0.0.1:6379") 18 | 19 | parser.ProcessAll([]string{"--redis", "1.2.3.4:1234"}) 20 | v = parser.Get("redis") 21 | So(v, ShouldEqual, "1.2.3.4:1234") 22 | 23 | parser.ProcessAll([]string{"--redis=1.2.3.4:1234"}) 24 | v = parser.Get("redis") 25 | So(v, ShouldEqual, "1.2.3.4:1234") 26 | 27 | parser.ProcessAll([]string{"-redis", "1.2.3.4:1234", "-postgres", "1.2.3.4"}) 28 | v = parser.Get("postgres") 29 | So(v, ShouldEqual, "1.2.3.4") 30 | 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /output.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "time" 8 | ) 9 | 10 | type OutputHandler struct { 11 | OpenTSDBAddress string 12 | GraphiteAddress string 13 | AmqpAddress string 14 | InfluxDBAddress string 15 | Hostname string 16 | } 17 | 18 | func (o *OutputHandler) WriteMetrics(all []*Metric) (e error) { 19 | if o.Hostname == "" { 20 | hn, e := os.Hostname() 21 | if e != nil { 22 | return e 23 | } 24 | o.Hostname = hn 25 | } 26 | sent := false 27 | if o.OpenTSDBAddress != "" { 28 | e = SendMetricsToOpenTSDB(o.OpenTSDBAddress, all, o.Hostname) 29 | sent = true 30 | } 31 | 32 | if o.GraphiteAddress != "" { 33 | e = SendMetricsToGraphite(o.GraphiteAddress, all, o.Hostname) 34 | sent = true 35 | } 36 | 37 | if o.InfluxDBAddress != "" { 38 | e = PublishMetricsWithInfluxDB(o.InfluxDBAddress, all, o.Hostname) 39 | if e != nil { 40 | logger.Printf("ERROR: %q", e) 41 | } else { 42 | sent = true 43 | } 44 | } 45 | 46 | if o.AmqpAddress != "" { 47 | e = PublishMetricsWithAMQP(o.AmqpAddress, all, o.Hostname) 48 | if e != nil { 49 | logger.Printf("ERROR: %q", e) 50 | } else { 51 | sent = true 52 | } 53 | } 54 | 55 | if !sent { 56 | SendMetricsToStdout(all, o.Hostname) 57 | } 58 | return 59 | } 60 | 61 | func SendMetricsToGraphite(address string, metrics []*Metric, hostname string) (e error) { 62 | return SendMetricsWith(address, metrics, hostname, func(started time.Time, metric *Metric) string { 63 | return metric.Graphite(started, hostname) 64 | }, 65 | ) 66 | } 67 | 68 | func SendMetricsToOpenTSDB(address string, metrics []*Metric, hostname string) (e error) { 69 | return SendMetricsWith(address, metrics, hostname, func(started time.Time, metric *Metric) string { 70 | return metric.OpenTSDB(started, hostname) 71 | }, 72 | ) 73 | } 74 | 75 | func SendMetricsWith(address string, metrics []*Metric, hostname string, serializer func(time.Time, *Metric) string) (e error) { 76 | started := time.Now() 77 | con, e := net.DialTimeout("tcp", address, 1*time.Second) 78 | if e != nil { 79 | return 80 | } 81 | defer con.Close() 82 | fmt.Printf("connected in %.06f\n", time.Now().Sub(started).Seconds()) 83 | 84 | started = time.Now() 85 | debug := os.Getenv("DEBUG") == "true" 86 | for _, m := range metrics { 87 | line := serializer(started, m) 88 | if debug { 89 | fmt.Println(line) 90 | } 91 | fmt.Fprintln(con, line) 92 | } 93 | fmt.Printf("sent %d metrics in %.06f\n", len(metrics), time.Now().Sub(started).Seconds()) 94 | return 95 | } 96 | 97 | func SendMetricsToStdout(metrics []*Metric, hostname string) { 98 | now := time.Now() 99 | for _, m := range metrics { 100 | fmt.Println(m.Ascii(now, hostname)) 101 | } 102 | } -------------------------------------------------------------------------------- /pgbouncer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "os/exec" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | const PGBOUNCER = "pgbouncer" 12 | 13 | func init() { 14 | parser.Add(PGBOUNCER, "127.0.0.1:6432", "Collect pgbouncer metrics") 15 | } 16 | 17 | type PgBouncer struct { 18 | Address string 19 | Port int 20 | } 21 | 22 | func (self *PgBouncer) Collect(c *MetricsCollection) (e error) { 23 | chunks := strings.Split(self.Address, ":") 24 | if len(chunks) == 2 { 25 | self.Address = chunks[0] 26 | self.Port, _ = strconv.Atoi(chunks[1]) 27 | } else { 28 | self.Address = chunks[0] 29 | self.Port = 6432 30 | } 31 | es := []string{} 32 | 33 | if e = self.CollectServers(c); e != nil { 34 | es = append(es, e.Error()) 35 | } 36 | 37 | if e = self.CollectClients(c); e != nil { 38 | es = append(es, e.Error()) 39 | } 40 | 41 | if e = self.CollectStats(c); e != nil { 42 | es = append(es, e.Error()) 43 | } 44 | 45 | if e = self.CollectSockets(c); e != nil { 46 | es = append(es, e.Error()) 47 | } 48 | 49 | if e = self.CollectPools(c); e != nil { 50 | es = append(es, e.Error()) 51 | } 52 | 53 | if e = self.CollectMemory(c); e != nil { 54 | es = append(es, e.Error()) 55 | } 56 | if len(es) > 0 { 57 | e = errors.New(strings.Join(es, ", ")) 58 | } 59 | return 60 | } 61 | 62 | func (self *PgBouncer) Prefix() string { 63 | return "pgbouncer" 64 | } 65 | 66 | type PgBouncerConnection struct { 67 | Type, User, Database, State, Address, LocalAddress string 68 | Port, LocalPort int64 69 | ConnectTime, RequestTime time.Time 70 | } 71 | 72 | type PgBouncerStat struct { 73 | Database string 74 | TotalRequests, TotalReceived, TotalSent, TotalQueryTime, AvgReq, AvgRecv, AvgSent, AvgQuery int64 75 | } 76 | 77 | func (self *PgBouncer) CollectStats(c *MetricsCollection) (e error) { 78 | allStats, e := self.Stats() 79 | if e == nil { 80 | for _, s := range allStats { 81 | tags := map[string]string{"database": s.Database} 82 | c.AddWithTags("stats.TotalRequests", s.TotalRequests, tags) 83 | c.AddWithTags("stats.TotalReceived", s.TotalReceived, tags) 84 | c.AddWithTags("stats.TotalSent", s.TotalSent, tags) 85 | c.AddWithTags("stats.TotalQueryTime", s.TotalQueryTime, tags) 86 | c.AddWithTags("stats.AvgReq", s.AvgReq, tags) 87 | c.AddWithTags("stats.AvgRecv", s.AvgRecv, tags) 88 | c.AddWithTags("stats.AvgSent", s.AvgSent, tags) 89 | c.AddWithTags("stats.AvgQuery", s.AvgQuery, tags) 90 | } 91 | } 92 | return 93 | } 94 | 95 | type PgBouncerFD struct { 96 | Task, User, Database, Address, Cancel string 97 | Fd, Port, Link int64 98 | } 99 | 100 | type PgBouncerSocket struct { 101 | Type, User, Database, State, Address, LocalAddress string 102 | ConnectTime, RequestTime time.Time 103 | Port, LocalPort, RecvPos, PktPos, PktRemain, SendPos, SendRemain, PktAvail, SendAvail int64 104 | } 105 | 106 | type PgBouncerMemory struct { 107 | Name string 108 | Size, Used, Free, MemTotal int64 109 | } 110 | 111 | func (self *PgBouncer) CollectClients(c *MetricsCollection) (e error) { 112 | return self.CollectConnections("clients", c) 113 | } 114 | 115 | func (self *PgBouncer) CollectServers(c *MetricsCollection) (e error) { 116 | return self.CollectConnections("servers", c) 117 | } 118 | 119 | func (self *PgBouncer) CollectConnections(t string, c *MetricsCollection) (e error) { 120 | connections, e := self.Connections(t) 121 | if e != nil { 122 | return 123 | } 124 | for _, connection := range connections { 125 | tags := map[string]string{ 126 | "database": connection.Database, 127 | "state": connection.State, 128 | "address": connection.Address, 129 | "port": strconv.FormatInt(connection.Port, 10), 130 | "local_address": connection.LocalAddress, 131 | "local_port": strconv.FormatInt(connection.LocalPort, 10), 132 | "type": connection.Type, 133 | } 134 | c.AddWithTags("connections.Total", 1, tags) 135 | } 136 | return 137 | } 138 | 139 | func (b *PgBouncer) Connections(t string) (s []*PgBouncerConnection, e error) { 140 | f := func(chunks []string) { 141 | ser := &PgBouncerConnection{ 142 | Type: chunks[0], 143 | User: chunks[1], 144 | Database: chunks[2], 145 | State: chunks[3], 146 | Address: chunks[4], 147 | Port: parseInt64(chunks[5]), 148 | LocalAddress: chunks[6], 149 | LocalPort: parseInt64(chunks[7]), 150 | } 151 | if time, e := time.Parse(timeFormat, chunks[8]+"Z"); e == nil { 152 | ser.ConnectTime = time 153 | } 154 | if time, e := time.Parse(timeFormat, chunks[9]+"Z"); e == nil { 155 | ser.RequestTime = time 156 | } 157 | s = append(s, ser) 158 | } 159 | e = b.Execute(t, f) 160 | return 161 | } 162 | 163 | func (b *PgBouncer) Execute(s string, f func([]string)) (e error) { 164 | cmd := exec.Command("psql", "-h", b.Address, "-p", strconv.Itoa(b.Port), "pgbouncer", "-c", "SHOW "+s, "-t", "-A") 165 | out, e := cmd.CombinedOutput() 166 | if e != nil { 167 | return 168 | } 169 | for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { 170 | if len(line) > 0 { 171 | chunks := strings.Split(line, "|") 172 | f(chunks) 173 | } 174 | } 175 | return 176 | } 177 | 178 | type PgBouncerPool struct { 179 | Database, User string 180 | ClientsActive, ClientsWaiting, ServersActive, ServersIdle, ServersUsed, ServersTested, ServersLogin, MaxWait int64 181 | } 182 | 183 | func (self *PgBouncer) CollectPools(c *MetricsCollection) (e error) { 184 | pools, e := self.Pools() 185 | if e != nil { 186 | return 187 | } 188 | for _, pool := range pools { 189 | tags := map[string]string{ 190 | "database": pool.Database, 191 | } 192 | for _, pool := range pools { 193 | c.AddWithTags("pools.ClientsActive", pool.ClientsActive, tags) 194 | c.AddWithTags("pools.ClientsWaiting", pool.ClientsWaiting, tags) 195 | c.AddWithTags("pools.ServersActive", pool.ServersActive, tags) 196 | c.AddWithTags("pools.ServersIdle", pool.ServersIdle, tags) 197 | c.AddWithTags("pools.ServersUsed", pool.ServersUsed, tags) 198 | c.AddWithTags("pools.ServersTested", pool.ServersTested, tags) 199 | c.AddWithTags("pools.ServersLogin", pool.ServersLogin, tags) 200 | c.AddWithTags("pools.MaxWait", pool.MaxWait, tags) 201 | } 202 | } 203 | return 204 | } 205 | 206 | func (b *PgBouncer) Pools() (ret []*PgBouncerPool, e error) { 207 | f := func(c []string) { 208 | p := &PgBouncerPool{} 209 | p.Database = c[0] 210 | p.User = c[1] 211 | p.ClientsActive = parseInt64(c[2]) 212 | p.ClientsWaiting = parseInt64(c[3]) 213 | p.ServersActive = parseInt64(c[4]) 214 | p.ServersIdle = parseInt64(c[5]) 215 | p.ServersUsed = parseInt64(c[6]) 216 | p.ServersTested = parseInt64(c[7]) 217 | p.ServersLogin = parseInt64(c[8]) 218 | p.MaxWait = parseInt64(c[9]) 219 | ret = append(ret, p) 220 | } 221 | b.Execute("pools", f) 222 | return 223 | } 224 | 225 | func (b *PgBouncer) Stats() (s []*PgBouncerStat, e error) { 226 | f := func(chunks []string) { 227 | ser := &PgBouncerStat{ 228 | Database: chunks[0], 229 | TotalRequests: parseInt64(chunks[1]), 230 | TotalReceived: parseInt64(chunks[2]), 231 | TotalSent: parseInt64(chunks[3]), 232 | TotalQueryTime: parseInt64(chunks[4]), 233 | AvgReq: parseInt64(chunks[5]), 234 | AvgRecv: parseInt64(chunks[6]), 235 | AvgSent: parseInt64(chunks[7]), 236 | AvgQuery: parseInt64(chunks[8]), 237 | } 238 | s = append(s, ser) 239 | } 240 | b.Execute("stats", f) 241 | return 242 | } 243 | 244 | func (b *PgBouncer) FDs() (ret []*PgBouncerFD, e error) { 245 | f := func(chunks []string) { 246 | s := &PgBouncerFD{ 247 | Fd: parseInt64(chunks[0]), 248 | Task: chunks[1], 249 | User: chunks[2], 250 | Database: chunks[3], 251 | Address: chunks[4], 252 | Port: parseInt64(chunks[5]), 253 | Cancel: chunks[6], 254 | Link: parseInt64(chunks[7]), 255 | } 256 | ret = append(ret, s) 257 | } 258 | b.Execute("fds", f) 259 | return 260 | } 261 | 262 | func (b *PgBouncer) Sockets() (ret []*PgBouncerSocket, e error) { 263 | return b.GenericSockets("sockets") 264 | } 265 | 266 | func (b *PgBouncer) GenericSockets(s string) (ret []*PgBouncerSocket, e error) { 267 | f := func(chunks []string) { 268 | s := &PgBouncerSocket{ 269 | Type: chunks[0], 270 | User: chunks[1], 271 | Database: chunks[2], 272 | State: chunks[3], 273 | Address: chunks[4], 274 | Port: parseInt64(chunks[5]), 275 | LocalAddress: chunks[6], 276 | LocalPort: parseInt64(chunks[7]), 277 | RecvPos: parseInt64(chunks[12]), 278 | PktPos: parseInt64(chunks[13]), 279 | PktRemain: parseInt64(chunks[14]), 280 | SendPos: parseInt64(chunks[15]), 281 | SendRemain: parseInt64(chunks[16]), 282 | PktAvail: parseInt64(chunks[17]), 283 | SendAvail: parseInt64(chunks[18]), 284 | } 285 | if time, e := time.Parse(timeFormat, chunks[8]+"Z"); e == nil { 286 | s.ConnectTime = time 287 | } 288 | if time, e := time.Parse(timeFormat, chunks[9]+"Z"); e == nil { 289 | s.RequestTime = time 290 | } 291 | ret = append(ret, s) 292 | } 293 | e = b.Execute(s, f) 294 | return 295 | } 296 | 297 | func (self *PgBouncer) CollectSockets(c *MetricsCollection) (e error) { 298 | sockets, e := self.Sockets() 299 | if e != nil { 300 | return 301 | } 302 | for _, socket := range sockets { 303 | tags := map[string]string{ 304 | "type": socket.Type, 305 | "database": socket.Database, 306 | "state": socket.State, 307 | "address": socket.Address, 308 | "port": strconv.FormatInt(socket.Port, 10), 309 | "local_address": socket.LocalAddress, 310 | "local_port": strconv.FormatInt(socket.LocalPort, 10), 311 | } 312 | c.AddWithTags("sockets.RecvPos", socket.RecvPos, tags) 313 | c.AddWithTags("sockets.PktPos", socket.PktPos, tags) 314 | c.AddWithTags("sockets.PktRemain", socket.PktRemain, tags) 315 | c.AddWithTags("sockets.SendPos", socket.SendPos, tags) 316 | c.AddWithTags("sockets.SendRemain", socket.SendRemain, tags) 317 | c.AddWithTags("sockets.PktAvail", socket.PktAvail, tags) 318 | c.AddWithTags("sockets.SendAvail", socket.SendAvail, tags) 319 | } 320 | return 321 | } 322 | 323 | func (b *PgBouncer) Memory() (ret []*PgBouncerMemory, e error) { 324 | f := func(chunks []string) { 325 | s := &PgBouncerMemory{ 326 | Name: chunks[0], 327 | Size: parseInt64(chunks[1]), 328 | Used: parseInt64(chunks[2]), 329 | Free: parseInt64(chunks[3]), 330 | MemTotal: parseInt64(chunks[4]), 331 | } 332 | ret = append(ret, s) 333 | } 334 | e = b.Execute("mem", f) 335 | return 336 | } 337 | 338 | func (b *PgBouncer) CollectMemory(c *MetricsCollection) (e error) { 339 | memories, e := b.Memory() 340 | if e == nil { 341 | for _, m := range memories { 342 | tags := map[string]string{"name": m.Name} 343 | c.AddWithTags("memory.Size", m.Size, tags) 344 | c.AddWithTags("memory.Used", m.Used, tags) 345 | c.AddWithTags("memory.Free", m.Free, tags) 346 | c.AddWithTags("memory.MemTotal", m.MemTotal, tags) 347 | } 348 | } 349 | return 350 | } 351 | -------------------------------------------------------------------------------- /postgres_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestParsePostgresUrl(t *testing.T) { 10 | Convey("PostgresUrl", t, func() { 11 | raw := "psql://ff:ffpwd@127.0.0.1:1234/ff_test" 12 | u := ParsePostgresUrl(raw) 13 | So(u.Host, ShouldEqual, "127.0.0.1") 14 | So(u.User, ShouldEqual, "ff") 15 | So(u.Password, ShouldEqual, "ffpwd") 16 | So(u.Database, ShouldEqual, "ff_test") 17 | So(u.Port, ShouldEqual, 1234) 18 | 19 | s, _ := u.ConnectString() 20 | So(s, ShouldEqual, "host=127.0.0.1 dbname=ff_test user=ff password=ffpwd port=1234 sslmode=disable") 21 | 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /processes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const PROCESSES = "processes" 12 | 13 | func init() { 14 | parser.Add(PROCESSES, "true", "Collect metrics for processes") 15 | } 16 | 17 | var procStatsMapping = map[int]string{ 18 | 13: "Utime", 19 | 14: "Stime", 20 | 15: "Cutime", 21 | 16: "Sctime", 22 | 17: "Priority", 23 | 18: "Nice", 24 | 19: "NumThreads", 25 | 20: "Itrealvalue", 26 | 21: "Starttime", 27 | 22: "Vsize", 28 | 23: "RSS", 29 | 24: "RSSlim", 30 | 27: "Startstac", 31 | 42: "GuestTime", 32 | 43: "CguestTime", 33 | } 34 | 35 | type Processes struct { 36 | } 37 | 38 | func (self *Processes) Prefix() string { 39 | return "processes" 40 | } 41 | 42 | var matchBrackets = regexp.MustCompile("(^\\(|\\)$)") 43 | 44 | func NormalizeProcessName(comm string) string { 45 | withoutBrackes := matchBrackets.ReplaceAllString(comm, "") 46 | return strings.Split(withoutBrackes, "/")[0] 47 | } 48 | 49 | var matchNums = regexp.MustCompile("[0-9]+") 50 | 51 | func generateProcfiles() (matches chan string, e error) { 52 | procmount := ProcRoot() + "/proc" 53 | d, e := os.Open(procmount) 54 | if e != nil { 55 | return nil, e 56 | } 57 | 58 | matches = make(chan string, 100) 59 | go func(dir *os.File) { 60 | for e := error(nil); e == error(nil); { 61 | names, e := dir.Readdirnames(100) 62 | if e != nil { 63 | break 64 | } 65 | for _, name := range names { 66 | if matchNums.MatchString(name) { 67 | matches <- procmount + "/" + name + "/stat" 68 | } 69 | } 70 | } 71 | close(matches) 72 | }(d) 73 | return matches, nil 74 | } 75 | 76 | func (self *Processes) Collect(c *MetricsCollection) (e error) { 77 | matches, e := generateProcfiles() 78 | if e != nil { 79 | return 80 | } 81 | for path := range matches { 82 | if data, e := ioutil.ReadFile(path); e == nil { 83 | chunks := strings.Split(string(data), " ") 84 | tags := map[string]string{ 85 | "pid": chunks[0], 86 | "ppid": chunks[3], 87 | "comm": chunks[1], 88 | "name": NormalizeProcessName(chunks[1]), 89 | "state": chunks[2], 90 | } 91 | for idx, v := range chunks { 92 | if i, e := strconv.ParseInt(v, 10, 64); e == nil { 93 | if k, ok := procStatsMapping[idx]; ok { 94 | c.AddWithTags(k, i, tags) 95 | } 96 | } 97 | } 98 | } 99 | } 100 | return 101 | } 102 | -------------------------------------------------------------------------------- /processes_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestProcesses(t *testing.T) { 10 | Convey("Processes", t, func() { 11 | mh := new(MetricHandler) 12 | col := &Processes{} 13 | stats, _ := mh.Collect(col) 14 | So(len(stats), ShouldBeGreaterThan, 0) 15 | So(len(stats), ShouldEqual, 980) 16 | for _, stat := range stats { 17 | if stat.Tags["name"] == "init" && stat.Key == "processes.Utime" { 18 | So(stat.Key, ShouldEqual, "processes.Utime") 19 | So(stat.Value, ShouldEqual, 25) 20 | So(stat.Tags["comm"], ShouldEqual, "(init)") 21 | So(stat.Tags["name"], ShouldEqual, "init") 22 | So(stat.Tags["state"], ShouldEqual, "S") 23 | So(stat.Tags["pid"], ShouldEqual, "1") 24 | } 25 | } 26 | 27 | }) 28 | } 29 | 30 | func TestNormlizeProcessName(t *testing.T) { 31 | Convey("NormalizeProcessName", t, func() { 32 | So(NormalizeProcessName("(int)"), ShouldEqual, "int") 33 | So(NormalizeProcessName("(kworker/2:1H)"), ShouldEqual, "kworker") 34 | 35 | }) 36 | } 37 | 38 | func BenchmarkNormlizeProcessName(b *testing.B) { 39 | for i := 0; i < b.N; i++ { 40 | NormalizeProcessName("(kworker/2:1H)") 41 | } 42 | } 43 | 44 | func BenchmarkCollect(b *testing.B) { 45 | mh := new(MetricHandler) 46 | col := &Processes{} 47 | for i := 0; i < b.N; i++ { 48 | mh.Collect(col) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | const REDIS = "redis" 10 | 11 | func init() { 12 | parser.Add(REDIS, "127.0.0.1:6379", "Collect redis metrics") 13 | } 14 | 15 | type Redis struct { 16 | Address string 17 | Raw []byte 18 | } 19 | 20 | func (self *Redis) Prefix() string { 21 | return "redis" 22 | } 23 | 24 | func (self *Redis) Collect(c *MetricsCollection) (e error) { 25 | logger.Printf("collecting redis") 26 | b, e := self.ReadInfo() 27 | if e != nil { 28 | logger.Printf("ERROR reading info: %q", e.Error()) 29 | return 30 | } 31 | str := string(b) 32 | values := map[string]string{} 33 | dbRegexp := regexp.MustCompile("^db(\\d+):keys=(\\d+),expires=(\\d+)") 34 | pid := values["process_id"] 35 | tcp_port := values["tcp_port"] 36 | for _, line := range strings.Split(str, "\n") { 37 | line = strings.TrimSpace(line) 38 | if strings.HasPrefix(line, "#") || len(line) < 2 { 39 | continue 40 | } 41 | res := dbRegexp.FindStringSubmatch(line) 42 | if len(res) == 4 { 43 | dbTags := map[string]string{ 44 | "db": res[1], 45 | "pid": pid, 46 | "port": tcp_port, 47 | } 48 | c.AddWithTags("db.Keys", parseInt64(res[2]), dbTags) 49 | c.AddWithTags("db.Expires", parseInt64(res[3]), dbTags) 50 | continue 51 | } 52 | chunks := strings.Split(line, ":") 53 | if len(chunks) > 1 { 54 | values[chunks[0]] = strings.Join(chunks[1:], ":") 55 | } 56 | } 57 | tags := map[string]string{ 58 | "pid": pid, 59 | "port": tcp_port, 60 | } 61 | 62 | c.AddWithTags("UptimeInSeconds", parseInt64(values["uptime_in_seconds"]), tags) 63 | c.AddWithTags("memory.UsedMemory", parseInt64(values["used_memory"]), tags) 64 | c.AddWithTags("memory.UsedMemoryRSS", parseInt64(values["used_memory_rss"]), tags) 65 | c.AddWithTags("memory.UsedMemoryPeak", parseInt64(values["used_memory_peak"]), tags) 66 | c.AddWithTags("memory.UsedMemoryLua", parseInt64(values["used_memory_lua"]), tags) 67 | c.AddWithTags("clients.ConnectedClients", parseInt64(values["connected_clients"]), tags) 68 | c.AddWithTags("clients.ClientLongestOutputList", parseInt64(values["client_longest_output_list"]), tags) 69 | c.AddWithTags("clients.ClientBiggestInputBuf", parseInt64(values["client_biggest_input_buf"]), tags) 70 | c.AddWithTags("clients.BlockedClients", parseInt64(values["blocked_clients"]), tags) 71 | c.AddWithTags("stats.TotalConnectionsReceived", parseInt64(values["total_connections_received"]), tags) 72 | c.AddWithTags("stats.TotalCommandsProcessed", parseInt64(values["total_commands_processed"]), tags) 73 | c.AddWithTags("stats.InstantaneousOpsPerSec", parseInt64(values["instantaneous_ops_per_sec"]), tags) 74 | c.AddWithTags("stats.RejectedConnections", parseInt64(values["rejected_connections"]), tags) 75 | c.AddWithTags("stats.ExpiredKeys", parseInt64(values["expired_keys"]), tags) 76 | c.AddWithTags("stats.EvictedKeys", parseInt64(values["evicted_keys"]), tags) 77 | c.AddWithTags("stats.KeyspaceHits", parseInt64(values["keyspace_hits"]), tags) 78 | c.AddWithTags("stats.KeyspaceMisses", parseInt64(values["keyspace_misses"]), tags) 79 | c.AddWithTags("stats.PubsubChannels", parseInt64(values["pubsub_channels"]), tags) 80 | c.AddWithTags("stats.PubsubPatterns", parseInt64(values["pubsub_patterns"]), tags) 81 | c.AddWithTags("stats.LatestForkUsec", parseInt64(values["latest_fork_usec"]), tags) 82 | c.AddWithTags("replication.ConnectedSlaves", parseInt64(values["connected_slaves"]), tags) 83 | return 84 | } 85 | 86 | func (self *Redis) ReadInfo() (b []byte, e error) { 87 | if len(self.Raw) == 0 { 88 | var con net.Conn 89 | logger.Printf("connecting %s", self.Address) 90 | con, e = net.Dial("tcp", self.Address) 91 | if e != nil { 92 | logger.Printf("ERROR connecting %s: %q", self.Address, e.Error()) 93 | return 94 | } 95 | defer con.Close() 96 | 97 | con.Write([]byte("INFO\r\n")) 98 | b = make([]byte, 4096) 99 | var i int 100 | i, e = con.Read(b) 101 | dbg.Printf("read %d bytes", i) 102 | if e != nil { 103 | logger.Printf("ERROR reading: %q", e.Error()) 104 | return 105 | } 106 | self.Raw = b 107 | } 108 | b = self.Raw 109 | return 110 | } 111 | -------------------------------------------------------------------------------- /redis_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestParseRedis(t *testing.T) { 10 | Convey("Redis", t, func() { 11 | mh := &MetricHandler{} 12 | es := &Redis{Raw: readFile("fixtures/redis_info.txt")} 13 | 14 | all, _ := mh.Collect(es) 15 | So(len(all), ShouldBeGreaterThan, 0) 16 | 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /riak_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/smartystreets/goconvey/convey" 7 | ) 8 | 9 | func TestRiakMetrics(t *testing.T) { 10 | Convey("RiakMetrics", t, func() { 11 | mh := &MetricHandler{} 12 | col := &Riak{Raw: readFile("fixtures/riak.json")} 13 | metrics, _ := mh.Collect(col) 14 | 15 | So(len(metrics), ShouldEqual, 126) 16 | m1 := metrics[0] 17 | So(m1.Key, ShouldEqual, "riak.VNodeGets") 18 | So(m1.Value, ShouldEqual, 241) 19 | 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # to be called with 5 | # GOOS=linux GOARCH=amd64 bash ./scripts/release.sh 6 | 7 | BIN_NAME=metrix 8 | 9 | # revision and version 10 | PROJECT_ROOT=$(grealpath $(dirname $0)/../) 11 | GIT_COMMIT=$(git rev-parse --short HEAD) 12 | GIT_STATUS=$(test -n "`git status --porcelain`" && echo "+CHANGES" || echo "") 13 | GIT_REV=$GIT_COMMIT$GIT_STATUS 14 | VERSION=$(grep VERSION $PROJECT_ROOT/constants.go | cut -d '"' -f 2) 15 | 16 | # dirs and paths 17 | RELEASE_PATH=/tmp/$BIN_NAME/releases/$GIT_REV 18 | NAME=$BIN_NAME-v$VERSION.$GOOS.$GOARCH 19 | RELEASE_TMP_DIR=$RELEASE_PATH/$NAME 20 | RELEASE_BIN=$RELEASE_TMP_DIR/$BIN_NAME 21 | 22 | echo "building in $RELEASE_PATH" 23 | mkdir -p $RELEASE_TMP_DIR 24 | go build -a -ldflags "-X main.GITCOMMIT $GIT_REV" -o $RELEASE_BIN 25 | chmod a+x $RELEASE_BIN 26 | cd $(dirname $RELEASE_BIN) && tar cfz $RELEASE_PATH/$NAME.tar.gz $BIN_NAME 27 | rm -Rf $RELEASE_TMP_DIR 28 | -------------------------------------------------------------------------------- /setup_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | ) 7 | 8 | func init() { 9 | dir, _ := os.Getwd() 10 | os.Setenv("PROC_ROOT", dir+"/fixtures") 11 | } 12 | 13 | func readFile(path string) []byte { 14 | b, e := ioutil.ReadFile(path) 15 | if e != nil { 16 | panic(e.Error()) 17 | } 18 | return b 19 | } 20 | -------------------------------------------------------------------------------- /suricata.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | "net" 6 | "io" 7 | "encoding/json" 8 | "errors" 9 | ) 10 | 11 | const SURICATA = "suricata" 12 | 13 | func init() { 14 | parser.Add(SURICATA, "/usr/local/var/run/suricata/suricata-command.socket", "dump Suricata's performance counters") 15 | } 16 | 17 | 18 | type Suricata struct { 19 | SocketName string 20 | } 21 | 22 | func (self *Suricata) Prefix() string { 23 | return SURICATA 24 | } 25 | 26 | func (self *Suricata) Collect(c *MetricsCollection) (e error) { 27 | name := "testTag" 28 | tags := map[string]string{"name": name} 29 | c.AddWithTags("suricata.test", 1024, tags) 30 | r,e := getCountersFromSocket(self.SocketName) 31 | if e != nil { 32 | return 33 | } 34 | for k,v := range r { 35 | for kk,vv := range v { 36 | // add thread name as tag 37 | if k != "Detect" && k != "FlowManagerThread" { 38 | tags := map[string]string{"thread": k} 39 | c.AddWithTags(kk, vv, tags) 40 | } else { 41 | c.Add(kk, vv) 42 | } 43 | } 44 | } 45 | return 46 | } 47 | 48 | type Response struct { 49 | ReturnCode string `json:"return"` 50 | Message map[string]map[string]int64 `json:"message"` 51 | } 52 | 53 | func getCountersFromSocket(socketName string) (counts map[string]map[string]int64, err error) { 54 | conn, err := net.Dial("unix", socketName) 55 | if err != nil { 56 | return nil, err 57 | } 58 | defer conn.Close() 59 | 60 | // see https://github.com/inliniac/suricata/blob/94571c5dd28858ff68c44b648fd41c5d87c0e28d/src/unix-manager.c#L288 61 | _, err = conn.Write([]byte("{\"version\": \"0.1\"}")) 62 | if err != nil { 63 | return nil, errors.New("can not send version :: "+err.Error()) 64 | } 65 | time.Sleep(100 * time.Millisecond) 66 | var buf [128]byte 67 | _, err = conn.Read(buf[0:]) 68 | if err != nil { 69 | // see https://github.com/inliniac/suricata/blob/94571c5dd28858ff68c44b648fd41c5d87c0e28d/src/unix-manager.c#L319 70 | return nil, errors.New("got kick on version :: "+err.Error()) 71 | } 72 | _, err = conn.Write([]byte("{\"command\": \"dump-counters\"}")) 73 | if err != nil { 74 | return nil, errors.New("can not send command :: "+err.Error()) 75 | } 76 | time.Sleep(100 * time.Millisecond) 77 | data := "" 78 | // read anser for command 79 | // see https://github.com/inliniac/suricata/blob/672049632431bb695f56798c9c5f196afcf2fb27/scripts/suricatasc/src/suricatasc.py#L83 80 | for i := 0; i < 16; i++ { 81 | var buf2 [4096]byte 82 | result2, err := conn.Read(buf2[0:]) 83 | if err != nil { 84 | if err != io.EOF { 85 | return nil, err 86 | } 87 | } 88 | data = data + string(buf2[:result2]) 89 | var isJson Response 90 | jerr := json.Unmarshal([]byte(data), &isJson) 91 | if jerr == nil { 92 | if isJson.ReturnCode == "OK" { 93 | return isJson.Message, nil 94 | } else { 95 | return nil, errors.New(isJson.ReturnCode) 96 | } 97 | 98 | } else { 99 | expect := "unexpected end of JSON input" 100 | if jerr.Error() != expect { 101 | // is json but not our counters 102 | return nil, errors.New(data) 103 | } 104 | } 105 | time.Sleep(10 * time.Millisecond) 106 | } 107 | return nil, errors.New("max limit for loop") 108 | } -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Table struct { 9 | Columns [][]string 10 | Lengths map[int]int 11 | } 12 | 13 | func NewTable() *Table { 14 | return &Table{ 15 | Columns: [][]string{}, 16 | Lengths: map[int]int{}, 17 | } 18 | } 19 | 20 | func (self *Table) Lines() (lines []string) { 21 | for _, col := range self.Columns { 22 | cl := []string{} 23 | for i, v := range col { 24 | cl = append(cl, fmt.Sprintf("%-*s", self.Lengths[i], v)) 25 | } 26 | lines = append(lines, strings.Join(cl, "\t")) 27 | } 28 | return 29 | } 30 | 31 | func (self *Table) Add(cols []string) { 32 | for i, v := range cols { 33 | if self.Lengths[i] < len(v) { 34 | self.Lengths[i] = len(v) 35 | } 36 | } 37 | self.Columns = append(self.Columns, cols) 38 | } 39 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | func AbortWith(message string) { 12 | fmt.Println("ERROR:", message) 13 | flag.PrintDefaults() 14 | os.Exit(1) 15 | } 16 | 17 | func FetchURL(url string) (b []byte, e error) { 18 | var rsp *http.Response 19 | rsp, e = http.Get(url) 20 | if e != nil { 21 | return 22 | } 23 | defer rsp.Body.Close() 24 | b, e = ioutil.ReadAll(rsp.Body) 25 | return 26 | } 27 | --------------------------------------------------------------------------------