├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── auth ├── collector.go ├── dataaccess └── dataaccess.go ├── error └── error.go ├── main.go └── types ├── lastjob.go ├── scheduledjob.go ├── totalbytes.go └── totalfiles.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # MySQL connection password file 9 | pw/ 10 | 11 | # IDEA configs 12 | .idea/ 13 | 14 | # Binary 15 | bareos_exporter 16 | 17 | # Test binary, build with `go test -c` 18 | *.test 19 | 20 | # Output of the go coverage tool, specifically when used with LiteIDE 21 | *.out 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang as builder 2 | RUN go get -d -v github.com/dreyau/bareos_exporter 3 | WORKDIR /go/src/github.com/dreyau/bareos_exporter 4 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o bareos_exporter . 5 | 6 | FROM busybox:latest 7 | 8 | ENV mysql_port 3306 9 | ENV mysql_server 192.168.3.70 10 | ENV mysql_username monty 11 | ENV endpoint /metrics 12 | ENV port 9625 13 | 14 | WORKDIR /bareos_exporter 15 | COPY --from=builder /go/src/github.com/dreyau/bareos_exporter/bareos_exporter bareos_exporter 16 | 17 | CMD ./bareos_exporter -port $port -endpoint $endpoint -u $mysql_username -h $mysql_server -P $mysql_port -p pw/auth 18 | EXPOSE $port 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yauheni Dretskin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## bareos_exporter 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/dreyau/bareos_exporter)](https://goreportcard.com/report/github.com/dreyau/bareos_exporter) 3 | 4 | [Prometheus](https://github.com/prometheus) exporter for [bareos](https://github.com/bareos) data recovery system 5 | 6 | ### [`Dockerfile`](https://github.com/dreyau/bareos_exporter/blob/master/Dockerfile) 7 | 8 | ### Usage with [docker](https://hub.docker.com/r/dreyau/bareos_exporter) 9 | 1. Create a file containing your mysql password and mount it inside `/bareos_exporter/pw/auth` 10 | 2. **(optional)** [Overwrite](https://docs.docker.com/engine/reference/run/#env-environment-variables) default args using ENV variables 11 | 3. Run docker image as follows 12 | ```bash 13 | docker run --name bareos_exporter -p 9625:9625 -v /your/password/file:/bareos_exporter/pw/auth -d dreyau/bareos_exporter:latest 14 | ``` 15 | ### Metrics 16 | 17 | - Total amout of bytes and files saved 18 | - Latest executed job metrics (level, errors, execution time, bytes and files saved) 19 | - Latest full job (level = F) metrics 20 | - Amount of scheduled jobs 21 | 22 | ### Flags 23 | 24 | Name | Description | Default 25 | --------|---------------------------------------------------------------------------------------------|---------------------- 26 | port | Bareos exporter port | 9625 27 | endpoint| Bareos exporter endpoint. | "/metrics" 28 | u | Username used to access Bareos MySQL Database | "root" 29 | p | Path to file containing your MySQL password. Written inside a file to prevent from leaking. | "./auth" 30 | h | MySQL instance hostname. | "127.0.0.1" 31 | P | MySQL instance port. | "3306" 32 | db | MySQL database name. | "bareos" -------------------------------------------------------------------------------- /auth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dreyau/bareos_exporter/d9654f6fd2ad3f12f670023d6b1f9278e845b2cc/auth -------------------------------------------------------------------------------- /collector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/dreyau/bareos_exporter/dataaccess" 5 | "github.com/prometheus/client_golang/prometheus" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type bareosMetrics struct { 11 | TotalFiles *prometheus.Desc 12 | TotalBytes *prometheus.Desc 13 | LastJobBytes *prometheus.Desc 14 | LastJobFiles *prometheus.Desc 15 | LastJobErrors *prometheus.Desc 16 | LastJobTimestamp *prometheus.Desc 17 | 18 | LastFullJobBytes *prometheus.Desc 19 | LastFullJobFiles *prometheus.Desc 20 | LastFullJobErrors *prometheus.Desc 21 | LastFullJobTimestamp *prometheus.Desc 22 | 23 | ScheduledJob *prometheus.Desc 24 | } 25 | 26 | func bareosCollector() *bareosMetrics { 27 | return &bareosMetrics{ 28 | TotalFiles: prometheus.NewDesc("bareos_files_saved_total", 29 | "Total files saved for server during all backups for hostname combined", 30 | []string{"hostname"}, nil, 31 | ), 32 | TotalBytes: prometheus.NewDesc("bareos_bytes_saved_total", 33 | "Total bytes saved for server during all backups for hostname combined", 34 | []string{"hostname"}, nil, 35 | ), 36 | LastJobBytes: prometheus.NewDesc("bareos_last_backup_job_bytes_saved_total", 37 | "Total bytes saved during last backup for hostname", 38 | []string{"hostname", "level"}, nil, 39 | ), 40 | LastJobFiles: prometheus.NewDesc("bareos_last_backup_job_files_saved_total", 41 | "Total files saved during last backup for hostname", 42 | []string{"hostname", "level"}, nil, 43 | ), 44 | LastJobErrors: prometheus.NewDesc("bareos_last_backup_job_errors_occurred_while_saving_total", 45 | "Total errors occurred during last backup for hostname", 46 | []string{"hostname", "level"}, nil, 47 | ), 48 | LastJobTimestamp: prometheus.NewDesc("bareos_last_backup_job_unix_timestamp", 49 | "Execution timestamp of last backup for hostname", 50 | []string{"hostname", "level"}, nil, 51 | ), 52 | LastFullJobBytes: prometheus.NewDesc("bareos_last_full_backup_job_bytes_saved_total", 53 | "Total bytes saved during last full backup (Level = F) for hostname", 54 | []string{"hostname"}, nil, 55 | ), 56 | LastFullJobFiles: prometheus.NewDesc("bareos_last_full_backup_job_files_saved_total", 57 | "Total files saved during last full backup (Level = F) for hostname", 58 | []string{"hostname"}, nil, 59 | ), 60 | LastFullJobErrors: prometheus.NewDesc("bareos_last_full_backup_job_errors_occurred_while_saving_total", 61 | "Total errors occurred during last full backup (Level = F) for hostname", 62 | []string{"hostname"}, nil, 63 | ), 64 | LastFullJobTimestamp: prometheus.NewDesc("bareos_last_full_backup_job_unix_timestamp", 65 | "Execution timestamp of last full backup (Level = F) for hostname", 66 | []string{"hostname"}, nil, 67 | ), 68 | ScheduledJob: prometheus.NewDesc("bareos_scheduled_jobs_total", 69 | "Probable execution timestamp of next backup for hostname", 70 | []string{"hostname"}, nil, 71 | ), 72 | } 73 | } 74 | 75 | func (collector *bareosMetrics) Describe(ch chan<- *prometheus.Desc) { 76 | ch <- collector.TotalFiles 77 | ch <- collector.TotalBytes 78 | ch <- collector.LastJobBytes 79 | ch <- collector.LastJobFiles 80 | ch <- collector.LastJobErrors 81 | ch <- collector.LastJobTimestamp 82 | ch <- collector.LastFullJobBytes 83 | ch <- collector.LastFullJobFiles 84 | ch <- collector.LastFullJobErrors 85 | ch <- collector.LastFullJobTimestamp 86 | ch <- collector.ScheduledJob 87 | } 88 | 89 | func (collector *bareosMetrics) Collect(ch chan<- prometheus.Metric) { 90 | connection, connectionErr := dataaccess.GetConnection(connectionString) 91 | 92 | defer connection.DB.Close() 93 | 94 | if connectionErr != nil { 95 | log.Error(connectionErr) 96 | return 97 | } 98 | 99 | var servers, getServerListErr = connection.GetServerList() 100 | 101 | if getServerListErr != nil { 102 | log.Error(getServerListErr) 103 | return 104 | } 105 | 106 | for _, server := range servers { 107 | serverFiles, filesErr := connection.TotalFiles(server) 108 | serverBytes, bytesErr := connection.TotalBytes(server) 109 | lastServerJob, jobErr := connection.LastJob(server) 110 | lastFullServerJob, fullJobErr := connection.LastFullJob(server) 111 | scheduledJob, scheduledJobErr := connection.ScheduledJobs(server) 112 | 113 | if filesErr != nil || bytesErr != nil || jobErr != nil || fullJobErr != nil || scheduledJobErr != nil{ 114 | log.Info(server) 115 | } 116 | 117 | if filesErr != nil { 118 | log.Error(filesErr) 119 | } 120 | 121 | if bytesErr != nil { 122 | log.Error(bytesErr) 123 | } 124 | 125 | if jobErr != nil { 126 | log.Error(jobErr) 127 | } 128 | 129 | if fullJobErr != nil { 130 | log.Error(fullJobErr) 131 | } 132 | 133 | if scheduledJobErr != nil { 134 | log.Error(scheduledJobErr) 135 | } 136 | 137 | ch <- prometheus.MustNewConstMetric(collector.TotalFiles, prometheus.CounterValue, float64(serverFiles.Files), server) 138 | ch <- prometheus.MustNewConstMetric(collector.TotalBytes, prometheus.CounterValue, float64(serverBytes.Bytes), server) 139 | 140 | ch <- prometheus.MustNewConstMetric(collector.LastJobBytes, prometheus.CounterValue, float64(lastServerJob.JobBytes), server, lastServerJob.Level) 141 | ch <- prometheus.MustNewConstMetric(collector.LastJobFiles, prometheus.CounterValue, float64(lastServerJob.JobFiles), server, lastServerJob.Level) 142 | ch <- prometheus.MustNewConstMetric(collector.LastJobErrors, prometheus.CounterValue, float64(lastServerJob.JobErrors), server, lastServerJob.Level) 143 | ch <- prometheus.MustNewConstMetric(collector.LastJobTimestamp, prometheus.CounterValue, float64(lastServerJob.JobDate.Unix()), server, lastServerJob.Level) 144 | 145 | ch <- prometheus.MustNewConstMetric(collector.LastFullJobBytes, prometheus.CounterValue, float64(lastFullServerJob.JobBytes), server) 146 | ch <- prometheus.MustNewConstMetric(collector.LastFullJobFiles, prometheus.CounterValue, float64(lastFullServerJob.JobFiles), server) 147 | ch <- prometheus.MustNewConstMetric(collector.LastFullJobErrors, prometheus.CounterValue, float64(lastFullServerJob.JobErrors), server) 148 | ch <- prometheus.MustNewConstMetric(collector.LastFullJobTimestamp, prometheus.CounterValue, float64(lastFullServerJob.JobDate.Unix()), server) 149 | 150 | 151 | ch <- prometheus.MustNewConstMetric(collector.ScheduledJob, prometheus.CounterValue, float64(scheduledJob.ScheduledJobs), server) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /dataaccess/dataaccess.go: -------------------------------------------------------------------------------- 1 | package dataaccess 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/dreyau/bareos_exporter/types" 7 | _ "github.com/go-sql-driver/mysql" // Keep driver import and usage (in GetConnection) in one file 8 | "time" 9 | ) 10 | 11 | type connection struct { 12 | DB *sql.DB 13 | } 14 | 15 | // GetConnection opens a new db connection 16 | func GetConnection(connectionString string) (*connection, error) { 17 | var connection connection 18 | var err error 19 | 20 | connection.DB, err = sql.Open("mysql", connectionString) 21 | 22 | return &connection, err 23 | } 24 | 25 | // GetServerList reads all servers with scheduled backups for current date 26 | func (connection connection) GetServerList() ([]string, error) { 27 | date := fmt.Sprintf("%s%%", time.Now().Format("2006-01-02")) 28 | results, err := connection.DB.Query("SELECT DISTINCT Name FROM job WHERE SchedTime LIKE ?", date) 29 | 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | var servers []string 35 | 36 | for results.Next() { 37 | var server string 38 | err = results.Scan(&server) 39 | servers = append(servers, server) 40 | } 41 | 42 | return servers, err 43 | } 44 | 45 | // TotalBytes returns total bytes saved for a server since the very first backup 46 | func (connection connection) TotalBytes(server string) (*types.TotalBytes, error) { 47 | results, err := connection.DB.Query("SELECT SUM(JobBytes) FROM job WHERE Name=?", server) 48 | 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | var totalBytes types.TotalBytes 54 | if results.Next() { 55 | err = results.Scan(&totalBytes.Bytes) 56 | results.Close() 57 | } 58 | 59 | return &totalBytes, err 60 | } 61 | 62 | // TotalFiles returns total files saved for a server since the very first backup 63 | func (connection connection) TotalFiles(server string) (*types.TotalFiles, error) { 64 | results, err := connection.DB.Query("SELECT SUM(JobFiles) FROM job WHERE Name=?", server) 65 | 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | var totalFiles types.TotalFiles 71 | if results.Next() { 72 | err = results.Scan(&totalFiles.Files) 73 | results.Close() 74 | } 75 | 76 | return &totalFiles, err 77 | } 78 | 79 | // LastJob returns metrics for latest executed server backup 80 | func (connection connection) LastJob(server string) (*types.LastJob, error) { 81 | results, err := connection.DB.Query("SELECT Level,JobBytes,JobFiles,JobErrors,DATE(StartTime) AS JobDate FROM job WHERE Name LIKE ? ORDER BY StartTime DESC LIMIT 1", server) 82 | 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | var lastJob types.LastJob 88 | if results.Next() { 89 | err = results.Scan(&lastJob.Level, &lastJob.JobBytes, &lastJob.JobFiles, &lastJob.JobErrors, &lastJob.JobDate) 90 | results.Close() 91 | } 92 | 93 | return &lastJob, err 94 | } 95 | 96 | // LastJob returns metrics for latest executed server backup with Level F 97 | func (connection connection) LastFullJob(server string) (*types.LastJob, error) { 98 | results, err := connection.DB.Query("SELECT Level,JobBytes,JobFiles,JobErrors,DATE(StartTime) AS JobDate FROM job WHERE Name LIKE ? AND Level = 'F' ORDER BY StartTime DESC LIMIT 1", server) 99 | 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | var lastJob types.LastJob 105 | if results.Next() { 106 | err = results.Scan(&lastJob.Level, &lastJob.JobBytes, &lastJob.JobFiles, &lastJob.JobErrors, &lastJob.JobDate) 107 | results.Close() 108 | } 109 | 110 | return &lastJob, err 111 | } 112 | 113 | // ScheduledTime returns amount of scheduled jobs 114 | func (connection connection) ScheduledJobs(server string) (*types.ScheduledJob, error) { 115 | date := fmt.Sprintf("%s%%", time.Now().Format("2006-01-02")) 116 | results, err := connection.DB.Query("SELECT COUNT(DATE(SchedTime)) AS JobsScheduled FROM job WHERE Name LIKE ? AND SchedTime >= ?", server, date) 117 | 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | var schedJob types.ScheduledJob 123 | if results.Next() { 124 | err = results.Scan(&schedJob.ScheduledJobs) 125 | results.Close() 126 | } 127 | 128 | return &schedJob, err 129 | } 130 | -------------------------------------------------------------------------------- /error/error.go: -------------------------------------------------------------------------------- 1 | package error 2 | 3 | // Check panics if an error occurred 4 | func Check(err error) { 5 | if err != nil { 6 | panic(err.Error()) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/dreyau/bareos_exporter/error" 5 | 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | 12 | "github.com/prometheus/client_golang/prometheus" 13 | "github.com/prometheus/client_golang/prometheus/promhttp" 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | var connectionString string 18 | 19 | var ( 20 | exporterPort = flag.Int("port", 9625, "Bareos exporter port") 21 | exporterEndpoint = flag.String("endpoint", "/metrics", "Bareos exporter endpoint") 22 | mysqlUser = flag.String("u", "root", "Bareos MySQL username") 23 | mysqlAuthFile = flag.String("p", "./auth", "MySQL password file path") 24 | mysqlHostname = flag.String("h", "127.0.0.1", "MySQL hostname") 25 | mysqlPort = flag.String("P", "3306", "MySQL port") 26 | mysqlDb = flag.String("db", "bareos", "MySQL database name") 27 | ) 28 | 29 | func init() { 30 | flag.Usage = func() { 31 | fmt.Println("Usage: bareos_exporter [ ... ]\n\nParameters:") 32 | fmt.Println() 33 | flag.PrintDefaults() 34 | } 35 | } 36 | 37 | func main() { 38 | flag.Parse() 39 | 40 | pass, err := ioutil.ReadFile(*mysqlAuthFile) 41 | error.Check(err) 42 | 43 | connectionString = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", *mysqlUser, strings.TrimSpace(string(pass)), *mysqlHostname, *mysqlPort, *mysqlDb) 44 | 45 | collector := bareosCollector() 46 | prometheus.MustRegister(collector) 47 | 48 | http.Handle(*exporterEndpoint, promhttp.Handler()) 49 | log.Info("Beginning to serve on port ", *exporterPort) 50 | 51 | addr := fmt.Sprintf(":%d", *exporterPort) 52 | log.Fatal(http.ListenAndServe(addr, nil)) 53 | } 54 | -------------------------------------------------------------------------------- /types/lastjob.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "time" 4 | 5 | // LastJob models query results for job metrics 6 | type LastJob struct { 7 | Level string `json:"level"` 8 | JobBytes int `json:"job-bytes"` 9 | JobFiles int `json:"job-files"` 10 | JobErrors int `json:"job-errors"` 11 | JobDate time.Time `json:"job-date"` 12 | } 13 | -------------------------------------------------------------------------------- /types/scheduledjob.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // ScheduledJob models query result of the time a job is about to be executed 4 | type ScheduledJob struct { 5 | ScheduledJobs int `json:"scheduled-jobs"` 6 | } 7 | -------------------------------------------------------------------------------- /types/totalbytes.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // TotalBytes models query result of saved bytes sum for a server 4 | type TotalBytes struct { 5 | Bytes int `json:"files"` 6 | } 7 | -------------------------------------------------------------------------------- /types/totalfiles.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // TotalFiles models query result of saved files sum for a server 4 | type TotalFiles struct { 5 | Files int `json:"files"` 6 | } 7 | --------------------------------------------------------------------------------