diff --git a/go.mod b/go.mod index 9349e7b..548c592 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,15 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/openconfig/goyang v1.4.0 github.com/openconfig/ygot v0.29.0 + github.com/prometheus/client_golang v1.16.0 github.com/rs/zerolog v1.29.1 github.com/spf13/viper v1.16.0 ) require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect @@ -28,9 +31,13 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/openconfig/gnmi v0.9.1 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1c11dde..bfbfbd6 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -171,6 +175,8 @@ github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZb github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/openconfig/gnmi v0.0.0-20200414194230-1597cc0f2600/go.mod h1:M/EcuapNQgvzxo1DDXHK4tx3QpYM/uG4l591v33jG2A= @@ -189,7 +195,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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 v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -548,8 +562,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/internal/api/router/endpoints.go b/internal/api/router/endpoints.go index f69b2f0..b003bfc 100644 --- a/internal/api/router/endpoints.go +++ b/internal/api/router/endpoints.go @@ -21,6 +21,12 @@ func healthCheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprintf(w, `{"status": "ok"}`) } +func prometheusMetrics(h http.Handler) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + h.ServeHTTP(w, r) + } +} + // getAFKEnabled endpoint returns all AFK enabled devices. // They are supposed to be managed by AFK, meaning the configuration should be applied periodically. func (m *Manager) getAFKEnabled(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { diff --git a/internal/api/router/manager.go b/internal/api/router/manager.go index 3aacd9d..97883f6 100644 --- a/internal/api/router/manager.go +++ b/internal/api/router/manager.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog/log" "github.com/criteo/data-aggregation-api/internal/api/auth" @@ -45,6 +46,7 @@ func (m *Manager) ListenAndServe(ctx context.Context, address string, port int) router := httprouter.New() + router.GET("/metrics", prometheusMetrics(promhttp.Handler())) router.GET("/api/health", healthCheck) router.GET("/v1/devices/:hostname/afk_enabled", withAuth.Wrap(m.getAFKEnabled)) router.GET("/v1/devices/:hostname/openconfig", withAuth.Wrap(m.getDeviceOpenConfig)) diff --git a/internal/job/job.go b/internal/job/job.go index 20ab4b6..740b04f 100644 --- a/internal/job/job.go +++ b/internal/job/job.go @@ -12,6 +12,7 @@ import ( "github.com/criteo/data-aggregation-api/internal/config" "github.com/criteo/data-aggregation-api/internal/convertor/device" "github.com/criteo/data-aggregation-api/internal/ingestor/repository" + "github.com/criteo/data-aggregation-api/internal/metrics" "github.com/criteo/data-aggregation-api/internal/report" ) @@ -134,6 +135,7 @@ func RunBuild(reportCh chan report.Message) (map[string]*device.Device, report.P // StartBuildLoop starts the build in an infinite loop. func StartBuildLoop(deviceRepo router.DevicesRepository, reports *report.Repository) { + metricsRegistry := metrics.NewRegistry() for { var wg sync.WaitGroup reports.StartNewReport() @@ -147,12 +149,14 @@ func StartBuildLoop(deviceRepo router.DevicesRepository, reports *report.Reposit reports.UpdateStatus(report.InProgress) if devs, stats, err := RunBuild(reportCh); err != nil { + metricsRegistry.BuildFailed() reports.UpdateStatus(report.Failed) reports.UpdatePerformanceStats(stats) log.Error().Err(err).Msg("build failed") } else { deviceRepo.Set(devs) log.Info().Msg("build successful") + metricsRegistry.BuildSuccessful() reports.UpdateStatus(report.Success) reports.UpdatePerformanceStats(stats) reports.MarkAsSuccessful() diff --git a/internal/metrics/registry.go b/internal/metrics/registry.go new file mode 100644 index 0000000..d3fdb17 --- /dev/null +++ b/internal/metrics/registry.go @@ -0,0 +1,49 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +type Registry struct { + lastBuildStatus *prometheus.GaugeVec + buildTotal *prometheus.CounterVec +} + +func NewRegistry() Registry { + return Registry{ + lastBuildStatus: promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "build_status", + Help: "Last completed build status, 0=Failed, 1=Success", + }, + []string{}, + ), + + buildTotal: promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "completed_build_total", + Help: "Total number of completed build", + }, + []string{"success"}, + ), + } +} + +// BuildSuccessful updates all Prometheus metrics related to build. +// +// `build_status` counter is set to 1. +// `completed_build_total` increases with success label set to true. +func (r *Registry) BuildSuccessful() { + r.lastBuildStatus.WithLabelValues().Set(1) + r.buildTotal.WithLabelValues("true").Inc() +} + +// BuildFailed updates all Prometheus metrics related to build. +// +// `build_status` counter is set to 0. +// `completed_build_total` increases with success label set to false. +func (r *Registry) BuildFailed() { + r.lastBuildStatus.WithLabelValues().Set(0) + r.buildTotal.WithLabelValues("false").Inc() +}