Skip to content

Commit abf0b00

Browse files
Add support for prometheus export and push (#57)
* generation of prometheus file from backup summary * add labels to metrics * prometheus groups of profiles * allow generic status by registering an interface * docs: add prometheus in README * display an error instead of a panic * add user defined prometheus labels
1 parent f58116b commit abf0b00

33 files changed

+851
-236
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@
2929
/*.log
3030

3131
status.json
32+
*.prom

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
project_name: resticprofile
22
env:
3-
- RESTIC_VERSION=0.12.0
3+
- RESTIC_VERSION=0.12.1
44

55
before:
66
hooks:

Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ TESTS=./...
2222
COVERAGE_FILE=coverage.out
2323

2424
BUILD=build/
25-
RESTIC_VERSION=0.12.0
26-
GO_VERSION=1.16
2725

2826
BUILD_DATE=`date`
2927
BUILD_COMMIT=`git rev-parse HEAD`

README.md

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ With resticprofile:
2929
* You can generate cryptographically secure random keys to use as a restic key file
3030
* You can easily schedule backups, retentions and checks (works for *systemd*, *crond*, *launchd* and *windows task scheduler*)
3131
* You can generate a simple status file to send to some monitoring software and make sure your backups are running fine
32-
* **[new for v0.10.0]** You can use a template syntax in your configuration file
33-
* **[new for v0.11.0]** You can generate scheduled tasks using *crond*
32+
* You can use a template syntax in your configuration file
33+
* You can generate scheduled tasks using *crond*
3434
* **[new for v0.12.0]** Get backup statistics in your status file
3535
* **[new for v0.14.0]** Automatically clear up [stale locks](#locks)
36+
* **[new for v0.15.0]** Export a **prometheus** file after a backup, or send the report to a push gateway automatically
3637

3738
The configuration file accepts various formats:
3839
* [TOML](https://github.com/toml-lang/toml) : configuration file with extension _.toml_ and _.conf_ to keep compatibility with versions before 0.6.0
@@ -101,6 +102,7 @@ For the rest of the documentation, I'll be showing examples using different form
101102
* [Changing schedule\-permission from user to system, or system to user](#changing-schedule-permission-from-user-to-system-or-system-to-user)
102103
* [Status file for easy monitoring](#status-file-for-easy-monitoring)
103104
* [Extended status](#extended-status)
105+
* [Prometheus](#prometheus)
104106
* [Variable expansion in configuration file](#variable-expansion-in-configuration-file)
105107
* [Pre\-defined variables](#pre-defined-variables)
106108
* [Hand\-made variables](#hand-made-variables)
@@ -1395,6 +1397,8 @@ Here's an example of a generated file, where you can see that the last `check` f
13951397
}
13961398
```
13971399

1400+
## Extended status
1401+
13981402
Note: In the backup section above you can see some fields like `files_new`, `files_total`, etc. This information is only available when resticprofile's output is either *not* sent to the terminal (e.g. redirected) or when you add the flag `extended-status` to your backup configuration.
13991403
This is a technical limitation to ensure restic displays terminal output correctly.
14001404

@@ -1405,8 +1409,6 @@ This is a technical limitation to ensure restic displays terminal output correct
14051409
- stderr
14061410
- duration
14071411

1408-
## Extended status
1409-
14101412
`extended-status` is **not set by default because it hides any output from restic**
14111413

14121414
```yaml
@@ -1421,6 +1423,89 @@ profile:
14211423

14221424
```
14231425

1426+
# Prometheus
1427+
1428+
resticprofile can generate a prometheus file, or send the report to a push gateway. For now, only a `backup` command will generate a report.
1429+
Here's a configuration example with both options to generate a file and send to a push gateway:
1430+
1431+
```yaml
1432+
root:
1433+
inherit: default
1434+
prometheus-save-to-file: "root.prom"
1435+
prometheus-push: "http://localhost:9091/"
1436+
backup:
1437+
extended-status: true
1438+
no-error-on-warning: true
1439+
source:
1440+
- /
1441+
```
1442+
1443+
Please note you need to set `extended-status` to `true` if you want all the available metrics. See [Extended status](#extended-status) for more information.
1444+
1445+
Here's an example of the generated prometheus file:
1446+
1447+
```
1448+
# HELP resticprofile_backup_added_bytes Total number of bytes added to the repository.
1449+
# TYPE resticprofile_backup_added_bytes gauge
1450+
resticprofile_backup_added_bytes{profile="root"} 35746
1451+
# HELP resticprofile_backup_dir_changed Number of directories with changes.
1452+
# TYPE resticprofile_backup_dir_changed gauge
1453+
resticprofile_backup_dir_changed{profile="root"} 9
1454+
# HELP resticprofile_backup_dir_new Number of new directories added to the backup.
1455+
# TYPE resticprofile_backup_dir_new gauge
1456+
resticprofile_backup_dir_new{profile="root"} 0
1457+
# HELP resticprofile_backup_dir_unmodified Number of directories unmodified since last backup.
1458+
# TYPE resticprofile_backup_dir_unmodified gauge
1459+
resticprofile_backup_dir_unmodified{profile="root"} 314
1460+
# HELP resticprofile_backup_duration_seconds The backup duration (in seconds).
1461+
# TYPE resticprofile_backup_duration_seconds gauge
1462+
resticprofile_backup_duration_seconds{profile="root"} 0.946567354
1463+
# HELP resticprofile_backup_files_changed Number of files with changes.
1464+
# TYPE resticprofile_backup_files_changed gauge
1465+
resticprofile_backup_files_changed{profile="root"} 3
1466+
# HELP resticprofile_backup_files_new Number of new files added to the backup.
1467+
# TYPE resticprofile_backup_files_new gauge
1468+
resticprofile_backup_files_new{profile="root"} 0
1469+
# HELP resticprofile_backup_files_processed Total number of files scanned by the backup for changes.
1470+
# TYPE resticprofile_backup_files_processed gauge
1471+
resticprofile_backup_files_processed{profile="root"} 3925
1472+
# HELP resticprofile_backup_files_unmodified Number of files unmodified since last backup.
1473+
# TYPE resticprofile_backup_files_unmodified gauge
1474+
resticprofile_backup_files_unmodified{profile="root"} 3922
1475+
# HELP resticprofile_backup_processed_bytes Total number of bytes scanned for changes.
1476+
# TYPE resticprofile_backup_processed_bytes gauge
1477+
resticprofile_backup_processed_bytes{profile="root"} 3.8524672e+07
1478+
# HELP resticprofile_backup_status Backup status: 0=fail, 1=warning, 2=success.
1479+
# TYPE resticprofile_backup_status gauge
1480+
resticprofile_backup_status{profile="root"} 1
1481+
# HELP resticprofile_build_info resticprofile build information.
1482+
# TYPE resticprofile_build_info gauge
1483+
resticprofile_build_info{goversion="go1.16.6",version="0.15.0-dev"} 1
1484+
1485+
```
1486+
1487+
## User defined labels
1488+
1489+
You can add your own prometheus labels. Please note they will be applied to **all** the metrics.
1490+
Here's an example:
1491+
1492+
```yaml
1493+
root:
1494+
inherit: default
1495+
prometheus-save-to-file: "root.prom"
1496+
prometheus-push: "http://localhost:9091/"
1497+
prometheus-labels:
1498+
- host: {{ .Hostname }}
1499+
backup:
1500+
extended-status: true
1501+
no-error-on-warning: true
1502+
source:
1503+
- /
1504+
```
1505+
1506+
which will add the `host` label to all your metrics.
1507+
1508+
14241509
# Variable expansion in configuration file
14251510

14261511
You might want to reuse the same configuration (or bits of it) on different environments. One way of doing it is to create a generic configuration where specific bits will be replaced by a variable.
@@ -1914,6 +1999,8 @@ Flags used by resticprofile only
19141999
* **run-after**: string OR list of strings
19152000
* **run-after-fail**: string OR list of strings
19162001
* **status-file**: string
2002+
* **prometheus-save-to-file**: string
2003+
* **prometheus-push**: string
19172004

19182005
Flags passed to the restic command line
19192006

command_error.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
type commandError struct {
10+
scd shellCommandDefinition
11+
stderr string
12+
err error
13+
}
14+
15+
func newCommandError(command shellCommandDefinition, stderr string, err error) *commandError {
16+
return &commandError{
17+
scd: command,
18+
stderr: stderr,
19+
err: err,
20+
}
21+
}
22+
23+
func (c *commandError) Error() string {
24+
return c.err.Error()
25+
}
26+
27+
func (c *commandError) Commandline() string {
28+
args := ""
29+
if c.scd.args != nil && len(c.scd.args) > 0 {
30+
args = fmt.Sprintf(" \"%s\"", strings.Join(c.scd.args, "\" \""))
31+
}
32+
return fmt.Sprintf("\"%s\"%s", c.scd.command, args)
33+
}
34+
35+
func (c *commandError) Stderr() string {
36+
return c.stderr
37+
}
38+
39+
func (c *commandError) ExitCode() (int, error) {
40+
if exitError, ok := asExitError(c.err); ok {
41+
return exitError.ExitCode(), nil
42+
} else {
43+
return 0, errors.New("exit code not available")
44+
}
45+
}

config/flag.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,6 @@ func stringifyValue(value reflect.Value) ([]string, bool) {
123123
return stringifyValue(value.Elem())
124124

125125
default:
126-
panic(fmt.Errorf("unexpected type %s", value.Kind()))
126+
return []string{fmt.Sprintf("ERROR: unexpected type %s", value.Kind())}, false
127127
}
128128
}

config/flag_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import (
88
"github.com/stretchr/testify/assert"
99
)
1010

11-
func TestPointerValueShouldPanic(t *testing.T) {
11+
func TestPointerValueShouldReturnErrorMessage(t *testing.T) {
1212
concrete := "test"
1313
value := &concrete
14-
assert.PanicsWithError(t, "unexpected type ptr", func() {
15-
stringifyValueOf(value)
16-
})
14+
argValue, _ := stringifyValueOf(value)
15+
assert.Equal(t, []string{"ERROR: unexpected type ptr"}, argValue)
1716
}
1817

1918
func TestNilValueFlag(t *testing.T) {

config/profile.go

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,36 @@ import (
1010

1111
// Profile contains the whole profile configuration
1212
type Profile struct {
13-
config *Config
14-
Name string
15-
Description string `mapstructure:"description"`
16-
Quiet bool `mapstructure:"quiet" argument:"quiet"`
17-
Verbose bool `mapstructure:"verbose" argument:"verbose"`
18-
Repository string `mapstructure:"repository" argument:"repo"`
19-
PasswordFile string `mapstructure:"password-file" argument:"password-file"`
20-
CacheDir string `mapstructure:"cache-dir" argument:"cache-dir"`
21-
CACert string `mapstructure:"cacert" argument:"cacert"`
22-
TLSClientCert string `mapstructure:"tls-client-cert" argument:"tls-client-cert"`
23-
Initialize bool `mapstructure:"initialize"`
24-
Inherit string `mapstructure:"inherit"`
25-
Lock string `mapstructure:"lock"`
26-
ForceLock bool `mapstructure:"force-inactive-lock"`
27-
RunBefore []string `mapstructure:"run-before"`
28-
RunAfter []string `mapstructure:"run-after"`
29-
RunAfterFail []string `mapstructure:"run-after-fail"`
30-
StatusFile string `mapstructure:"status-file"`
31-
OtherFlags map[string]interface{} `mapstructure:",remain"`
32-
Environment map[string]string `mapstructure:"env"`
33-
Backup *BackupSection `mapstructure:"backup"`
34-
Retention *RetentionSection `mapstructure:"retention"`
35-
Check *OtherSectionWithSchedule `mapstructure:"check"`
36-
Prune *OtherSectionWithSchedule `mapstructure:"prune"`
37-
Snapshots map[string]interface{} `mapstructure:"snapshots"`
38-
Forget *OtherSectionWithSchedule `mapstructure:"forget"`
39-
Mount map[string]interface{} `mapstructure:"mount"`
13+
config *Config
14+
Name string
15+
Description string `mapstructure:"description"`
16+
Quiet bool `mapstructure:"quiet" argument:"quiet"`
17+
Verbose bool `mapstructure:"verbose" argument:"verbose"`
18+
Repository string `mapstructure:"repository" argument:"repo"`
19+
PasswordFile string `mapstructure:"password-file" argument:"password-file"`
20+
CacheDir string `mapstructure:"cache-dir" argument:"cache-dir"`
21+
CACert string `mapstructure:"cacert" argument:"cacert"`
22+
TLSClientCert string `mapstructure:"tls-client-cert" argument:"tls-client-cert"`
23+
Initialize bool `mapstructure:"initialize"`
24+
Inherit string `mapstructure:"inherit"`
25+
Lock string `mapstructure:"lock"`
26+
ForceLock bool `mapstructure:"force-inactive-lock"`
27+
RunBefore []string `mapstructure:"run-before"`
28+
RunAfter []string `mapstructure:"run-after"`
29+
RunAfterFail []string `mapstructure:"run-after-fail"`
30+
StatusFile string `mapstructure:"status-file"`
31+
PrometheusSaveToFile string `mapstructure:"prometheus-save-to-file"`
32+
PrometheusPush string `mapstructure:"prometheus-push"`
33+
PrometheusLabels map[string]string `mapstructure:"prometheus-labels"`
34+
OtherFlags map[string]interface{} `mapstructure:",remain"`
35+
Environment map[string]string `mapstructure:"env"`
36+
Backup *BackupSection `mapstructure:"backup"`
37+
Retention *RetentionSection `mapstructure:"retention"`
38+
Check *OtherSectionWithSchedule `mapstructure:"check"`
39+
Prune *OtherSectionWithSchedule `mapstructure:"prune"`
40+
Snapshots map[string]interface{} `mapstructure:"snapshots"`
41+
Forget *OtherSectionWithSchedule `mapstructure:"forget"`
42+
Mount map[string]interface{} `mapstructure:"mount"`
4043
}
4144

4245
// BackupSection contains the specific configuration to the 'backup' command

examples/dev.yaml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ default:
2424
repository: "/Volumes/RAMDisk/{{ .Profile.Name }}"
2525
lock: "/Volumes/RAMDisk/resticprofile-{{ .Profile.Name }}.lock"
2626

27+
space:
28+
description: Repository contains space
29+
initialize: false
30+
password-file: key
31+
repository: "/Volumes/RAMDisk/with space"
32+
2733
documents:
2834
inherit: default
2935
backup:
@@ -80,7 +86,7 @@ self:
8086
force-inactive-lock: true
8187
initialize: true
8288
inherit: default
83-
status-file: /Volumes/RAMDisk/status.json
89+
# status-file: /Volumes/RAMDisk/status.json
8490
backup:
8591
extended-status: false
8692
check-before: false
@@ -104,6 +110,24 @@ self:
104110
schedule: "weekly"
105111
schedule-priority: standard
106112

113+
prom:
114+
force-inactive-lock: true
115+
initialize: true
116+
inherit: default
117+
prometheus-save-to-file: "self.prom"
118+
prometheus-push: "http://localhost:9091/"
119+
prometheus-labels:
120+
- host: {{ .Hostname }}
121+
status-file: /Volumes/RAMDisk/status.json
122+
backup:
123+
extended-status: true
124+
no-error-on-warning: true
125+
source:
126+
- {{ .CurrentDir }}
127+
{{ template "tags" . }}
128+
# exclude:
129+
# - examples/private
130+
107131
system:
108132
initialize: true
109133
no-cache: true

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/hashicorp/go-version v1.3.0 // indirect
1515
github.com/mackerelio/go-osstat v0.2.0
1616
github.com/mitchellh/mapstructure v1.4.1
17+
github.com/prometheus/client_golang v1.11.0
1718
github.com/rickb777/date v1.15.3
1819
github.com/shirou/gopsutil/v3 v3.21.6
1920
github.com/smartystreets/assertions v1.0.0 // indirect

0 commit comments

Comments
 (0)