Skip to content

Instantly share code, notes, and snippets.

@grobie
Created January 2, 2015 17:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grobie/f75535195750e0dbd45b to your computer and use it in GitHub Desktop.
Save grobie/f75535195750e0dbd45b to your computer and use it in GitHub Desktop.
consul metrics exporter
// consulCollector implements the prometheus.Collector interface.
type consulCollector struct {
bin string
errc chan error
metrics map[string]prometheus.Gauge
}
func newConsulCollector(bin string, errc chan error) prometheus.Collector {
return &consulCollector{
bin: bin,
errc: errc,
metrics: map[string]prometheus.Gauge{},
}
}
func (c *consulCollector) Collect(metricc chan<- prometheus.Metric) {
err := c.updateMetrics()
if err != nil {
c.errc <- err
return
}
for _, m := range c.metrics {
m.Collect(metricc)
}
}
// TODO(alx): Clarify proper usage of Describe and improve error handling.
func (c *consulCollector) Describe(descc chan<- *prometheus.Desc) {
err := c.updateMetrics()
if err != nil {
c.errc <- err
return
}
for _, m := range c.metrics {
descc <- m.Desc()
}
}
func (c *consulCollector) updateMetrics() error {
stats, err := getConsulStats(c.bin)
if err != nil {
return fmt.Errorf("consul info failed: %s", err)
}
for name, value := range stats {
if _, ok := c.metrics[name]; !ok {
c.metrics[name] = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "consul",
Name: name,
Help: fmt.Sprintf("%s from consul info", name),
},
)
}
c.metrics[name].Set(float64(value))
}
return nil
}
type consulStats map[string]int64
func getConsulStats(bin string) (consulStats, error) {
info := exec.Command(bin, "info")
outPipe, err := info.StdoutPipe()
if err != nil {
return nil, err
}
if err := info.Start(); err != nil {
return nil, err
}
stats, err := parseConsulStats(outPipe)
if err != nil {
return nil, err
}
if err := info.Wait(); err != nil {
return nil, err
}
return stats, nil
}
func parseConsulStats(r io.Reader) (consulStats, error) {
var (
s = bufio.NewScanner(r)
stats = consulStats{}
ignoredFields = map[string]struct{}{
"arch": struct{}{},
"os": struct{}{},
"state": struct{}{},
"version": struct{}{},
}
ignoredKeys = map[string]struct{}{
"build": struct{}{},
}
key string
)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if strings.Contains(line, ":") {
key = strings.TrimSuffix(line, ":")
}
if _, ok := ignoredKeys[key]; ok {
continue
}
if strings.Contains(line, "=") {
var (
parts = strings.SplitN(line, "=", 2)
field, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
name = strings.Join([]string{key, field}, "_")
)
if _, ok := ignoredFields[field]; ok {
continue
}
switch value {
case "true":
stats[name] = 1
case "false":
stats[name] = 0
case "never":
stats[name] = 0
default:
i, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return nil, err
}
stats[name] = i
}
}
}
if err := s.Err(); err != nil {
return nil, err
}
return stats, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment