2015-10-22 16:17:57 +00:00
|
|
|
package prometheus_client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-12-01 17:08:38 +00:00
|
|
|
"log"
|
2015-10-28 22:19:13 +00:00
|
|
|
"net/http"
|
2016-03-22 16:34:33 +00:00
|
|
|
"regexp"
|
2016-03-23 13:18:37 +00:00
|
|
|
"strconv"
|
2016-03-22 16:34:33 +00:00
|
|
|
"strings"
|
2015-10-28 22:19:13 +00:00
|
|
|
|
2016-01-27 21:21:36 +00:00
|
|
|
"github.com/influxdata/telegraf"
|
2016-01-27 23:15:14 +00:00
|
|
|
"github.com/influxdata/telegraf/plugins/outputs"
|
2015-10-22 16:17:57 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
)
|
|
|
|
|
2016-03-22 16:34:33 +00:00
|
|
|
var (
|
|
|
|
sanitizedChars = strings.NewReplacer("/", "_", "@", "_", " ", "_", "-", "_", ".", "_")
|
|
|
|
|
|
|
|
// Prometheus metric names must match this regex
|
|
|
|
// see https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
|
|
|
metricName = regexp.MustCompile("^[a-zA-Z_:][a-zA-Z0-9_:]*$")
|
|
|
|
|
|
|
|
// Prometheus labels must match this regex
|
|
|
|
// see https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
|
|
|
labelName = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
|
|
)
|
|
|
|
|
2015-10-22 16:17:57 +00:00
|
|
|
type PrometheusClient struct {
|
2016-03-23 13:18:37 +00:00
|
|
|
Listen string
|
|
|
|
SmartStrings bool
|
|
|
|
metrics map[string]*prometheus.UntypedVec
|
2015-10-22 16:17:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var sampleConfig = `
|
2016-02-18 21:26:51 +00:00
|
|
|
## Address to listen on
|
2015-10-22 16:17:57 +00:00
|
|
|
# listen = ":9126"
|
2016-03-23 13:18:37 +00:00
|
|
|
# smart_strings = true
|
2015-10-22 16:17:57 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
func (p *PrometheusClient) Start() error {
|
|
|
|
if p.Listen == "" {
|
2015-10-28 22:19:13 +00:00
|
|
|
p.Listen = "localhost:9126"
|
2015-10-22 16:17:57 +00:00
|
|
|
}
|
2015-10-28 22:19:13 +00:00
|
|
|
|
2015-10-22 16:17:57 +00:00
|
|
|
http.Handle("/metrics", prometheus.Handler())
|
|
|
|
server := &http.Server{
|
2015-10-28 22:19:13 +00:00
|
|
|
Addr: p.Listen,
|
2015-10-22 16:17:57 +00:00
|
|
|
}
|
2015-10-28 22:19:13 +00:00
|
|
|
|
2015-10-22 16:17:57 +00:00
|
|
|
p.metrics = make(map[string]*prometheus.UntypedVec)
|
2015-10-28 22:19:13 +00:00
|
|
|
go server.ListenAndServe()
|
2015-10-22 16:17:57 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PrometheusClient) Stop() {
|
|
|
|
// TODO: Use a listener for http.Server that counts active connections
|
|
|
|
// that can be stopped and closed gracefully
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PrometheusClient) Connect() error {
|
|
|
|
// This service output does not need to make any further connections
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PrometheusClient) Close() error {
|
|
|
|
// This service output does not need to close any of its connections
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PrometheusClient) SampleConfig() string {
|
|
|
|
return sampleConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *PrometheusClient) Description() string {
|
|
|
|
return "Configuration for the Prometheus client to spawn"
|
|
|
|
}
|
|
|
|
|
2016-01-27 23:15:14 +00:00
|
|
|
func (p *PrometheusClient) Write(metrics []telegraf.Metric) error {
|
|
|
|
if len(metrics) == 0 {
|
2015-10-22 16:17:57 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-01-27 23:15:14 +00:00
|
|
|
for _, point := range metrics {
|
2015-12-01 17:08:38 +00:00
|
|
|
key := point.Name()
|
2016-03-22 16:34:33 +00:00
|
|
|
key = sanitizedChars.Replace(key)
|
2015-10-22 16:17:57 +00:00
|
|
|
|
2016-03-22 16:34:33 +00:00
|
|
|
var labels []string
|
2015-10-22 16:17:57 +00:00
|
|
|
l := prometheus.Labels{}
|
2016-03-22 16:34:33 +00:00
|
|
|
for k, v := range point.Tags() {
|
|
|
|
k = sanitizedChars.Replace(k)
|
|
|
|
if len(k) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !labelName.MatchString(k) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
labels = append(labels, k)
|
|
|
|
l[k] = v
|
2015-10-22 16:17:57 +00:00
|
|
|
}
|
|
|
|
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
for n, val := range point.Fields() {
|
2016-03-22 16:34:33 +00:00
|
|
|
n = sanitizedChars.Replace(n)
|
2016-03-08 18:33:57 +00:00
|
|
|
var mname string
|
|
|
|
if n == "value" {
|
|
|
|
mname = key
|
|
|
|
} else {
|
|
|
|
mname = fmt.Sprintf("%s_%s", key, n)
|
|
|
|
}
|
2016-03-22 16:34:33 +00:00
|
|
|
|
|
|
|
if !metricName.MatchString(mname) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
if _, ok := p.metrics[mname]; !ok {
|
|
|
|
p.metrics[mname] = prometheus.NewUntypedVec(
|
|
|
|
prometheus.UntypedOpts{
|
|
|
|
Name: mname,
|
|
|
|
Help: fmt.Sprintf("Telegraf collected point '%s'", mname),
|
|
|
|
},
|
|
|
|
labels,
|
|
|
|
)
|
2016-03-22 16:34:33 +00:00
|
|
|
if err := prometheus.Register(p.metrics[mname]); err != nil {
|
|
|
|
log.Printf("prometheus_client: Metric failed to register with prometheus, %s", err)
|
|
|
|
continue
|
|
|
|
}
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
}
|
|
|
|
|
2015-12-01 17:08:38 +00:00
|
|
|
switch val := val.(type) {
|
|
|
|
default:
|
|
|
|
log.Printf("Prometheus output, unsupported type. key: %s, type: %T\n",
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
mname, val)
|
2016-03-23 13:18:37 +00:00
|
|
|
case string:
|
|
|
|
if !p.SmartStrings {
|
|
|
|
log.Printf("Prometheus output, unsupported type. key: %s, label: %s, type: %T\n",
|
|
|
|
mname, l, val)
|
|
|
|
}
|
|
|
|
// Get metric value
|
|
|
|
m, err := p.metrics[mname].GetMetricWith(l)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR Getting metric in Prometheus output, "+
|
|
|
|
"key: %s, labels: %v,\nerr: %s\n",
|
|
|
|
mname, l, err.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If has dot in val - parse as float, else int
|
|
|
|
if strings.Contains(val, ".") {
|
|
|
|
// Float
|
|
|
|
tval, err := strconv.ParseFloat(val, 64)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Prometheus output, can't convert string to float. key: %s, label: %s, val: %s\n",
|
|
|
|
mname, l, val)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
m.Set(tval)
|
|
|
|
} else {
|
|
|
|
// Int
|
|
|
|
tval, err := strconv.ParseInt(val, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Prometheus output, can't convert string to int. key: %s, label: %s, val: %s\n",
|
|
|
|
mname, l, val)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
m.Set(float64(tval))
|
|
|
|
}
|
2015-10-22 16:17:57 +00:00
|
|
|
case int64:
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
m, err := p.metrics[mname].GetMetricWith(l)
|
2015-12-01 17:08:38 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR Getting metric in Prometheus output, "+
|
|
|
|
"key: %s, labels: %v,\nerr: %s\n",
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
mname, l, err.Error())
|
2015-12-01 17:08:38 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
m.Set(float64(val))
|
2015-10-22 16:17:57 +00:00
|
|
|
case float64:
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
m, err := p.metrics[mname].GetMetricWith(l)
|
2015-12-01 17:08:38 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR Getting metric in Prometheus output, "+
|
|
|
|
"key: %s, labels: %v,\nerr: %s\n",
|
fix prometheus output
if i understand the prometheus data model correctly, the current output
for this plugin is unusable
prometheus only accepts a single value per measurement. prior to this change, the range loop
causes a measurement to end up w/ a random value
for instance:
net,dc=sjc1,grp_dashboard=1,grp_home=1,grp_hwy_fetcher=1,grp_web_admin=1,host=sjc1-b4-8,hw=app,interface=docker0,state=live
bytes_recv=477596i,bytes_sent=152963303i,drop_in=0i,drop_out=0i,err_in=0i,err_out=0i,packets_recv=7231i,packets_sent=11460i
1457121990003778992
this 'net' measurent would have all it's tags copied to prometheus
labels, but any of 152963303, or 0, or 7231 as a value for
'net' depending on which field is last in the map iteration
this change expands the fields into new measurements by appending
the field name to the influxdb measurement name.
ie, the above example results with 'net' dropped and new measurements
to take it's place:
net_bytes_recv
net_bytes_sent
net_drop_in
net_err_in
net_packets_recv
net_packets_sent
i hope this can be merged, i love telegraf's composability of tags and
filtering
2016-03-04 20:05:10 +00:00
|
|
|
mname, l, err.Error())
|
2015-12-01 17:08:38 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
m.Set(val)
|
2015-10-22 16:17:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2016-01-27 21:21:36 +00:00
|
|
|
outputs.Add("prometheus_client", func() telegraf.Output {
|
2015-10-22 16:17:57 +00:00
|
|
|
return &PrometheusClient{}
|
|
|
|
})
|
|
|
|
}
|