diff --git a/collector/supervisord.go b/collector/supervisord.go new file mode 100644 index 00000000..795bb4db --- /dev/null +++ b/collector/supervisord.go @@ -0,0 +1,134 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !nosupervisord +package collector + +import ( + "flag" + + "github.com/kolo/xmlrpc" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/log" +) + +var ( + supervisordURL = flag.String("collector.supervisord.url", "http://localhost:9001/RPC2", "XML RPC endpoint") +) + +type supervisordCollector struct { + client *xmlrpc.Client + upDesc *prometheus.Desc + stateDesc *prometheus.Desc + exitStatusDesc *prometheus.Desc + uptimeDesc *prometheus.Desc +} + +func init() { + Factories["supervisord"] = NewSupervisordCollector +} + +func NewSupervisordCollector() (Collector, error) { + client, err := xmlrpc.NewClient(*supervisordURL, nil) + if err != nil { + return nil, err + } + + var ( + subsystem = "supervisord" + labelNames = []string{"name", "group"} + ) + return &supervisordCollector{ + client: client, + upDesc: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "up"), + "Process Up", + labelNames, + nil, + ), + stateDesc: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "state"), + "Process State", + labelNames, + nil, + ), + exitStatusDesc: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "exit_status"), + "Process Exit Status", + labelNames, + nil, + ), + uptimeDesc: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "uptime"), + "Process Uptime", + labelNames, + nil, + ), + }, nil +} + +func (c *supervisordCollector) isRunning(state int) bool { + // http://supervisord.org/subprocess.html#process-states + const ( + STOPPED = 0 + STARTING = 10 + RUNNING = 20 + BACKOFF = 30 + STOPPING = 40 + EXITED = 100 + FATAL = 200 + UNKNOWN = 1000 + ) + switch state { + case STARTING, RUNNING, STOPPING: + return true + } + return false +} + +func (c *supervisordCollector) Update(ch chan<- prometheus.Metric) error { + var infos []struct { + Name string `xmlrpc:"name"` + Group string `xmlrpc:"group"` + Start int `xmlrpc:"start"` + Stop int `xmlrpc:"stop"` + Now int `xmlrpc:"now"` + State int `xmlrpc:"state"` + StateName string `xmlrpc:"statename"` + SpawnErr string `xmlrpc:"spanerr"` + ExitStatus int `xmlrpc:"exitstatus"` + StdoutLogfile string `xmlrcp:"stdout_logfile"` + StderrLogfile string `xmlrcp:"stderr_logfile"` + PID int `xmlrpc:"pid"` + } + if err := c.client.Call("supervisor.getAllProcessInfo", nil, &infos); err != nil { + return err + } + for _, info := range infos { + lables := []string{info.Name, info.Group} + + ch <- prometheus.MustNewConstMetric(c.stateDesc, prometheus.GaugeValue, float64(info.State), lables...) + ch <- prometheus.MustNewConstMetric(c.exitStatusDesc, prometheus.GaugeValue, float64(info.ExitStatus), lables...) + + if c.isRunning(info.State) { + ch <- prometheus.MustNewConstMetric(c.upDesc, prometheus.GaugeValue, 1, lables...) + ch <- prometheus.MustNewConstMetric(c.uptimeDesc, prometheus.CounterValue, float64(info.Now-info.Start), lables...) + } else { + ch <- prometheus.MustNewConstMetric(c.upDesc, prometheus.GaugeValue, 0, lables...) + ch <- prometheus.MustNewConstMetric(c.uptimeDesc, prometheus.CounterValue, 0, lables...) + } + log.Debugf("%s:%s is %s on pid %d", info.Group, info.Name, info.StateName, info.PID) + } + + return nil +}