friendica-exporter/internal/metrics/collector.go
2024-10-27 19:51:26 +01:00

363 lines
9.3 KiB
Go

package metrics
import (
"fmt"
"friendica-exporter/internal/client"
"friendica-exporter/serverinfo"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
)
const (
metricPrefix = "friendica_"
labelErrorCauseOther = "other"
labelErrorCauseAuth = "auth"
labelErrorCauseRateLimit = "ratelimit"
)
var (
updateAvailableDesc = prometheus.NewDesc(
metricPrefix+"update_available",
"Contains information whether a system update is available (0 = no, 1 = yes). The available_version level contains the latest available Friendica version, whereas the version level contains the current installed Friendica version.",
[]string{"version", "available_version"}, nil)
updateStatus = prometheus.NewDesc(
metricPrefix+"update_status",
"Contains information whether a system update was successful (1 = no, 0 = yes).",
nil, nil)
updateDatabaseStatus = prometheus.NewDesc(
metricPrefix+"update_db_status",
"Contains information whether a database update was successful (0 = no, 1 = yes).",
nil, nil)
cronLastExecution = prometheus.NewDesc(
metricPrefix+"cron_last_execution",
"Contains information about the last execution of cron of this Instance.",
[]string{"datetime"}, nil)
workerLastExecution = prometheus.NewDesc(
metricPrefix+"worker_last_execution",
"Contains information about the last execution of worker of this Instance.",
[]string{"datetime"}, nil)
workerJPM = prometheus.NewDesc(
metricPrefix+"worker_jpm",
"Number of jobs per Minute.",
[]string{"frequency"}, nil)
workerTasksTotal = prometheus.NewDesc(
metricPrefix+"worker_tasks_total",
"Number of worker tasks of this Instance.",
[]string{"level"}, nil)
workerTasksActive = prometheus.NewDesc(
metricPrefix+"worker_tasks_active",
"Number of active worker tasks of this Instance.",
[]string{"level"}, nil)
usersTotal = prometheus.NewDesc(
metricPrefix+"users_total",
"Number of total users of this Instance.",
nil, nil)
usersActiveWeek = prometheus.NewDesc(
metricPrefix+"users_active_week",
"Number of active users of the last week.",
nil, nil)
usersActiveMonth = prometheus.NewDesc(
metricPrefix+"users_active_month",
"Number of active users of the last month.",
nil, nil)
usersActiveHalfYear = prometheus.NewDesc(
metricPrefix+"users_active_half_year",
"Number of active users of the last half year.",
nil, nil)
usersPending = prometheus.NewDesc(
metricPrefix+"users_pending",
"Number of pending users.",
nil, nil)
)
type friendicaCollector struct {
log logrus.FieldLogger
infoClient client.InfoClient
upMetric prometheus.Gauge
scrapeErrorsMetric *prometheus.CounterVec
}
func RegisterCollector(log logrus.FieldLogger, infoClient client.InfoClient) error {
c := &friendicaCollector{
log: log,
infoClient: infoClient,
upMetric: prometheus.NewGauge(prometheus.GaugeOpts{
Name: metricPrefix + "up",
Help: "Indicates if the metrics could be scraped by the exporter.",
}),
scrapeErrorsMetric: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: metricPrefix + "scrape_errors_total",
Help: "Counts the number of scrape errors by this collector.",
}, []string{"cause"}),
}
return prometheus.Register(c)
}
func (c *friendicaCollector) Describe(ch chan<- *prometheus.Desc) {
c.upMetric.Describe(ch)
c.scrapeErrorsMetric.Describe(ch)
ch <- updateAvailableDesc
ch <- updateStatus
ch <- updateDatabaseStatus
ch <- cronLastExecution
ch <- workerLastExecution
ch <- workerJPM
ch <- workerTasksTotal
ch <- workerTasksActive
ch <- usersTotal
ch <- usersActiveWeek
ch <- usersActiveMonth
ch <- usersActiveHalfYear
ch <- usersPending
}
func (c *friendicaCollector) Collect(ch chan<- prometheus.Metric) {
if err := c.collectFriendica(ch); err != nil {
c.log.Errorf("Error during scrape: %s", err)
cause := labelErrorCauseOther
if err == client.ErrNotAuthorized {
cause = labelErrorCauseAuth
} else if err == client.ErrRatelimit {
cause = labelErrorCauseRateLimit
}
c.scrapeErrorsMetric.WithLabelValues(cause).Inc()
c.upMetric.Set(0)
} else {
c.upMetric.Set(1)
}
c.upMetric.Collect(ch)
c.scrapeErrorsMetric.Collect(ch)
}
func (c *friendicaCollector) collectFriendica(ch chan<- prometheus.Metric) error {
status, err := c.infoClient()
if err != nil {
return err
}
return readMetrics(ch, status)
}
func readMetrics(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
if err := collectSimpleMetrics(ch, status); err != nil {
return err
}
if err := collectWorkerTotal(ch, status); err != nil {
return err
}
if err := collectJPM(ch, status); err != nil {
return err
}
if err := collectLastExecutions(ch, status); err != nil {
return err
}
if err := collectUpdate(ch, status); err != nil {
return err
}
return nil
}
func collectUpdate(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
updateInfo := status.Update
serverInfo := status.Server
updateAvailableValue := 0.0
// Fix small bug: its indicated as "true" even if there is no real update available.
if updateInfo.Available && serverInfo.Version != updateInfo.AvailableVersion {
updateAvailableValue = 1.0
}
metric, err := prometheus.NewConstMetric(updateAvailableDesc, prometheus.GaugeValue, updateAvailableValue, serverInfo.Version, updateInfo.AvailableVersion)
if err != nil {
return fmt.Errorf("error creating metric for %s: %w", updateAvailableDesc, err)
}
ch <- metric
return nil
}
type simpleMetric struct {
desc *prometheus.Desc
value float64
}
func collectSimpleMetrics(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
metrics := []simpleMetric{
{
desc: updateStatus,
value: float64(status.Update.Status),
},
{
desc: updateDatabaseStatus,
value: float64(status.Update.DatabaseStatus),
},
{
desc: usersTotal,
value: float64(status.Users.Total),
},
{
desc: usersActiveWeek,
value: float64(status.Users.ActiveWeek),
},
{
desc: usersActiveMonth,
value: float64(status.Users.ActiveMonth),
},
{
desc: usersActiveHalfYear,
value: float64(status.Users.ActiveHalfYear),
},
{
desc: usersPending,
value: float64(status.Users.Pending),
},
}
for _, m := range metrics {
metric, err := prometheus.NewConstMetric(m.desc, prometheus.GaugeValue, m.value)
if err != nil {
return fmt.Errorf("error creating metric for %s: %w", m.desc, err)
}
ch <- metric
}
return nil
}
type workerMetric struct {
desc *prometheus.Desc
value float64
level string
}
func collectWorkerTotal(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
metrics := []workerMetric{
{
desc: workerTasksActive,
value: float64(status.Worker.Active.Critical),
level: "critical",
},
{
desc: workerTasksActive,
value: float64(status.Worker.Active.High),
level: "high",
},
{
desc: workerTasksActive,
value: float64(status.Worker.Active.Medium),
level: "medium",
},
{
desc: workerTasksActive,
value: float64(status.Worker.Active.Low),
level: "low",
},
{
desc: workerTasksActive,
value: float64(status.Worker.Active.Negligible),
level: "negligible",
},
{
desc: workerTasksTotal,
value: float64(status.Worker.Total.Critical),
level: "critical",
},
{
desc: workerTasksTotal,
value: float64(status.Worker.Total.High),
level: "high",
},
{
desc: workerTasksTotal,
value: float64(status.Worker.Total.Medium),
level: "medium",
},
{
desc: workerTasksTotal,
value: float64(status.Worker.Total.Low),
level: "low",
},
{
desc: workerTasksTotal,
value: float64(status.Worker.Total.Negligible),
level: "negligible",
},
}
for _, m := range metrics {
metric, err := prometheus.NewConstMetric(m.desc, prometheus.GaugeValue, m.value, m.level)
if err != nil {
return fmt.Errorf("error creating metric for %s: %w", m.desc, err)
}
ch <- metric
}
return nil
}
type jpmMetric struct {
desc *prometheus.Desc
value float64
frequency string
}
func collectJPM(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
metrics := []jpmMetric{
{
desc: workerJPM,
value: float64(status.Worker.JPM.OneMinute),
frequency: "1 minute",
},
{
desc: workerJPM,
value: float64(status.Worker.JPM.ThreeMinutes),
frequency: "3 minutes",
},
{
desc: workerJPM,
value: float64(status.Worker.JPM.FiveMinutes),
frequency: "5 minutes",
},
}
for _, m := range metrics {
metric, err := prometheus.NewConstMetric(m.desc, prometheus.GaugeValue, m.value, m.frequency)
if err != nil {
return fmt.Errorf("error creating metric for %s: %w", m.desc, err)
}
ch <- metric
}
return nil
}
func collectLastExecutions(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
workerInfo := status.Worker
metric, err := prometheus.NewConstMetric(workerLastExecution, prometheus.GaugeValue, float64(workerInfo.LastExecution.Timestamp), workerInfo.LastExecution.DateTime)
if err != nil {
return fmt.Errorf("error creating metric for %s: %w", workerLastExecution, err)
}
ch <- metric
cronInfo := status.Cron
metric, err = prometheus.NewConstMetric(cronLastExecution, prometheus.GaugeValue, float64(cronInfo.LastExecution.Timestamp), cronInfo.LastExecution.DateTime)
if err != nil {
return fmt.Errorf("error creating metric for %s: %w", cronLastExecution, err)
}
ch <- metric
return nil
}