From 9346c11f37a8a8e9633dbac6a0a4c63de0f4f6b7 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Wed, 17 Oct 2018 12:06:46 +0300 Subject: [PATCH 1/2] Add support for prometheus --- config/config.go | 6 +++- go.mod | 9 +++++ go.sum | 18 ++++++++++ main.go | 8 ++++- stats/init.go | 5 +++ stats/prometheus.go | 80 +++++++++++++++++++++++++++++++++++++++++++++ stats/server.go | 2 ++ 7 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 stats/prometheus.go diff --git a/config/config.go b/config/config.go index a52f76a..27f5ad8 100644 --- a/config/config.go +++ b/config/config.go @@ -38,6 +38,9 @@ type Config struct { TagsFormat statsd.TagFormat Enabled bool } + Prometheus struct { + Prefix string + } Secret []byte AdTag []byte @@ -116,7 +119,7 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo bindIP, publicIPv4, publicIPv6, statsIP net.IP, bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16, statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string, - statsdTags map[string]string, + statsdTags map[string]string, prometheusPrefix string, secureOnly bool, secret, adtag []byte) (*Config, error) { secureMode := secureOnly @@ -174,6 +177,7 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo ReadBufferSize: int(readBufferSize), WriteBufferSize: int(writeBufferSize), } + conf.Prometheus.Prefix = prometheusPrefix if statsdIP != "" { conf.StatsD.Enabled = true diff --git a/go.mod b/go.mod index 18ed04a..217fe11 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,29 @@ require ( github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/beevik/ntp v0.2.0 + github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.0 github.com/gofrs/uuid v3.1.0+incompatible + github.com/gogo/protobuf v1.1.1 // indirect + github.com/golang/protobuf v1.2.0 // indirect github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect github.com/kr/pretty v0.1.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/pkg/errors v0.8.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v0.9.0 + github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect + github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37 // indirect + github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d // indirect github.com/stretchr/testify v1.2.2 go.uber.org/atomic v1.3.2 // indirect go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.9.1 golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/statsd.v2 v2.0.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect diff --git a/go.sum b/go.sum index 33453b3..0e1b3b7 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,18 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZq github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea h1:g2k+8WR7cHch4g0tBDhfiEvAp7fXxTNBiD1oC1Oxj3E= github.com/juju/errors v0.0.0-20181012004132-a4583d0a56ea/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= @@ -21,10 +27,20 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.0 h1:tXuTFVHC03mW0D+Ua1Q2d1EAVqLTuggX50V0VLICCzY= +github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37 h1:Y7YdJ9Xb3MoQOzAWXnDunAJYpvhVwZdTirNfGUgPKaA= +github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= @@ -35,6 +51,8 @@ go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= diff --git a/main.go b/main.go index 2d5bdcd..3a12956 100644 --- a/main.go +++ b/main.go @@ -110,6 +110,12 @@ var ( Envar("MTG_STATSD_TAGS"). StringMap() + prometheusPrefix = app.Flag("prometheus-prefix", + "Which namespace to use to send stats to Prometheus."). + Envar("MTG_PROMETHEUS_PREFIX"). + Default("mtg"). + String() + writeBufferSize = app.Flag("write-buffer", "Write buffer size in bytes. You can think about it as a buffer from client to Telegram."). Short('w'). @@ -151,7 +157,7 @@ func main() { // nolint: gocyclo *bindIP, *publicIPv4, *publicIPv6, *statsIP, *bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort, *statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat, - *statsdTags, *secureOnly, + *statsdTags, *prometheusPrefix, *secureOnly, *secret, *adtag, ) if err != nil { diff --git a/stats/init.go b/stats/init.go index 7b43a28..ba303d7 100644 --- a/stats/init.go +++ b/stats/init.go @@ -15,6 +15,11 @@ func Init(conf *config.Config) error { } go client.run() } + prometheus, err := newPrometheus(conf) + if err != nil { + return errors.Annotate(err, "Cannot initialize prometheus client") + } + go prometheus.run() go NewStats(conf).start() go startServer(conf) diff --git a/stats/prometheus.go b/stats/prometheus.go new file mode 100644 index 0000000..00490a5 --- /dev/null +++ b/stats/prometheus.go @@ -0,0 +1,80 @@ +package stats + +import ( + "time" + + "github.com/juju/errors" + "github.com/prometheus/client_golang/prometheus" + + "github.com/9seconds/mtg/config" +) + +const prometheusPollTime = time.Second + +type prometheusExporter struct { + connections *prometheus.GaugeVec + traffic *prometheus.GaugeVec + speed *prometheus.GaugeVec + crashes prometheus.Gauge +} + +func (p *prometheusExporter) run() { + for range time.Tick(prometheusPollTime) { + instance := GetStats() + + p.connections.WithLabelValues("abridged", "v4").Set(float64(instance.Connections.Abridged.IPv4)) + p.connections.WithLabelValues("abridged", "v6").Set(float64(instance.Connections.Abridged.IPv6)) + p.connections.WithLabelValues("intermediate", "v4").Set(float64(instance.Connections.Intermediate.IPv4)) + p.connections.WithLabelValues("intermediate", "v6").Set(float64(instance.Connections.Intermediate.IPv6)) + p.connections.WithLabelValues("secure", "v4").Set(float64(instance.Connections.Secure.IPv4)) + p.connections.WithLabelValues("secure", "v6").Set(float64(instance.Connections.Secure.IPv6)) + p.traffic.WithLabelValues("ingress").Set(float64(instance.Traffic.ingress)) + p.traffic.WithLabelValues("egress").Set(float64(instance.Traffic.egress)) + p.speed.WithLabelValues("ingress").Set(float64(instance.Speed.ingress)) + p.speed.WithLabelValues("egress").Set(float64(instance.Speed.egress)) + p.crashes.Set(float64(instance.Crashes)) + } +} + +func newPrometheus(conf *config.Config) (*prometheusExporter, error) { + connections := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: conf.Prometheus.Prefix, + Name: "connections", + Help: "Current number of connections to the proxy.", + }, []string{"type", "protocol"}) + traffic := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: conf.Prometheus.Prefix, + Name: "traffic", + Help: "Traffic passed through the proxy in bytes.", + }, []string{"direction"}) + speed := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: conf.Prometheus.Prefix, + Name: "speed", + Help: "Current throughput in bytes per second.", + }, []string{"direction"}) + crashes := prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: conf.Prometheus.Prefix, + Name: "crashes", + Help: "How many crashes happened.", + }) + + if err := prometheus.Register(connections); err != nil { + return nil, errors.Annotate(err, "Cannot register connections collector") + } + if err := prometheus.Register(traffic); err != nil { + return nil, errors.Annotate(err, "cannot register traffic collector") + } + if err := prometheus.Register(speed); err != nil { + return nil, errors.Annotate(err, "cannot register speed collector") + } + if err := prometheus.Register(crashes); err != nil { + return nil, errors.Annotate(err, "cannot register crashes collector") + } + + return &prometheusExporter{ + connections: connections, + traffic: traffic, + speed: speed, + crashes: crashes, + }, nil +} diff --git a/stats/server.go b/stats/server.go index b4a0fef..9d96b02 100644 --- a/stats/server.go +++ b/stats/server.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" + "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/zap" "github.com/9seconds/mtg/config" @@ -32,6 +33,7 @@ func startServer(conf *config.Config) { log.Errorw("Cannot encode json", "error", err) } }) + http.Handle("/prometheus/", promhttp.Handler()) if err := http.ListenAndServe(conf.StatAddr(), nil); err != nil { log.Fatalw("Stats server has been stopped", "error", err) From a9d0ca1e90ec724451b9c154f072f685fd310123 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Wed, 17 Oct 2018 12:53:16 +0300 Subject: [PATCH 2/2] Describe and document prometheus integration --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 6410061..d88ce2a 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ supported environment variables: | `MTG_STATSD_PREFIX` | `--statsd-prefix` | `mtg` | Which bucket prefix we should use. For example, if you set `mtg`, then metric `traffic.ingress` would be send as `mtg.traffic.ingress`. | | `MTG_STATSD_TAGS_FORMAT` | `--statsd-tags-format` | | Which tags format we should use. By default, we are using default vanilla statsd tags format but if you want to send directly to InfluxDB or Datadog, please specify it there. Possible options are `influxdb` and `datadog`. | | `MTG_STATSD_TAGS` | `--statsd-tags` | | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs. | +| `MTG_PROMETHEUS_PREFIX` | `--prometheus-prefix` | `mtg` | Which namespace should be used for prometheus metrics. | | `MTG_BUFFER_WRITE` | `-w`, `--write-buffer` | `65536` | The size of TCP write buffer in bytes. Write buffer is the buffer for messages which are going from client to Telegram. | | `MTG_BUFFER_READ` | `-r`, `--read-buffer` | `131072` | The size of TCP read buffer in bytes. Read buffer is the buffer for messages from Telegram to client. | | `MTG_SECURE_ONLY` | `-s`, `--secure-only` | `false` | Support only clients with secure mode (i.e only clients with dd-secrets). | @@ -235,3 +236,14 @@ All metrics are gauges. Here is the list of metrics and their meaning: All metrics are prefixed with given prefix. Default prefix is `mtg`. With such prefix metric name `traffic.ingress`, for example, would be `mtg.traffic.ingress`. + + +# Prometheus integration + +[Prometheus](https://prometheus.io) integration comes out of +the box, you do not need to setup anything special. Prometheus +scrape endpoint lives on the same IP/port where generic stats +service (`http://${MTG_STATS_IP}:${MTG_STATS_PORT}`) but on +`/prometheus` path. So, if you access http stats service as `curl +http://localhost:3129/`, then your prometheus endpoint is `curl +http://localhost:3129/prometheus/`.