blob: 014d2f764479173e1b7c8cd90f7cabfb0202d549 [file] [log] [blame]
/*
* Copyright 2018-2024 Open Networking Foundation (ONF) and the ONF Contributors
* 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 stats
import (
"context"
"fmt"
"net/http"
"time"
"github.com/opencord/voltha-lib-go/v7/pkg/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
cPrefix = "voltha"
)
type PromStatsServer struct {
// To hold the counters which are specific to devices
devCounters *prometheus.CounterVec
// To hold the counters which are NOT tied to specific devices
otherCounters *prometheus.CounterVec
// To hold the durations which are specific to devices
devDurations *prometheus.HistogramVec
// To hold the durations which are NOT tied to specific to devices
otherDurations *prometheus.HistogramVec
}
var StatsServer = PromStatsServer{}
// Start starts the statistics manager with name and makes the collected stats available
// at port p. All the statistics collected by this collector will be appended with "voltha_(name)_"
// when they appear in Prometheus. The function starts a prometheus HTTP listener in the background and does not return
// any errors, the listener is stopped on context cancellation
func (ps *PromStatsServer) Start(ctx context.Context, p int, name CollectorName) {
//log.SetLogger(logging.New())
ps.initializeCollectors(ctx, name)
logger.Infow(ctx, "Starting Statistics HTTP Server", log.Fields{"listeningPort": p})
http.Handle("/metrics", promhttp.Handler())
server := &http.Server{Addr: fmt.Sprintf(":%d", p), Handler: nil}
go func() {
<-ctx.Done()
logger.Infof(ctx, "Shutting down the Statistics HTTP server")
err := server.Shutdown(ctx)
if err != nil {
logger.Errorw(ctx, "Statistics server shutting down failure", log.Fields{"error": err})
}
}()
go func() {
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
logger.Errorw(ctx, "Starting Statistics HTTP server error", log.Fields{"error": err})
}
}()
logger.Infow(ctx, "Started Prometheus listener for statistics on port", log.Fields{"port": p})
}
func (ps *PromStatsServer) initializeCollectors(ctx context.Context, name CollectorName) {
logger.Infof(ctx, "Initializing statistics collector")
collectorName := cPrefix + "_" + name.String()
var (
// in milliseconds
defBuckets = []float64{2, 5, 10, 25, 50, 100, 300, 1000, 5000}
)
ps.devCounters = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: collectorName,
Name: "device_counters",
Help: "Device specific counters",
},
[]string{"device_id", "serial_no", "counter"},
)
ps.otherCounters = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: collectorName,
Name: "counters",
Help: "Non device counters",
},
[]string{"counter"},
)
ps.devDurations = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: collectorName,
Name: "device_durations",
Help: "Time taken in ms to complete a specific task for a specific device",
Buckets: defBuckets,
},
[]string{"device_id", "serial_no", "duration"},
)
ps.otherDurations = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: collectorName,
Name: "durations",
Help: "Time taken in ms to complete a specific task not tied to a device",
Buckets: defBuckets,
},
[]string{"duration"},
)
prometheus.MustRegister(ps.devCounters)
prometheus.MustRegister(ps.otherCounters)
prometheus.MustRegister(ps.devDurations)
prometheus.MustRegister(ps.otherDurations)
}
// CountForDevice counts the number of times the counterName happens for device devId with serial number sn. Each call to Count increments it by one.
func (ps *PromStatsServer) CountForDevice(devId, sn string, counterName DeviceCounter) {
if ps.devCounters != nil {
ps.devCounters.WithLabelValues(devId, sn, counterName.String()).Inc()
}
}
// AddForDevice adds val to counter.
func (ps *PromStatsServer) AddForDevice(devId, sn string, counter DeviceCounter, val float64) {
if ps.devCounters != nil {
ps.devCounters.WithLabelValues(devId, sn, counter.String()).Add(val)
}
}
// CollectDurationForDevice calculates the duration from startTime to time.Now() for device devID with serial number sn.
func (ps *PromStatsServer) CollectDurationForDevice(devID, sn string, dName DeviceDuration, startTime time.Time) {
if ps.otherDurations != nil {
timeSpent := time.Since(startTime)
ps.devDurations.WithLabelValues(devID, sn, dName.String()).Observe(float64(timeSpent.Milliseconds()))
}
}
// Count counts the number of times the counterName happens. Each call to Count increments it by one.
func (ps *PromStatsServer) Count(counter NonDeviceCounter) {
if ps.otherCounters != nil {
ps.otherCounters.WithLabelValues(counter.String()).Inc()
}
}
// Add adds val to counter.
func (ps *PromStatsServer) Add(counter NonDeviceCounter, val float64) {
if ps.otherCounters != nil {
ps.otherCounters.WithLabelValues(counter.String()).Add(val)
}
}
// CollectDuration calculates the duration from startTime to time.Now().
func (ps *PromStatsServer) CollectDuration(dName NonDeviceDuration, startTime time.Time) {
if ps.otherDurations != nil {
timeSpent := time.Since(startTime)
ps.otherDurations.WithLabelValues(dName.String()).Observe(float64(timeSpent.Milliseconds()))
}
}