Initial commit
This commit is contained in:
commit
a78752b496
9 changed files with 319 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
prometheus-borg-exporter
|
||||
test
|
14
Makefile
Normal file
14
Makefile
Normal file
|
@ -0,0 +1,14 @@
|
|||
.PHONY: all build clean
|
||||
|
||||
PROJECT_NAME = $(shell head -n1 go.mod | cut -d'/' -f2)
|
||||
|
||||
|
||||
all: build
|
||||
|
||||
# Build static executable
|
||||
build:
|
||||
CGO_ENABLED=0 go build
|
||||
strip $(PROJECT_NAME)
|
||||
|
||||
clean:
|
||||
rm $(PROJECT_NAME)
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Prometheus Borg(Backup) exporter
|
||||
|
||||
Simple [Prometheus](https://prometheus.io/) exporter for [BorgBackup](https://www.borgbackup.org/).
|
||||
|
||||
Rewritten in go from https://code.recycled.cloud/RecycledCloud/prometheus-borgbackup-exporter.
|
||||
It has been rewritten mainly to enable building a static binary but also to support ipv6.
|
||||
Additional features have also been added like `last_archive_time`.
|
||||
|
||||
## Building
|
||||
|
||||
To build, you'll need at least the [go toolchain](https://go.dev/doc/install) installed.
|
||||
On Ubuntu you can generally have the latest version by running:
|
||||
|
||||
```sh
|
||||
snap install go --classic
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
go build
|
||||
```
|
||||
|
||||
Note this executable is dynamically linked and not stripped.
|
||||
|
||||
### Static executable
|
||||
|
||||
To build a static executable, you'll need `make` installed. You can then simply run:
|
||||
|
||||
```sh
|
||||
make
|
||||
```
|
||||
|
||||
## Useful links:
|
||||
|
||||
- [Writing a go exporter](https://prometheus.io/docs/guides/go-application/)
|
||||
- [Writing (Prometheus) exporters guidelines](https://prometheus.io/docs/instrumenting/writing_exporters/)
|
||||
- [Prometheus's `client_golang` library](https://github.com/prometheus/client_golang)
|
||||
|
15
go.mod
Normal file
15
go.mod
Normal file
|
@ -0,0 +1,15 @@
|
|||
module code.recycled.cloud/prometheus-borg-exporter
|
||||
|
||||
go 1.22.1
|
||||
|
||||
require github.com/prometheus/client_golang v1.19.0
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
)
|
20
go.sum
Normal file
20
go.sum
Normal file
|
@ -0,0 +1,20 @@
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
50
info.go
Normal file
50
info.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
Cache struct {
|
||||
Stats struct {
|
||||
Total_chunks float64
|
||||
Total_csize float64
|
||||
Total_size float64
|
||||
Total_unique_chunks float64
|
||||
Unique_csize float64
|
||||
Unique_size float64
|
||||
}
|
||||
}
|
||||
Repository struct {
|
||||
Last_modified string
|
||||
}
|
||||
}
|
||||
|
||||
func (info *Info) LastmodUnix() float64 {
|
||||
lastmod, err := time.Parse("2006-01-02T15:04:05.999999", info.Repository.Last_modified)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return float64(lastmod.Unix())
|
||||
}
|
||||
|
||||
func GetInfo(path string) (*Info, error) {
|
||||
cmd := exec.Command("borg", "info", "--json", path)
|
||||
cmd.Env = append(cmd.Env, "BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes")
|
||||
|
||||
stdout, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := &Info{}
|
||||
err = json.Unmarshal([]byte(stdout), data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
44
list.go
Normal file
44
list.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
type List struct {
|
||||
Archives []struct {
|
||||
Time string
|
||||
}
|
||||
}
|
||||
|
||||
func (list *List) LastArchiveUnix() float64 {
|
||||
if len(list.Archives) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
last, err := time.Parse("2006-01-02T15:04:05.999999", list.Archives[len(list.Archives)-1].Time)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return float64(last.Unix())
|
||||
}
|
||||
|
||||
func GetList(path string) (*List, error) {
|
||||
cmd := exec.Command("borg", "list", "--json", path)
|
||||
cmd.Env = append(cmd.Env, "BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes")
|
||||
|
||||
stdout, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := &List{}
|
||||
err = json.Unmarshal([]byte(stdout), data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
92
main.go
Normal file
92
main.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.0-alpha.0"
|
||||
|
||||
const LISTEN_ADDR = ":9403"
|
||||
const INTERVAL = 30 * time.Minute
|
||||
|
||||
var backupDir = flag.String("backup-dir", "/srv/backups", "Directory where the backups are located")
|
||||
var version_flag = flag.Bool("version", false, "Shows the program version")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *version_flag {
|
||||
fmt.Printf("%v\n", VERSION)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
log.Printf("Backup directory is: %v\n", *backupDir)
|
||||
|
||||
m := NewMetrics(reg)
|
||||
|
||||
go RecordMetrics(*m)
|
||||
|
||||
http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
|
||||
|
||||
log.Printf("Listening on %v ...\n", LISTEN_ADDR)
|
||||
|
||||
log.Fatal(http.ListenAndServe(LISTEN_ADDR, nil))
|
||||
}
|
||||
|
||||
func RecordMetrics(m Metrics) {
|
||||
for {
|
||||
entries, err := os.ReadDir(*backupDir)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") {
|
||||
log.Printf(">> Ignoring %v\n", entry.Name())
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(*backupDir, entry.Name())
|
||||
|
||||
info, err := GetInfo(path)
|
||||
if err != nil {
|
||||
log.Printf(">> Could not get info about %v: %v\n", path, err)
|
||||
continue
|
||||
}
|
||||
list, err := GetList(path)
|
||||
if err != nil {
|
||||
log.Printf(">> Could not get archive list from %v: %v\n", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
stats := info.Cache.Stats
|
||||
|
||||
log.Printf("> Got info for: %v\n", path)
|
||||
m.ArchiveCount.With(prometheus.Labels{"repo_name": entry.Name()}).Set(float64(len(list.Archives)))
|
||||
m.LastArchiveTime.With(prometheus.Labels{"repo_name": entry.Name()}).Set(list.LastArchiveUnix())
|
||||
m.LastModified.With(prometheus.Labels{"repo_name": entry.Name()}).Set(info.LastmodUnix())
|
||||
m.TotalChunks.With(prometheus.Labels{"repo_name": entry.Name()}).Set(stats.Total_chunks)
|
||||
m.TotalCsize.With(prometheus.Labels{"repo_name": entry.Name()}).Set(stats.Total_csize)
|
||||
m.TotalSize.With(prometheus.Labels{"repo_name": entry.Name()}).Set(stats.Total_size)
|
||||
m.TotalUniqueChunks.With(prometheus.Labels{"repo_name": entry.Name()}).Set(stats.Total_unique_chunks)
|
||||
m.UniqueCsize.With(prometheus.Labels{"repo_name": entry.Name()}).Set(stats.Unique_csize)
|
||||
m.UniqueSize.With(prometheus.Labels{"repo_name": entry.Name()}).Set(stats.Unique_size)
|
||||
}
|
||||
|
||||
log.Printf("> Waiting %v\n", INTERVAL)
|
||||
time.Sleep(INTERVAL)
|
||||
}
|
||||
}
|
43
metrics.go
Normal file
43
metrics.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type Metrics struct {
|
||||
ArchiveCount prometheus.GaugeVec
|
||||
LastArchiveTime prometheus.GaugeVec
|
||||
LastModified prometheus.GaugeVec
|
||||
TotalChunks prometheus.GaugeVec
|
||||
TotalCsize prometheus.GaugeVec
|
||||
TotalSize prometheus.GaugeVec
|
||||
TotalUniqueChunks prometheus.GaugeVec
|
||||
UniqueCsize prometheus.GaugeVec
|
||||
UniqueSize prometheus.GaugeVec
|
||||
}
|
||||
|
||||
func NewMetrics(reg prometheus.Registerer) *Metrics {
|
||||
metrics := &Metrics{
|
||||
ArchiveCount: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "archive_count", Help: "Number of archive for this repository"}, []string{"repo_name"}),
|
||||
LastArchiveTime: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "last_archive", Help: "UNIX timestamp of the last archive"}, []string{"repo_name"}),
|
||||
LastModified: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "last_modified", Help: "Last modified UNIX timestamp"}, []string{"repo_name"}),
|
||||
TotalChunks: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "total_chunks", Help: "Number of chunks"}, []string{"repo_name"}),
|
||||
TotalCsize: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "total_csize", Help: "Total compressed and encrypted size of all chunks"}, []string{"repo_name"}),
|
||||
TotalSize: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "total_size", Help: "Total uncompressed size of all chunks"}, []string{"repo_name"}),
|
||||
TotalUniqueChunks: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "total_unique_chunks", Help: "Number of unique chunks"}, []string{"repo_name"}),
|
||||
UniqueCsize: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "unique_csize", Help: "Compressed and encrypted size of all chunks"}, []string{"repo_name"}),
|
||||
UniqueSize: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "borg", Subsystem: "repository", Name: "unique_size", Help: "Uncompressed size of all chunks"}, []string{"repo_name"}),
|
||||
}
|
||||
|
||||
reg.MustRegister(metrics.ArchiveCount)
|
||||
reg.MustRegister(metrics.LastArchiveTime)
|
||||
reg.MustRegister(metrics.LastModified)
|
||||
reg.MustRegister(metrics.TotalChunks)
|
||||
reg.MustRegister(metrics.TotalCsize)
|
||||
reg.MustRegister(metrics.TotalSize)
|
||||
reg.MustRegister(metrics.TotalUniqueChunks)
|
||||
reg.MustRegister(metrics.UniqueCsize)
|
||||
reg.MustRegister(metrics.UniqueSize)
|
||||
|
||||
return metrics
|
||||
}
|
Loading…
Reference in a new issue