diff --git a/pkg/instrumentation/instrumentation.go b/pkg/instrumentation/instrumentation.go index 0cc4024ec..f19f671f0 100644 --- a/pkg/instrumentation/instrumentation.go +++ b/pkg/instrumentation/instrumentation.go @@ -18,8 +18,6 @@ import ( "fmt" "sync" - "github.com/prometheus/client_golang/prometheus" - cfgapi "github.com/containers/nri-plugins/pkg/apis/config/v1alpha1/instrumentation" "github.com/containers/nri-plugins/pkg/http" "github.com/containers/nri-plugins/pkg/instrumentation/metrics" @@ -52,11 +50,6 @@ var ( Attribute = tracing.Attribute ) -// RegisterGatherer registers a prometheus metrics gatherer. -func RegisterGatherer(g prometheus.Gatherer) { - metrics.RegisterGatherer(g) -} - // HTTPServer returns our HTTP server. func HTTPServer() *http.Server { return srv @@ -122,9 +115,9 @@ func start() error { if err := metrics.Start( srv.GetMux(), + metrics.WithNamespace("nri"), metrics.WithExporterDisabled(!cfg.PrometheusExport), - metrics.WithServiceName(ServiceName), - metrics.WithPeriod(cfg.ReportPeriod.Duration), + metrics.WithReportPeriod(cfg.ReportPeriod.Duration), ); err != nil { return fmt.Errorf("failed to start metrics: %v", err) } diff --git a/pkg/instrumentation/metrics/metrics.go b/pkg/instrumentation/metrics/metrics.go index f32b57317..96eb69721 100644 --- a/pkg/instrumentation/metrics/metrics.go +++ b/pkg/instrumentation/metrics/metrics.go @@ -15,14 +15,105 @@ package metrics import ( - oc "github.com/containers/nri-plugins/pkg/instrumentation/metrics/opencensus" + "fmt" + "time" + + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/containers/nri-plugins/pkg/http" + logger "github.com/containers/nri-plugins/pkg/log" + "github.com/containers/nri-plugins/pkg/metrics" +) + +type ( + Option func() error ) var ( - RegisterGatherer = oc.RegisterGatherer - WithExporterDisabled = oc.WithExporterDisabled - WithPeriod = oc.WithPeriod - WithServiceName = oc.WithServiceName - Start = oc.Start - Stop = oc.Stop + disabled bool + namespace string + enable []string + poll []string + reportPeriod time.Duration + mux *http.ServeMux + log = logger.Get("metrics") ) + +func WithExporterDisabled(v bool) Option { + return func() error { + disabled = v + return nil + } +} + +func WithNamespace(v string) Option { + return func() error { + namespace = v + return nil + } +} + +func WithReportPeriod(v time.Duration) Option { + return func() error { + reportPeriod = v + return nil + } +} + +func WithMetrics(enable []string, poll []string) Option { + return func() error { + enable = enable + poll = poll + return nil + } +} + +func Start(m *http.ServeMux, options ...Option) error { + Stop() + + for _, opt := range options { + if err := opt(); err != nil { + return err + } + } + + if m == nil { + log.Info("no mux provided, metrics exporter disabled") + return nil + } + + if disabled { + log.Info("metrics exporter disabled") + return nil + } + + log.Info("starting metrics exporter...") + + g, err := metrics.NewGatherer( + metrics.WithNamespace("nri"), + metrics.WithPollInterval(reportPeriod), + metrics.WithMetrics(enable, poll), + ) + if err != nil { + return fmt.Errorf("failed to create metrics gatherer: %v", err) + } + + handlerOpts := promhttp.HandlerOpts{ + ErrorLog: log, + ErrorHandling: promhttp.ContinueOnError, + } + m.Handle("/metrics", promhttp.HandlerFor(g, handlerOpts)) + + mux = m + + return nil +} + +func Stop() { + if mux == nil { + return + } + + mux.Unregister("/metrics") + mux = nil +} diff --git a/pkg/instrumentation/metrics/opencensus/metrics.go b/pkg/instrumentation/metrics/opencensus/metrics.go deleted file mode 100644 index 1073a0fb1..000000000 --- a/pkg/instrumentation/metrics/opencensus/metrics.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright The NRI Plugins Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "contrib.go.opencensus.io/exporter/prometheus" - promcli "github.com/prometheus/client_golang/prometheus" - model "github.com/prometheus/client_model/go" - "go.opencensus.io/stats/view" - - "github.com/containers/nri-plugins/pkg/http" - logger "github.com/containers/nri-plugins/pkg/log" -) - -// Option represents an option which can be applied to metrics. -type Option func(*metrics) error - -type metrics struct { - disabled bool - service string - period time.Duration - exporter *prometheus.Exporter - mux *http.ServeMux -} - -var ( - log = logger.Get("metrics") - mtr = &metrics{ - service: filepath.Base(os.Args[0]), - } -) - -const ( - // HTTP path our mux serves metrics on. - httpMetricsPath = "/metrics" -) - -// WithExporterDisabled can be used to disable the metrics exporter. -func WithExporterDisabled(disabled bool) Option { - return func(m *metrics) error { - m.disabled = disabled - return nil - } -} - -// WithPeriod sets the internal metrics collection period. -func WithPeriod(period time.Duration) Option { - return func(m *metrics) error { - m.period = period - return nil - } -} - -// WithServiceName sets the service name reported for metrics. -func WithServiceName(name string) Option { - return func(m *metrics) error { - m.service = name - return nil - } -} - -// Start metrics exporter. -func Start(mux *http.ServeMux, options ...Option) error { - return mtr.start(mux, options...) -} - -// Stop metrics exporter. -func Stop() { - mtr.shutdown() -} - -func (m *metrics) start(mux *http.ServeMux, options ...Option) error { - m.shutdown() - - for _, opt := range options { - if err := opt(m); err != nil { - return fmt.Errorf("failed to set metrics option: %w", err) - } - } - - if m.disabled { - log.Info("metrics exporter disabled") - return nil - } - - log.Info("starting metrics exporter...") - - exporter, err := prometheus.NewExporter( - prometheus.Options{ - Namespace: strings.ReplaceAll(strings.ToLower(m.service), "-", "_"), - Gatherer: promcli.Gatherers{registeredGatherers}, - OnError: func(err error) { log.Error("prometheus export error: %v", err) }, - }, - ) - if err != nil { - return fmt.Errorf("failed to create prometheus exporter: %w", err) - } - - m.mux = mux - m.exporter = exporter - - m.mux.Handle(httpMetricsPath, m.exporter) - view.RegisterExporter(m.exporter) - view.SetReportingPeriod(m.period) - - return nil -} - -func (m *metrics) shutdown() { - if m.exporter == nil { - return - } - - view.UnregisterExporter(m.exporter) - m.mux.Unregister(httpMetricsPath) - - m.exporter = nil - m.mux = nil -} - -// Our registered prometheus gatherers. -var ( - registeredGatherers = &gatherers{gatherers: promcli.Gatherers{}} -) - -type gatherers struct { - sync.RWMutex - gatherers promcli.Gatherers -} - -func (g *gatherers) register(gatherer promcli.Gatherer) { - g.Lock() - defer g.Unlock() - g.gatherers = append(g.gatherers, gatherer) -} - -func (g *gatherers) Gather() ([]*model.MetricFamily, error) { - g.RLock() - defer g.RUnlock() - return g.gatherers.Gather() -} - -func RegisterGatherer(g promcli.Gatherer) { - registeredGatherers.register(g) -} diff --git a/pkg/log/log.go b/pkg/log/log.go index 6047543f2..ccb6cf2e0 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -77,6 +77,9 @@ type Logger interface { // Fatal formats and emits an error message and os.Exit()'s with status 1. Fatal(format string, args ...interface{}) + // Println to mimick minimal stdlin log.Logger interface. + Println(v ...any) + // DebugBlock formats and emits a multiline debug message. DebugBlock(prefix string, format string, args ...interface{}) // InfoBlock formats and emits a multiline information message. @@ -410,6 +413,10 @@ func (l logger) Panic(format string, args ...interface{}) { panic(msg) } +func (l logger) Println(a ...any) { + l.Info("%s", fmt.Sprintln(a...)) +} + func (l logger) DebugBlock(prefix string, format string, args ...interface{}) { if l.DebugEnabled() { l.block(LevelDebug, prefix, format, args...)