626 lines
16 KiB
Go
626 lines
16 KiB
Go
package metrics
|
|
|
|
import (
|
|
"fmt"
|
|
"friendica-exporter/internal/client"
|
|
"friendica-exporter/serverinfo"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/sirupsen/logrus"
|
|
"strconv"
|
|
)
|
|
|
|
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)
|
|
postsInbound = prometheus.NewDesc(
|
|
metricPrefix+"posts_inbound",
|
|
"Number of posts inbound.",
|
|
[]string{"type"}, nil)
|
|
postsOutbound = prometheus.NewDesc(
|
|
metricPrefix+"posts_outbound",
|
|
"Number of posts outbound.",
|
|
[]string{"type"}, nil)
|
|
packetsInbound = prometheus.NewDesc(
|
|
metricPrefix+"packets_inbound",
|
|
"Number of packets inbound.",
|
|
[]string{"protocol"}, nil)
|
|
packetsOutbound = prometheus.NewDesc(
|
|
metricPrefix+"packets_outbound",
|
|
"Number of packets outbound.",
|
|
[]string{"protocol"}, nil)
|
|
usersPending = prometheus.NewDesc(
|
|
metricPrefix+"users_pending",
|
|
"Number of pending users.",
|
|
nil, nil)
|
|
reportsNewest = prometheus.NewDesc(
|
|
metricPrefix+"reports_newest",
|
|
"Contains the datetime about the newest report of this Instance.",
|
|
[]string{"datetime"}, nil)
|
|
reportsOpen = prometheus.NewDesc(
|
|
metricPrefix+"reports_open",
|
|
"Number of open reports.",
|
|
nil, nil)
|
|
reportsClosed = prometheus.NewDesc(
|
|
metricPrefix+"reports_closed",
|
|
"Number of closed reports.",
|
|
nil, nil)
|
|
serverInfoDesc = prometheus.NewDesc(
|
|
metricPrefix+"server_info",
|
|
"Contains meta information about Server as labels. Value is always 1.",
|
|
[]string{"version"}, nil)
|
|
phpInfoDesc = prometheus.NewDesc(
|
|
metricPrefix+"php_info",
|
|
"Contains meta information about PHP as labels. Value is always 1.",
|
|
[]string{"version"}, nil)
|
|
phpMemoryLimitDesc = prometheus.NewDesc(
|
|
metricPrefix+"php_memory_limit_bytes",
|
|
"Configured PHP memory limit in bytes.",
|
|
nil, nil)
|
|
phpMaxUploadSizeDesc = prometheus.NewDesc(
|
|
metricPrefix+"php_upload_max_size_bytes",
|
|
"Configured maximum upload size in bytes.",
|
|
nil, nil)
|
|
phpMaxPostSizeDesc = prometheus.NewDesc(
|
|
metricPrefix+"php_post_max_size_bytes",
|
|
"Configured maximum post size in bytes.",
|
|
nil, nil)
|
|
databaseMaxAllowedPacketDesc = prometheus.NewDesc(
|
|
metricPrefix+"php_post_max_allowed_packet",
|
|
"Configured maximum allowed packet length to send to or receive from the server.",
|
|
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
|
|
ch <- postsInbound
|
|
ch <- postsOutbound
|
|
ch <- packetsInbound
|
|
ch <- packetsOutbound
|
|
ch <- reportsNewest
|
|
ch <- reportsOpen
|
|
ch <- reportsClosed
|
|
ch <- serverInfoDesc
|
|
ch <- phpInfoDesc
|
|
ch <- phpMaxUploadSizeDesc
|
|
ch <- phpMaxPostSizeDesc
|
|
ch <- phpMemoryLimitDesc
|
|
ch <- databaseMaxAllowedPacketDesc
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if err := collectPosts(ch, status); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := collectPacketsPerDirection(ch, status, packetsInbound); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := collectPacketsPerDirection(ch, status, packetsOutbound); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := collectPhpMetrics(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),
|
|
},
|
|
{
|
|
desc: reportsOpen,
|
|
value: float64(status.Reports.Open),
|
|
},
|
|
{
|
|
desc: reportsClosed,
|
|
value: float64(status.Reports.Closed),
|
|
},
|
|
}
|
|
|
|
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
|
|
|
|
reportsInfo := status.Reports
|
|
|
|
metric, err = prometheus.NewConstMetric(reportsNewest, prometheus.GaugeValue, float64(reportsInfo.Newest.Timestamp), reportsInfo.Newest.DateTime)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating metric for %s: %w", reportsNewest, err)
|
|
}
|
|
ch <- metric
|
|
return nil
|
|
}
|
|
|
|
type postMetric struct {
|
|
desc *prometheus.Desc
|
|
value float64
|
|
postType string
|
|
}
|
|
|
|
func collectPosts(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
|
|
metrics := []postMetric{
|
|
{
|
|
desc: postsInbound,
|
|
value: float64(status.Posts.Inbound.Posts),
|
|
postType: "posts",
|
|
},
|
|
{
|
|
desc: postsInbound,
|
|
value: float64(status.Posts.Inbound.Comments),
|
|
postType: "comments",
|
|
},
|
|
{
|
|
desc: postsOutbound,
|
|
value: float64(status.Posts.Outbound.Posts),
|
|
postType: "posts",
|
|
},
|
|
{
|
|
desc: postsOutbound,
|
|
value: float64(status.Posts.Outbound.Comments),
|
|
postType: "comments",
|
|
},
|
|
}
|
|
|
|
for _, m := range metrics {
|
|
metric, err := prometheus.NewConstMetric(m.desc, prometheus.GaugeValue, m.value, m.postType)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating metric for %s: %w", m.desc, err)
|
|
}
|
|
ch <- metric
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type packetMetrics struct {
|
|
desc *prometheus.Desc
|
|
value float64
|
|
protocol string
|
|
}
|
|
|
|
func collectPacketsPerDirection(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo, desc *prometheus.Desc) error {
|
|
var packetInfo serverinfo.PacketCounts
|
|
if desc == packetsInbound {
|
|
packetInfo = status.Packets.Inbound
|
|
} else {
|
|
packetInfo = status.Packets.Outbound
|
|
}
|
|
|
|
metrics := []packetMetrics{
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.ActivityPub),
|
|
protocol: "apub",
|
|
},
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.DFRN),
|
|
protocol: "dfrn",
|
|
},
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.Feed),
|
|
protocol: "feed",
|
|
},
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.Diaspora),
|
|
protocol: "dspr",
|
|
},
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.Mail),
|
|
protocol: "mail",
|
|
},
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.OStatus),
|
|
protocol: "stat",
|
|
},
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.Bluesky),
|
|
protocol: "bsky",
|
|
},
|
|
{
|
|
desc: desc,
|
|
value: float64(packetInfo.Thumblr),
|
|
protocol: "tmbl",
|
|
},
|
|
}
|
|
|
|
for _, m := range metrics {
|
|
metric, err := prometheus.NewConstMetric(m.desc, prometheus.GaugeValue, m.value, m.protocol)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating metric for %s: %w", m.desc, err)
|
|
}
|
|
ch <- metric
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func collectPhpMetrics(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
|
|
|
|
metric, err := prometheus.NewConstMetric(phpInfoDesc, prometheus.GaugeValue, 1, status.Server.PHP.Version)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating metric for %s: %w", phpInfoDesc, err)
|
|
}
|
|
ch <- metric
|
|
|
|
metric, err = prometheus.NewConstMetric(serverInfoDesc, prometheus.GaugeValue, 1, status.Server.Version)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating metric for %s: %w", serverInfoDesc, err)
|
|
}
|
|
ch <- metric
|
|
|
|
metricVal, err := strconv.ParseFloat(status.Server.Database.MaxAllowedPacket, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("error converting value to byte for %s: %w", databaseMaxAllowedPacketDesc, err)
|
|
}
|
|
metric, err = prometheus.NewConstMetric(databaseMaxAllowedPacketDesc, prometheus.GaugeValue, metricVal)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating metric for %s: %w", databaseMaxAllowedPacketDesc, err)
|
|
}
|
|
ch <- metric
|
|
|
|
metrics := []struct {
|
|
desc *prometheus.Desc
|
|
value string
|
|
}{
|
|
{
|
|
desc: phpMemoryLimitDesc,
|
|
value: status.Server.PHP.MemoryLimit,
|
|
},
|
|
{
|
|
desc: phpMaxPostSizeDesc,
|
|
value: status.Server.PHP.PostMaxSize,
|
|
},
|
|
{
|
|
desc: phpMaxUploadSizeDesc,
|
|
value: status.Server.PHP.UploadMaxFilesize,
|
|
},
|
|
}
|
|
|
|
for _, m := range metrics {
|
|
metricVal, err := ConvertMemoryToBytes(m.value)
|
|
if err != nil {
|
|
return fmt.Errorf("error converting value to byte for %s: %w", m.desc, err)
|
|
}
|
|
metric, err = prometheus.NewConstMetric(m.desc, prometheus.GaugeValue, float64(metricVal))
|
|
if err != nil {
|
|
return fmt.Errorf("error creating metric for %s: %w", m.desc, err)
|
|
}
|
|
ch <- metric
|
|
}
|
|
|
|
return nil
|
|
}
|