diff --git a/README.md b/README.md index 0e0f8db..f59e62b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Current collectors are: - s3roundtrip (S3 roundtrip check using AWS SDK) - redis (redis metrics) - memcached (memcached metrics) +- beanstalk (beanstalk metrics) Install --- @@ -45,6 +46,7 @@ $ go build ./cmd/oiofs.plugin/oiofs.plugin.go $ go build ./cmd/s3roundtrip.plugin/s3roundtrip.plugin.go $ go build ./cmd/redis.plugin/redis.plugin.go $ go build ./cmd/memcached.plugin/memcached.plugin.go +$ go build ./cmd/beanstalk.plugin/beanstalk.plugin.go ``` Type in `./[name].plugin -h` to get all available options for each plugin @@ -61,6 +63,7 @@ $ cp oiofs.plugin /usr/libexec/netdata/plugins.d/ $ cp s3roundtrip.plugin /usr/libexec/netdata/plugins.d/ $ cp redis.plugin /usr/libexec/netdata/plugins.d/ $ cp memcached.plugin /usr/libexec/netdata/plugins.d/ +$ cp beanstalk.plugin /usr/libexec/netdata/plugins.d/ ``` Ubuntu Xenial @@ -73,6 +76,7 @@ $ cp oiofs.plugin /usr/libexec/netdata/plugins.d/ $ cp s3roundtrip.plugin /usr/libexec/netdata/plugins.d/ $ cp redis.plugin /usr/libexec/netdata/plugins.d/ $ cp memcached.plugin /usr/libexec/netdata/plugins.d/ +$ cp beanstalk.plugin /usr/libexec/netdata/plugins.d/ ``` Add the following /etc/netdata/netdata.conf: @@ -105,7 +109,11 @@ Add the following /etc/netdata/netdata.conf: [plugin:memcached] update every = 10 - command options = --targets 172.30.2.106:6011:memcached + command options = --targets 172.30.2.106:6011 + +[plugin:beanstalk] + update every = 10 + command options = --targets 172.30.2.106:6014:tube1:tube2:tube3 ``` Create and configure plugin config files diff --git a/beanstalk/beanstalk.go b/beanstalk/beanstalk.go new file mode 100644 index 0000000..88762f0 --- /dev/null +++ b/beanstalk/beanstalk.go @@ -0,0 +1,84 @@ +// OpenIO netdata collectors +// Copyright (C) 2020 OpenIO SAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public +// License along with this program. If not, see . + +package beanstalk + +import ( + "bufio" + "net" + "strings" +) + +type collector struct { + addr string + tubes []string +} + +func NewCollector(addr string, tubes []string) *collector { + return &collector{ + addr: addr, + tubes: tubes, + } +} + +func SendCommand(conn net.Conn, cmd string, prefix string, data map[string]string) error { + if _, err := conn.Write([]byte(cmd + "\r\n")); err != nil { + return err + } + + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + line := scanner.Text() + if line == "" { + break + } + if line == "NOT_FOUND" { + break + } + kv := strings.Split(line, ": ") + if len(kv) != 2 { + continue + } + data[prefix+kv[0]] = kv[1] + } + + if err := scanner.Err(); err != nil { + return err + } + + return nil +} + +func (c *collector) Collect() (map[string]string, error) { + conn, err := net.Dial("tcp", c.addr) + if err != nil { + return nil, err + } + defer conn.Close() + + data := map[string]string{} + + if err = SendCommand(conn, "stats", "", data); err != nil { + return nil, err + } + for _, tube := range c.tubes { + if err = SendCommand(conn, "stats-tube "+tube, "_"+tube+"_", data); err != nil { + return nil, err + } + } + + return data, nil +} diff --git a/beanstalk/beanstalk.spec.stats-tube_default.txt b/beanstalk/beanstalk.spec.stats-tube_default.txt new file mode 100644 index 0000000..454d54b --- /dev/null +++ b/beanstalk/beanstalk.spec.stats-tube_default.txt @@ -0,0 +1,17 @@ +OK 267 +--- +name: default +current-jobs-urgent: 0 +current-jobs-ready: 0 +current-jobs-reserved: 0 +current-jobs-delayed: 0 +current-jobs-buried: 0 +total-jobs: 0 +current-using: 3 +current-watching: 31 +current-waiting: 12 +cmd-delete: 0 +cmd-pause-tube: 0 +pause: 0 +pause-time-left: 0 + diff --git a/beanstalk/beanstalk.spec.stats-tube_oio-delete.txt b/beanstalk/beanstalk.spec.stats-tube_oio-delete.txt new file mode 100644 index 0000000..d5e00df --- /dev/null +++ b/beanstalk/beanstalk.spec.stats-tube_oio-delete.txt @@ -0,0 +1,17 @@ +OK 276 +--- +name: oio-delete +current-jobs-urgent: 0 +current-jobs-ready: 0 +current-jobs-reserved: 0 +current-jobs-delayed: 0 +current-jobs-buried: 0 +total-jobs: 90110 +current-using: 2 +current-watching: 1 +current-waiting: 1 +cmd-delete: 90110 +cmd-pause-tube: 0 +pause: 0 +pause-time-left: 0 + diff --git a/beanstalk/beanstalk.spec.stats-tube_oio-rebuild.txt b/beanstalk/beanstalk.spec.stats-tube_oio-rebuild.txt new file mode 100644 index 0000000..3e85541 --- /dev/null +++ b/beanstalk/beanstalk.spec.stats-tube_oio-rebuild.txt @@ -0,0 +1,17 @@ +OK 269 +--- +name: oio-rebuild +current-jobs-urgent: 0 +current-jobs-ready: 0 +current-jobs-reserved: 0 +current-jobs-delayed: 0 +current-jobs-buried: 0 +total-jobs: 0 +current-using: 2 +current-watching: 2 +current-waiting: 1 +cmd-delete: 0 +cmd-pause-tube: 0 +pause: 0 +pause-time-left: 0 + diff --git a/beanstalk/beanstalk.spec.stats-tube_oio.txt b/beanstalk/beanstalk.spec.stats-tube_oio.txt new file mode 100644 index 0000000..f98cb84 --- /dev/null +++ b/beanstalk/beanstalk.spec.stats-tube_oio.txt @@ -0,0 +1,17 @@ +OK 274 +--- +name: oio +current-jobs-urgent: 0 +current-jobs-ready: 0 +current-jobs-reserved: 0 +current-jobs-delayed: 0 +current-jobs-buried: 0 +total-jobs: 640670 +current-using: 24 +current-watching: 10 +current-waiting: 10 +cmd-delete: 640670 +cmd-pause-tube: 0 +pause: 0 +pause-time-left: 0 + diff --git a/beanstalk/beanstalk.spec.stats.txt b/beanstalk/beanstalk.spec.stats.txt new file mode 100644 index 0000000..5db7bcc --- /dev/null +++ b/beanstalk/beanstalk.spec.stats.txt @@ -0,0 +1,51 @@ +OK 971 +--- +current-jobs-urgent: 0 +current-jobs-ready: 0 +current-jobs-reserved: 0 +current-jobs-delayed: 0 +current-jobs-buried: 0 +cmd-put: 728737 +cmd-peek: 0 +cmd-peek-ready: 0 +cmd-peek-delayed: 0 +cmd-peek-buried: 0 +cmd-reserve: 728752 +cmd-reserve-with-timeout: 173970 +cmd-delete: 728737 +cmd-release: 4 +cmd-use: 28 +cmd-watch: 13 +cmd-ignore: 0 +cmd-bury: 0 +cmd-kick: 0 +cmd-touch: 0 +cmd-stats: 128258 +cmd-stats-job: 0 +cmd-stats-tube: 129094 +cmd-list-tubes: 32282 +cmd-list-tube-used: 0 +cmd-list-tubes-watched: 0 +cmd-pause-tube: 0 +job-timeouts: 0 +total-jobs: 728737 +max-job-size: 65535 +current-tubes: 4 +current-connections: 31 +current-producers: 10 +current-workers: 12 +current-waiting: 12 +total-connections: 122597 +pid: 2892 +version: 1.10 +rusage-utime: 35.948646 +rusage-stime: 82.499803 +uptime: 348107 +binlog-oldest-index: 149 +binlog-current-index: 149 +binlog-records-migrated: 0 +binlog-records-written: 1457478 +binlog-max-size: 10240000 +id: fe6081983c4b33dd +hostname: node-1.novalocal + diff --git a/beanstalk/beanstalk_test.go b/beanstalk/beanstalk_test.go new file mode 100644 index 0000000..44a3061 --- /dev/null +++ b/beanstalk/beanstalk_test.go @@ -0,0 +1,200 @@ +// OpenIO netdata collectors +// Copyright (C) 2020 OpenIO SAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public +// License along with this program. If not, see . + +package beanstalk + +import ( + "bufio" + "fmt" + "io/ioutil" + "log" + "net" + "reflect" + "strings" + "testing" +) + +type testServer struct { +} + +var expected = map[string]string{ + "_default_cmd-delete": "0", + "_default_cmd-pause-tube": "0", + "_default_current-jobs-buried": "0", + "_default_current-jobs-delayed": "0", + "_default_current-jobs-ready": "0", + "_default_current-jobs-reserved": "0", + "_default_current-jobs-urgent": "0", + "_default_current-using": "3", + "_default_current-waiting": "12", + "_default_current-watching": "31", + "_default_name": "default", + "_default_pause": "0", + "_default_pause-time-left": "0", + "_default_total-jobs": "0", + "_oio-delete_cmd-delete": "90110", + "_oio-delete_cmd-pause-tube": "0", + "_oio-delete_current-jobs-buried": "0", + "_oio-delete_current-jobs-delayed": "0", + "_oio-delete_current-jobs-ready": "0", + "_oio-delete_current-jobs-reserved": "0", + "_oio-delete_current-jobs-urgent": "0", + "_oio-delete_current-using": "2", + "_oio-delete_current-waiting": "1", + "_oio-delete_current-watching": "1", + "_oio-delete_name": "oio-delete", + "_oio-delete_pause": "0", + "_oio-delete_pause-time-left": "0", + "_oio-delete_total-jobs": "90110", + "_oio-rebuild_cmd-delete": "0", + "_oio-rebuild_cmd-pause-tube": "0", + "_oio-rebuild_current-jobs-buried": "0", + "_oio-rebuild_current-jobs-delayed": "0", + "_oio-rebuild_current-jobs-ready": "0", + "_oio-rebuild_current-jobs-reserved": "0", + "_oio-rebuild_current-jobs-urgent": "0", + "_oio-rebuild_current-using": "2", + "_oio-rebuild_current-waiting": "1", + "_oio-rebuild_current-watching": "2", + "_oio-rebuild_name": "oio-rebuild", + "_oio-rebuild_pause": "0", + "_oio-rebuild_pause-time-left": "0", + "_oio-rebuild_total-jobs": "0", + "_oio_cmd-delete": "640670", + "_oio_cmd-pause-tube": "0", + "_oio_current-jobs-buried": "0", + "_oio_current-jobs-delayed": "0", + "_oio_current-jobs-ready": "0", + "_oio_current-jobs-reserved": "0", + "_oio_current-jobs-urgent": "0", + "_oio_current-using": "24", + "_oio_current-waiting": "10", + "_oio_current-watching": "10", + "_oio_name": "oio", + "_oio_pause": "0", + "_oio_pause-time-left": "0", + "_oio_total-jobs": "640670", + "binlog-current-index": "149", + "binlog-max-size": "10240000", + "binlog-oldest-index": "149", + "binlog-records-migrated": "0", + "binlog-records-written": "1457478", + "cmd-bury": "0", + "cmd-delete": "728737", + "cmd-ignore": "0", + "cmd-kick": "0", + "cmd-list-tube-used": "0", + "cmd-list-tubes": "32282", + "cmd-list-tubes-watched": "0", + "cmd-pause-tube": "0", + "cmd-peek": "0", + "cmd-peek-buried": "0", + "cmd-peek-delayed": "0", + "cmd-peek-ready": "0", + "cmd-put": "728737", + "cmd-release": "4", + "cmd-reserve": "728752", + "cmd-reserve-with-timeout": "173970", + "cmd-stats": "128258", + "cmd-stats-job": "0", + "cmd-stats-tube": "129094", + "cmd-touch": "0", + "cmd-use": "28", + "cmd-watch": "13", + "current-connections": "31", + "current-jobs-buried": "0", + "current-jobs-delayed": "0", + "current-jobs-ready": "0", + "current-jobs-reserved": "0", + "current-jobs-urgent": "0", + "current-producers": "10", + "current-tubes": "4", + "current-waiting": "12", + "current-workers": "12", + "hostname": "node-1.novalocal", + "id": "fe6081983c4b33dd", + "job-timeouts": "0", + "max-job-size": "65535", + "pid": "2892", + "rusage-stime": "82.499803", + "rusage-utime": "35.948646", + "total-connections": "122597", + "total-jobs": "728737", + "uptime": "348107", + "version": "1.10", +} + +func newTestServer() *testServer { + return &testServer{} +} + +func (s *testServer) Run(l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + log.Printf("WARN: %s", err) + return + } + go s.handleConn(conn) + } +} + +func (s *testServer) handleConn(conn net.Conn) { + defer conn.Close() + out := bufio.NewWriter(conn) + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + line := scanner.Text() + b, err := ioutil.ReadFile("./beanstalk.spec." + strings.Replace(line, " ", "_", -1) + ".txt") + if err != nil { + fmt.Print(err) + break + } + _, err = out.Write(b) + if err != nil { + fmt.Print(err) + break + } + out.Flush() + } +} + +func TestBeanstalkCollector(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + + if err != nil { + t.Fatalf("listen error: %v", err) + } + beanstalk := newTestServer() + go beanstalk.Run(l) + + var tubes = []string{"default", "oio", "oio-delete", "oio-rebuild", "prout"} + collector := NewCollector(l.Addr().String(), tubes) + data, err := collector.Collect() + if err != nil { + t.Fatalf("unexpected Collect error: %v", err) + } + + if !reflect.DeepEqual(data, expected) { + t.Fatalf("unexpected result got\n%v\nexpected\n%v\n", data, expected) + } + + l.Close() + _, err = collector.Collect() + if err == nil { + t.Fatalf("expected error") + } +} diff --git a/cmd/beanstalk/beanstalk.plugin.go b/cmd/beanstalk/beanstalk.plugin.go new file mode 100644 index 0000000..c5e0a9e --- /dev/null +++ b/cmd/beanstalk/beanstalk.plugin.go @@ -0,0 +1,131 @@ +// OpenIO netdata collectors +// Copyright (C) 2020 OpenIO SAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public +// License along with this program. If not, see . + +package main + +import ( + "flag" + "log" + "oionetdata/beanstalk" + "oionetdata/collector" + "oionetdata/netdata" + "os" + "strings" + "time" +) + +func main() { + if len(os.Args) < 2 { + log.Fatalf("argument required") + } + var targets string + fs := flag.NewFlagSet("", flag.ExitOnError) + fs.StringVar(&targets, "targets", "", "Comma separated list of Redis IP:PORT") + err := fs.Parse(os.Args[2:]) + if err != nil { + log.Fatalln("ERROR: Beanstalk plugin: Could not parse args", err) + } + intervalSeconds := collector.ParseIntervalSeconds(os.Args[1]) + + if targets == "" { + log.Fatalln("ERROR: Beanstalk plugin: missing targets") + } + + writer := netdata.NewDefaultWriter() + worker := netdata.NewWorker(time.Duration(intervalSeconds)*time.Second, writer) + + for _, target := range strings.Split(targets, ",") { + res := strings.Split(target, ":") + if len(res) < 2 { + log.Fatalln("Invalid parameter", target, "must be IP:PORT[:tube1][:tube2]...") + } + addr := res[0] + ":" + res[1] + tubes := res[2:] + collector := beanstalk.NewCollector(addr, tubes) + worker.AddCollector(collector) + instance := "beanstalk." + addr + ":global" + + c := netdata.NewChart(instance, "jobs", "", "", "", "general", "beanstalk.job") + c.AddDimension("current-jobs-urgent", "urgent", netdata.AbsoluteAlgorithm) + c.AddDimension("current-jobs-ready", "ready", netdata.AbsoluteAlgorithm) + c.AddDimension("current-jobs-reserved", "reserved", netdata.AbsoluteAlgorithm) + c.AddDimension("current-jobs-delayed", "delayed", netdata.AbsoluteAlgorithm) + c.AddDimension("current-jobs-buried", "buried", netdata.AbsoluteAlgorithm) + c.AddDimension("total-jobs", "total", netdata.IncrementalAlgorithm) + c.AddDimension("jobs-timeouts", "timeouts", netdata.IncrementalAlgorithm) + worker.AddChart(c, collector) + + c = netdata.NewChart(instance, "commands", "", "", "", "general", "beanstalk.commands") + c.AddDimension("cmd-put", "put", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-peek", "peek", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-peek-ready", "peek-ready", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-peek-delayed", "peek-delayed", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-peek-buried", "peek-buried", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-reserve", "reserve", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-use", "use", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-watch", "watch", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-ignore", "ignore", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-delete", "delete", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-release", "release", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-bury", "bury", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-kick", "kick", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-stats", "stats", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-stats-job", "stats-job", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-stats-tube", "stats-tube", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-list-tubes", "list-tubes", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-list-tubes-used", "list-tubes-used", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-list-tubes-watched", "list-tubes-watched", netdata.IncrementalAlgorithm) + c.AddDimension("cmd-pause-tube", "pause-tube", netdata.IncrementalAlgorithm) + worker.AddChart(c, collector) + + c = netdata.NewChart(instance, "tubes", "", "", "", "general", "beanstalk.tubes") + c.AddDimension("current-tubes", "current", netdata.AbsoluteAlgorithm) + worker.AddChart(c, collector) + + c = netdata.NewChart(instance, "connections", "", "", "", "general", "beanstalk.connections") + c.AddDimension("current-connections", "open", netdata.AbsoluteAlgorithm) + c.AddDimension("current-producers", "producers", netdata.AbsoluteAlgorithm) + c.AddDimension("current-workers", "workers", netdata.AbsoluteAlgorithm) + c.AddDimension("current-waiting", "waiting", netdata.AbsoluteAlgorithm) + c.AddDimension("total-connections", "total", netdata.IncrementalAlgorithm) + worker.AddChart(c, collector) + + c = netdata.NewChart(instance, "binlog", "", "", "", "general", "beanstalk.binlog") + c.AddDimension("binlog-records-written", "written", netdata.IncrementalAlgorithm) + c.AddDimension("binlog-records-migrated", "compaction", netdata.IncrementalAlgorithm) + worker.AddChart(c, collector) + + for _, tube := range tubes { + instance = "beanstalk." + addr + ":" + tube + c = netdata.NewChart(instance, "jobs", "", "", "", tube, "beanstalk.job") + c.AddDimension("_"+tube+"_current-jobs-urgent", "urgent", netdata.AbsoluteAlgorithm) + c.AddDimension("_"+tube+"_current-jobs-ready", "ready", netdata.AbsoluteAlgorithm) + c.AddDimension("_"+tube+"_current-jobs-reserved", "reserved", netdata.AbsoluteAlgorithm) + c.AddDimension("_"+tube+"_current-jobs-delayed", "delayed", netdata.AbsoluteAlgorithm) + c.AddDimension("_"+tube+"_current-jobs-buried", "buried", netdata.AbsoluteAlgorithm) + c.AddDimension("_"+tube+"_total-jobs", "total", netdata.IncrementalAlgorithm) + worker.AddChart(c, collector) + + c = netdata.NewChart(instance, "connections", "", "", "", tube, "beanstalk.connections") + c.AddDimension("_"+tube+"_current-using", "using", netdata.AbsoluteAlgorithm) + c.AddDimension("_"+tube+"_current-waiting", "waiting", netdata.AbsoluteAlgorithm) + c.AddDimension("_"+tube+"_current-watching", "watching", netdata.AbsoluteAlgorithm) + worker.AddChart(c, collector) + } + } + + worker.Run() +}