Some checks failed
CI pipeline for ffd-indexer / checks (go test + golangci) (push) Failing after 26s
CI pipeline for ffd-indexer / build binaries + containers (push) Failing after 3s
CI pipeline for ffd-indexer / format (gofmt + nixfmt) (push) Failing after 3s
CI pipeline for ffd-indexer / integration (live scrape + Typesense) (push) Failing after 20s
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
// Package metrics exposes the state of the federated forge index to
|
|
// Prometheus.
|
|
//
|
|
// The ffd-metrics daemon is a long-running process that reads the SQLite
|
|
// store on each scrape and renders it as Prometheus text metrics at
|
|
// /metrics. It never touches the forges itself: the indexer's daily run
|
|
// populates the store, and this daemon merely reflects the current DB state.
|
|
package metrics
|
|
|
|
import (
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"git.xengi.de/ffd/indexer/store"
|
|
)
|
|
|
|
// Collector implements prometheus.Collector by querying the store on every
|
|
// Prometheus scrape. Gather is therefore always fresh but also does one
|
|
// SQLite read per scrape.
|
|
type Collector struct {
|
|
store *store.Store
|
|
|
|
forgeInfo *prometheus.Desc
|
|
forgeOnline *prometheus.Desc
|
|
repositoryInfo *prometheus.Desc
|
|
repositoryLangs *prometheus.Desc
|
|
}
|
|
|
|
// NewCollector creates a Collector backed by st.
|
|
func NewCollector(st *store.Store) *Collector {
|
|
return &Collector{
|
|
store: st,
|
|
forgeInfo: prometheus.NewDesc(
|
|
"ffd_forge_info",
|
|
"Constant gauge with the forge's type and detected software version",
|
|
[]string{"forge_url", "forge_type", "forge_version"},
|
|
nil,
|
|
),
|
|
forgeOnline: prometheus.NewDesc(
|
|
"ffd_forge_online",
|
|
"Whether a forge was reachable and scraped successfully on its last run (1) or not (0).",
|
|
[]string{"forge_url", "forge_type"},
|
|
nil,
|
|
),
|
|
repositoryInfo: prometheus.NewDesc(
|
|
"ffd_repository_info",
|
|
"Constant gauge, one series per stored repository. Count by forge_url to get repositories per forge.",
|
|
[]string{"repo_url", "forge_url", "forge_type"},
|
|
nil,
|
|
),
|
|
repositoryLangs: prometheus.NewDesc(
|
|
"ffd_repository_languages",
|
|
"Constant gauge, one series per (repository, language) pair. Sum by language to get language usage.",
|
|
[]string{"repo_url", "language", "forge_url", "forge_type"},
|
|
nil,
|
|
),
|
|
}
|
|
}
|
|
|
|
// Describe implements prometheus.Collector.
|
|
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
|
|
ch <- c.forgeInfo
|
|
ch <- c.forgeOnline
|
|
ch <- c.repositoryInfo
|
|
ch <- c.repositoryLangs
|
|
}
|
|
|
|
// Collect implements prometheus.Collector. Query errors are surfaced as
|
|
// invalid metrics for each family so the scrape still produces a value (with
|
|
// a 500 if the handler is configured to propagate errors) instead of silently
|
|
// dropping data.
|
|
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
|
|
statuses, err := c.store.ForgeStatuses()
|
|
if err != nil {
|
|
c.emitInvalid(ch, err)
|
|
return
|
|
}
|
|
|
|
for _, st := range statuses {
|
|
online := 0.0
|
|
if st.Online {
|
|
online = 1
|
|
}
|
|
ch <- prometheus.MustNewConstMetric(
|
|
c.forgeInfo,
|
|
prometheus.GaugeValue,
|
|
1,
|
|
st.URL, st.ForgeType, st.Version,
|
|
)
|
|
ch <- prometheus.MustNewConstMetric(
|
|
c.forgeOnline,
|
|
prometheus.GaugeValue,
|
|
online,
|
|
st.URL, st.ForgeType,
|
|
)
|
|
}
|
|
|
|
repos, err := c.store.RepoIdentities()
|
|
if err != nil {
|
|
c.emitInvalid(ch, err)
|
|
return
|
|
}
|
|
|
|
for _, r := range repos {
|
|
forge := []string{r.ForgeURL, r.ForgeType}
|
|
ch <- prometheus.MustNewConstMetric(
|
|
c.repositoryInfo,
|
|
prometheus.GaugeValue,
|
|
1,
|
|
append([]string{r.URL}, forge...)...,
|
|
)
|
|
for _, lang := range r.Languages {
|
|
ch <- prometheus.MustNewConstMetric(
|
|
c.repositoryLangs,
|
|
prometheus.GaugeValue,
|
|
1,
|
|
append([]string{r.URL, lang}, forge...)...,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// emitInvalid emits one invalid metric per metric family for scrape errors.
|
|
func (c *Collector) emitInvalid(ch chan<- prometheus.Metric, err error) {
|
|
for _, d := range []*prometheus.Desc{c.forgeInfo, c.forgeOnline, c.repositoryInfo, c.repositoryLangs} {
|
|
ch <- prometheus.NewInvalidMetric(d, err)
|
|
}
|
|
}
|