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)) }