-
Notifications
You must be signed in to change notification settings - Fork 136
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of metrics (Prometheus support)
Created a metrics package that leans on the official Prometheus client to serve metrics. Adds a new Pollable for Sensors that record metrics received from user- defined functions.
- Loading branch information
Showing
11 changed files
with
283 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package metrics | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"net/http" | ||
"utils" | ||
) | ||
|
||
// Metrics represents the service to advertise for finding the metrics | ||
// endpoint, and the collection of Sensors. | ||
type Metrics struct { | ||
ServiceName string `json:"name"` | ||
Url string `json:"url"` | ||
Port int `json:"port"` | ||
TTL int `json:"ttl"` | ||
Poll int `json:"poll"` | ||
Interfaces json.RawMessage `json:"interfaces"` // optional override | ||
Tags []string `json:"tags,omitempty"` | ||
Sensors []*Sensor `json:"sensors"` | ||
IpAddress string | ||
} | ||
|
||
func (m *Metrics) Parse() error { | ||
if ipAddress, err := utils.IpFromInterfaces(m.Interfaces); err != nil { | ||
return err | ||
} else { | ||
m.IpAddress = ipAddress | ||
} | ||
for _, sensor := range m.Sensors { | ||
if err := sensor.Parse(); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (m *Metrics) Serve() { | ||
http.Handle(m.Url, prometheus.Handler()) | ||
listen := fmt.Sprintf("%s:%v", m.IpAddress, m.Port) | ||
http.ListenAndServe(listen, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package metrics | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"os" | ||
"os/exec" | ||
"strconv" | ||
"utils" | ||
|
||
log "github.com/Sirupsen/logrus" | ||
) | ||
|
||
// A Sensor is a single measurement of the application. | ||
type Sensor struct { | ||
Namespace string `json:"namespace"` | ||
Subsystem string `json:"subsystem"` | ||
Name string `json:"name"` | ||
Help string `json:"help"` // help string returned by API | ||
Type string `json:"type"` | ||
Poll int `json:"poll"` // time in seconds | ||
CheckExec json.RawMessage `json:"check"` | ||
checkCmd *exec.Cmd | ||
collector prometheus.Collector | ||
} | ||
|
||
// PollTime implements Pollable for Sensor | ||
// It returns the sensor's poll interval. | ||
func (s Sensor) PollTime() int { | ||
return s.Poll | ||
} | ||
|
||
// PollAction implements Pollable for Sensor. | ||
func (s Sensor) PollAction() { | ||
if metricValue, err := s.getMetrics(); err != nil { | ||
s.record(metricValue) | ||
} else { | ||
log.Errorln(err) | ||
} | ||
} | ||
|
||
func (s Sensor) getMetrics() (string, error) { | ||
// we'll pass stderr to the container's stderr, but stdout must | ||
// be "clean" and not have anything other than what we intend | ||
// to write to our collector. | ||
s.checkCmd.Stderr = os.Stderr | ||
if out, err := s.checkCmd.Output(); err != nil { | ||
return "", err | ||
} else { | ||
return string(out[:]), nil | ||
} | ||
} | ||
|
||
func (s Sensor) record(metricValue string) { | ||
if val, err := strconv.ParseFloat(metricValue, 64); err != nil { | ||
log.Errorln(err) | ||
} else { | ||
switch collector := s.collector.(type) { | ||
case prometheus.Counter: | ||
collector.Add(val) | ||
case prometheus.Gauge: | ||
collector.Set(val) | ||
case prometheus.Histogram: | ||
collector.Observe(val) | ||
case prometheus.Summary: | ||
collector.Observe(val) | ||
} | ||
} | ||
} | ||
|
||
func (s *Sensor) Parse() error { | ||
if check, err := utils.ParseCommandArgs(s.CheckExec); err != nil { | ||
return err | ||
} else { | ||
s.checkCmd = check | ||
} | ||
// the prometheus client lib's API here is baffling... they don't expose | ||
// an interface or embed their Opts type in each of the Opts "subtypes", | ||
// so we can't share the initialization. | ||
switch { | ||
case s.Type == "counter": | ||
s.collector = prometheus.NewCounter(prometheus.CounterOpts{ | ||
Namespace: s.Namespace, | ||
Subsystem: s.Subsystem, | ||
Name: s.Name, | ||
Help: s.Help, | ||
}) | ||
case s.Type == "gauge": | ||
s.collector = prometheus.NewGauge(prometheus.GaugeOpts{ | ||
Namespace: s.Namespace, | ||
Subsystem: s.Subsystem, | ||
Name: s.Name, | ||
Help: s.Help, | ||
}) | ||
case s.Type == "histogram": | ||
s.collector = prometheus.NewHistogram(prometheus.HistogramOpts{ | ||
Namespace: s.Namespace, | ||
Subsystem: s.Subsystem, | ||
Name: s.Name, | ||
Help: s.Help, | ||
}) | ||
case s.Type == "summary": | ||
s.collector = prometheus.NewSummary(prometheus.SummaryOpts{ | ||
Namespace: s.Namespace, | ||
Subsystem: s.Subsystem, | ||
Name: s.Name, | ||
Help: s.Help, | ||
}) | ||
default: | ||
return fmt.Errorf("Invalid sensor type: %s\n", s.Type) | ||
} | ||
|
||
prometheus.MustRegister(s.collector) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters