Export merlin sync statistics into a format readable by Prometheus.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
3.3 KiB

package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
"log"
"path/filepath"
"os"
"time"
"io/ioutil"
"strings"
"strconv"
"errors"
)
//
// HELPERS
//
var ErrNoInformation = errors.New("no project information")
type ProjectInfo struct {
LastSyncStart time.Time
Duration int
ExitCode int
}
func getProjectInfo(path string) (*ProjectInfo, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
components := strings.Split(string(data), ",")
if len(components) == 3 {
lastSyncUnix, err := strconv.ParseInt(components[0], 10, 64)
if err != nil {
return nil, err
}
lastSync := time.Unix(lastSyncUnix, 0)
duration, err := strconv.Atoi(components[1])
if err != nil {
return nil, err
}
exitCode, err := strconv.Atoi(components[2])
if err != nil {
return nil, err
}
return &ProjectInfo{
LastSyncStart: lastSync,
Duration: duration,
ExitCode: exitCode,
}, nil
}
return nil, ErrNoInformation
}
//
// METRICS
//
var (
stampsDirectory = "/home/mirror/merlin/stamps"
namespace = "merlin"
lastSyncStart = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "last_sync_start"),
"Timestamp the last synchronization attempt was started.",
[]string{"project"}, nil,
)
lastSyncDuration = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "last_sync_duration"),
"Duration of the last sync.",
[]string{"project"}, nil,
)
lastSyncExitCode = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "last_sync_exit_code"),
"Duration of the last sync.",
[]string{"project"}, nil,
)
)
//
// EXPORTER
//
type Exporter struct {
stampsDirectory string
}
func NewExporter(stampsDirectory string) *Exporter {
return &Exporter{
stampsDirectory: stampsDirectory,
}
}
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- lastSyncStart
ch <- lastSyncDuration
ch <- lastSyncExitCode
}
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
log.Printf("collecting")
err := filepath.Walk(e.stampsDirectory, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
projectName := info.Name()
// Fetch the project information
projectInfo, err := getProjectInfo(path)
if err != nil && err == ErrNoInformation {
log.Printf("no project information for %s", projectName)
return nil
} else if err != nil {
log.Printf("error processing %s: %v", projectName, err)
return nil
}
ch <- prometheus.MustNewConstMetric(
lastSyncStart, prometheus.GaugeValue, float64(projectInfo.LastSyncStart.Unix()), projectName,
)
ch <- prometheus.MustNewConstMetric(
lastSyncDuration, prometheus.GaugeValue, float64(projectInfo.Duration), projectName,
)
ch <- prometheus.MustNewConstMetric(
lastSyncExitCode, prometheus.GaugeValue, float64(projectInfo.ExitCode), projectName,
)
return nil
})
if err != nil {
log.Printf("failed to walk directory: %v", err)
}
}
func main() {
// Setup the exporter
exporter := NewExporter(stampsDirectory)
prometheus.MustRegister(exporter)
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":9101", nil))
}