Skip to content

Commit ae0b4c1

Browse files
author
Jef Spaleta
authored
Merge pull request #15 from sensu/feature/metrics-option
WIP: metrics output support
2 parents 6cd1e67 + 8c9652d commit ae0b4c1

File tree

4 files changed

+184
-12
lines changed

4 files changed

+184
-12
lines changed

.github/workflows/lint.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ jobs:
1313
- name: Checkout code
1414
uses: actions/checkout@v2
1515
- name: Run golangci-lint
16-
uses: actions-contrib/golangci-lint@v1
16+
uses: golangci/golangci-lint-action@v2

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Versioning](http://semver.org/spec/v2.0.0.html).
88
## Unreleased
99

1010
### Added
11+
- Added --metrics option to change output into something Sensu agent can register as tagged metrics
12+
- Added --metrics-format option to make tagged metrics format selectable
13+
- Added --tags option to make it possible to add additional tags for all metrics
1114
- Added --human-readable option to support ignoring human-readable option like df unix/linux command
1215

1316
## [0.4.2] - 2021-03-31

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ Flags:
4646
-r, --include-read-only Include read-only filesystems (default false)
4747
-f, --fail-on-error Fail and exit on errors getting file system usage (e.g. permission denied) (default false)
4848
-H, --human-readable print sizes in powers of 1024 (default false)
49+
--metrics Output metrics instead of human readable output
50+
--metrics-format string Metrics output format, supports opentsdb_line or prometheus_text (default "opentsdb_line")
51+
--tags strings Comma separated list of additional metrics tags using key=value format.
4952
-h, --help help for check-disk-usage
5053
5154
Use "check-disk-usage [command] --help" for more information about a command.
@@ -79,6 +82,7 @@ continue to check the remaining file systems as expected.
7982
* The `--human-readable` (False by default) option determines if you prefer
8083
to display sizes of different drives in a human format. (Like df Unix/linux
8184
command.)
85+
* The `--metrics` (False by default) option will change text output to conform to selected metrics format (`--metrics-format`)
8286

8387
## Configuration
8488

main.go

+176-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package main
22

33
import (
44
"fmt"
5+
"strconv"
56
"strings"
7+
"time"
68

79
human "github.com/dustin/go-humanize"
810
"github.com/sensu-community/sensu-plugin-sdk/sensu"
@@ -23,10 +25,57 @@ type Config struct {
2325
IncludeReadOnly bool
2426
FailOnError bool
2527
HumanReadable bool
28+
MetricsMode bool
29+
ExtraTags []string
30+
}
31+
32+
type MetricGroup struct {
33+
Comment string
34+
Type string
35+
Name string
36+
Metrics []Metric
37+
}
38+
39+
func (g *MetricGroup) AddMetric(tags map[string]string, value float64, timeNow int64) {
40+
g.Metrics = append(g.Metrics, Metric{
41+
Tags: tags,
42+
Timestamp: timeNow,
43+
Value: value,
44+
})
45+
}
46+
func (g *MetricGroup) Output() {
47+
var output string
48+
metricName := strings.Replace(g.Name, ".", "_", -1)
49+
fmt.Printf("# HELP %s [%s] %s\n", metricName, g.Type, g.Comment)
50+
fmt.Printf("# TYPE %s %s\n", metricName, g.Type)
51+
for _, m := range g.Metrics {
52+
tagStr := ""
53+
for tag, tvalue := range m.Tags {
54+
if len(tagStr) > 0 {
55+
tagStr = tagStr + ","
56+
}
57+
tagStr = tagStr + tag + "=\"" + tvalue + "\""
58+
}
59+
if len(tagStr) > 0 {
60+
tagStr = "{" + tagStr + "}"
61+
}
62+
output = strings.Join(
63+
[]string{metricName + tagStr, fmt.Sprintf("%v", m.Value), strconv.FormatInt(m.Timestamp, 10)}, " ")
64+
fmt.Println(output)
65+
}
66+
fmt.Println("")
67+
}
68+
69+
type Metric struct {
70+
Tags map[string]string
71+
Timestamp int64
72+
Value float64
2673
}
2774

2875
var (
29-
plugin = Config{
76+
tags = map[string]string{}
77+
extraTags = map[string]string{}
78+
plugin = Config{
3079
PluginConfig: sensu.PluginConfig{
3180
Name: "check-disk-usage",
3281
Short: "Cross platform disk usage check for Sensu",
@@ -125,6 +174,22 @@ var (
125174
Usage: "print sizes in powers of 1024 (default false)",
126175
Value: &plugin.HumanReadable,
127176
},
177+
{
178+
Path: "metrics",
179+
Env: "",
180+
Argument: "metrics",
181+
Default: false,
182+
Usage: "Output metrics instead of human readable output",
183+
Value: &plugin.MetricsMode,
184+
},
185+
{
186+
Path: "tags",
187+
Env: "",
188+
Argument: "tags",
189+
Default: []string{},
190+
Usage: "Comma separated list of additional metrics tags using key=value format.",
191+
Value: &plugin.ExtraTags,
192+
},
128193
}
129194
)
130195

@@ -143,6 +208,15 @@ func checkArgs(event *types.Event) (int, error) {
143208
if plugin.Warning >= plugin.Critical {
144209
return sensu.CheckStateCritical, fmt.Errorf("--warning value can not be greater than or equal to --critical value")
145210
}
211+
for _, tagString := range plugin.ExtraTags {
212+
fmt.Println(tagString)
213+
parts := strings.Split(tagString, `=`)
214+
if len(parts) == 2 {
215+
extraTags[parts[0]] = parts[1]
216+
} else {
217+
return sensu.CheckStateCritical, fmt.Errorf("Failed to parse input tag: %s", tagString)
218+
}
219+
}
146220
return sensu.CheckStateOK, nil
147221
}
148222

@@ -152,12 +226,56 @@ func executeCheck(event *types.Event) (int, error) {
152226
warnings int
153227
)
154228

229+
timeNow := time.Now().Unix()
155230
parts, err := disk.Partitions(plugin.IncludePseudo)
156231
if err != nil {
157232
return sensu.CheckStateCritical, fmt.Errorf("Failed to get partitions, error: %v", err)
158233
}
159234

235+
metricGroups := map[string]*MetricGroup{
236+
"disk.critical": &MetricGroup{
237+
Name: "disk.critical",
238+
Type: "GAUGE",
239+
Comment: "non-zero value indicates mountpoint usage is above critical threshold",
240+
Metrics: []Metric{},
241+
},
242+
"disk.warning": &MetricGroup{
243+
Name: "disk.warning",
244+
Type: "GAUGE",
245+
Comment: "non-zero value indicates mountpoint usage is above warning threshold",
246+
Metrics: []Metric{},
247+
},
248+
"disk.percent_used": &MetricGroup{
249+
Name: "disk.percent_usage",
250+
Type: "GAUGE",
251+
Comment: "Percentage of mounted volume used",
252+
Metrics: []Metric{},
253+
},
254+
"disk.total_bytes": &MetricGroup{
255+
Name: "disk.total_bytes",
256+
Type: "GAUGE",
257+
Comment: "Total size in bytes of mounted volumed",
258+
Metrics: []Metric{},
259+
},
260+
"disk.used_bytes": &MetricGroup{
261+
Name: "disk.used_bytes",
262+
Type: "GAUGE",
263+
Comment: "Used size in bytes of mounted volumed",
264+
Metrics: []Metric{},
265+
},
266+
"disk.free_bytes": &MetricGroup{
267+
Name: "disk.free_bytes",
268+
Type: "GAUGE",
269+
Comment: "Free size in bytes of mounted volumed",
270+
Metrics: []Metric{},
271+
},
272+
}
273+
160274
for _, p := range parts {
275+
tags = map[string]string{}
276+
for key, value := range extraTags {
277+
tags[key] = value
278+
}
161279
// Ignore excluded (or non-included) file system types
162280
if !isValidFSType(p.Fstype) {
163281
continue
@@ -173,13 +291,16 @@ func executeCheck(event *types.Event) (int, error) {
173291
continue
174292
}
175293

294+
tags["mountpoint"] = p.Mountpoint
176295
device := p.Mountpoint
177296
s, err := disk.Usage(device)
178297
if err != nil {
179298
if plugin.FailOnError {
180299
return sensu.CheckStateCritical, fmt.Errorf("Failed to get disk usage for %s, error: %v", device, err)
181300
}
182-
fmt.Printf("%s UNKNOWN: %s - error: %v\n", plugin.PluginConfig.Name, device, err)
301+
if !plugin.MetricsMode {
302+
fmt.Printf("%s UNKNOWN: %s - error: %v\n", plugin.PluginConfig.Name, device, err)
303+
}
183304
continue
184305
}
185306

@@ -189,23 +310,67 @@ func executeCheck(event *types.Event) (int, error) {
189310
}
190311

191312
// implement magic factor for larger file systems?
192-
fmt.Printf("%s ", plugin.PluginConfig.Name)
313+
crit := 0
314+
warn := 0
193315
if s.UsedPercent >= plugin.Critical {
194316
criticals++
195-
fmt.Printf("CRITICAL: ")
196-
} else if s.UsedPercent >= plugin.Warning {
317+
crit = 1
318+
}
319+
if s.UsedPercent >= plugin.Warning {
197320
warnings++
198-
fmt.Printf(" WARNING: ")
321+
warn = 1
322+
}
323+
metricGroups["disk.critical"].AddMetric(tags, float64(crit), timeNow)
324+
metricGroups["disk.warning"].AddMetric(tags, float64(warn), timeNow)
325+
if !plugin.MetricsMode {
326+
fmt.Printf("%s ", plugin.PluginConfig.Name)
327+
if crit > 0 {
328+
fmt.Printf("CRITICAL: ")
329+
} else if warn > 0 {
330+
fmt.Printf(" WARNING: ")
331+
} else {
332+
fmt.Printf(" OK: ")
333+
}
334+
if plugin.HumanReadable {
335+
fmt.Printf("%s %.2f%% - Total: %s, Used: %s, Free: %s\n",
336+
p.Mountpoint, s.UsedPercent, human.IBytes(s.Total), human.IBytes(s.Used), human.IBytes(s.Free))
337+
} else {
338+
fmt.Printf("%s %.2f%% - Total: %s, Used: %s, Free: %s\n",
339+
p.Mountpoint, s.UsedPercent, human.Bytes(s.Total), human.Bytes(s.Used), human.Bytes(s.Free))
340+
}
341+
}
342+
metricGroups["disk.percent_used"].AddMetric(tags, float64(s.UsedPercent), timeNow)
343+
metricGroups["disk.total_bytes"].AddMetric(tags, float64(s.Total), timeNow)
344+
metricGroups["disk.used_bytes"].AddMetric(tags, float64(s.Used), timeNow)
345+
metricGroups["disk.free_bytes"].AddMetric(tags, float64(s.Free), timeNow)
346+
}
347+
tags = map[string]string{}
348+
for key, value := range extraTags {
349+
tags[key] = value
350+
}
351+
tags["mountpoint"] = "any"
352+
anyCritical := func() float64 {
353+
if criticals > 0 {
354+
return 1
199355
} else {
200-
fmt.Printf(" OK: ")
356+
return 0
201357
}
202-
if plugin.HumanReadable {
203-
fmt.Printf("%s %.2f%% - Total: %s, Used: %s, Free: %s\n", p.Mountpoint, s.UsedPercent, human.IBytes(s.Total), human.IBytes(s.Used), human.IBytes(s.Free))
358+
}()
359+
metricGroups["disk.critical"].AddMetric(tags, anyCritical, timeNow)
360+
anyWarning := func() float64 {
361+
if warnings > 0 {
362+
return 1
204363
} else {
205-
fmt.Printf("%s %.2f%% - Total: %s, Used: %s, Free: %s\n", p.Mountpoint, s.UsedPercent, human.Bytes(s.Total), human.Bytes(s.Used), human.Bytes(s.Free))
364+
return 0
365+
}
366+
}()
367+
metricGroups["disk.warning"].AddMetric(tags, anyWarning, timeNow)
368+
if plugin.MetricsMode {
369+
for _, g := range metricGroups {
370+
g.Output()
206371
}
207-
}
208372

373+
}
209374
if criticals > 0 {
210375
return sensu.CheckStateCritical, nil
211376
} else if warnings > 0 {

0 commit comments

Comments
 (0)