diff options
author | Anton Tolchanov <1687799+knyar@users.noreply.github.com> | 2018-12-21 13:10:03 +0000 |
---|---|---|
committer | Ben Kochie <superq@gmail.com> | 2018-12-21 14:10:03 +0100 |
commit | cf8b29d1fb8be541fc2b4cafe531a0f8abdd2bab (patch) | |
tree | 0cf8993b646849ac876b83a89f8b9092a6254751 | |
parent | 97dab59e18c575d1384a9afd54e9f30473f34322 (diff) | |
download | prometheus_node_collector-cf8b29d1fb8be541fc2b4cafe531a0f8abdd2bab.tar.bz2 prometheus_node_collector-cf8b29d1fb8be541fc2b4cafe531a0f8abdd2bab.tar.xz prometheus_node_collector-cf8b29d1fb8be541fc2b4cafe531a0f8abdd2bab.zip |
Add a sample btrfs stats collector script (#1200)
Signed-off-by: Anton Tolchanov <commits@knyar.net>
-rwxr-xr-x | text_collector_examples/btrfs_stats.py | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/text_collector_examples/btrfs_stats.py b/text_collector_examples/btrfs_stats.py new file mode 100755 index 0000000..b26bfd2 --- /dev/null +++ b/text_collector_examples/btrfs_stats.py | |||
@@ -0,0 +1,112 @@ | |||
1 | #!/usr/bin/env python | ||
2 | |||
3 | # Collect per-device btrfs filesystem errors. | ||
4 | # Designed to work on Debian and Centos 6 (with python2.6). | ||
5 | |||
6 | import collections | ||
7 | import glob | ||
8 | import os | ||
9 | import re | ||
10 | import subprocess | ||
11 | |||
12 | def get_btrfs_mount_points(): | ||
13 | """List all btrfs mount points. | ||
14 | |||
15 | Yields: | ||
16 | (string) filesystem mount points. | ||
17 | """ | ||
18 | with open("/proc/mounts") as f: | ||
19 | for line in f: | ||
20 | parts = line.split() | ||
21 | if parts[2] == "btrfs": | ||
22 | yield parts[1] | ||
23 | |||
24 | def get_btrfs_errors(mountpoint): | ||
25 | """Get per-device errors for a btrfs mount point. | ||
26 | |||
27 | Args: | ||
28 | mountpoint: (string) path to a mount point. | ||
29 | |||
30 | Yields: | ||
31 | (device, error_type, error_count) tuples, where: | ||
32 | device: (string) path to block device. | ||
33 | error_type: (string) type of btrfs error. | ||
34 | error_count: (int) number of btrfs errors of a given type. | ||
35 | """ | ||
36 | p = subprocess.Popen(["btrfs", "device", "stats", mountpoint], | ||
37 | stdout=subprocess.PIPE) | ||
38 | (stdout, stderr) = p.communicate() | ||
39 | if p.returncode != 0: | ||
40 | raise RuntimeError("btrfs returned exit code %d" % p.returncode) | ||
41 | for line in stdout.splitlines(): | ||
42 | if line == '': | ||
43 | continue | ||
44 | # Sample line: | ||
45 | # [/dev/vdb1].flush_io_errs 0 | ||
46 | m = re.search(r"^\[([^\]]+)\]\.(\S+)\s+(\d+)$", line.decode("utf-8")) | ||
47 | if not m: | ||
48 | raise RuntimeError("unexpected output from btrfs: '%s'" % line) | ||
49 | yield m.group(1), m.group(2), int(m.group(3)) | ||
50 | |||
51 | def btrfs_error_metrics(): | ||
52 | """Collect btrfs error metrics. | ||
53 | |||
54 | Returns: | ||
55 | a list of strings to be exposed as Prometheus metrics. | ||
56 | """ | ||
57 | metric = "node_btrfs_errors_total" | ||
58 | contents = [ | ||
59 | "# TYPE %s counter" % metric, | ||
60 | "# HELP %s number of btrfs errors" % metric, | ||
61 | ] | ||
62 | errors_by_device = collections.defaultdict(dict) | ||
63 | for mountpoint in get_btrfs_mount_points(): | ||
64 | for device, error_type, error_count in get_btrfs_errors(mountpoint): | ||
65 | contents.append( | ||
66 | '%s{mountpoint="%s",device="%s",type="%s"} %d' % | ||
67 | (metric, mountpoint, device, error_type, error_count)) | ||
68 | |||
69 | if len(contents) > 2: | ||
70 | # return metrics if there are actual btrfs filesystems found | ||
71 | # (i.e. `contents` contains more than just TYPE and HELP). | ||
72 | return contents | ||
73 | |||
74 | def btrfs_allocation_metrics(): | ||
75 | """Collect btrfs allocation metrics. | ||
76 | |||
77 | Returns: | ||
78 | a list of strings to be exposed as Prometheus metrics. | ||
79 | """ | ||
80 | prefix = 'node_btrfs_allocation' | ||
81 | metric_to_filename = { | ||
82 | 'size_bytes': 'total_bytes', | ||
83 | 'used_bytes': 'bytes_used', | ||
84 | 'reserved_bytes': 'bytes_reserved', | ||
85 | 'pinned_bytes': 'bytes_pinned', | ||
86 | 'disk_size_bytes': 'disk_total', | ||
87 | 'disk_used_bytes': 'disk_used', | ||
88 | } | ||
89 | contents = [] | ||
90 | for m, f in metric_to_filename.items(): | ||
91 | contents += [ | ||
92 | "# TYPE %s_%s gauge" % (prefix, m), | ||
93 | "# HELP %s_%s btrfs allocation data (%s)" % (prefix, m, f), | ||
94 | ] | ||
95 | |||
96 | for alloc in glob.glob("/sys/fs/btrfs/*/allocation"): | ||
97 | fs = alloc.split('/')[4] | ||
98 | for type_ in ('data', 'metadata', 'system'): | ||
99 | for m, f in metric_to_filename.items(): | ||
100 | filename = os.path.join(alloc, type_, f) | ||
101 | with open(filename) as f: | ||
102 | value = int(f.read().strip()) | ||
103 | contents.append('%s_%s{fs="%s",type="%s"} %d' % ( | ||
104 | prefix, m, fs, type_, value)) | ||
105 | if len(contents) > 2*len(metric_to_filename): | ||
106 | return contents | ||
107 | |||
108 | if __name__ == "__main__": | ||
109 | contents = ((btrfs_error_metrics() or []) + | ||
110 | (btrfs_allocation_metrics() or [])) | ||
111 | |||
112 | print("\n".join(contents)) | ||