Skip to content

Commit 0ab20ab

Browse files
authored
Add disk label (#3953)
* Add disk label * Fix CI * Address reviews * Add label to rates * Fix plateform tests * Add tag_by_label option * Use regex * Fix tests with undefined behaviour * Add os information * Update tagging test * Address reviews
1 parent 491b424 commit 0ab20ab

7 files changed

Lines changed: 109 additions & 24 deletions

File tree

disk/datadog_checks/disk/data/conf.yaml.default

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ instances:
114114
#
115115
# tag_by_filesystem: false
116116

117+
## @param tag_by_label - boolean - optional - default: true
118+
## Instruct the check to tag all the metrics with disk label if there is one.
119+
## Works on Linux only.
120+
#
121+
# tag_by_label: true
122+
117123
## @param device_tag_re - list of regex:tags string - optional
118124
## Instruct the check to apply additional tags to matching
119125
## devices (or mount points if `use_mount` is true).

disk/datadog_checks/disk/disk.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from datadog_checks.base import AgentCheck, ConfigurationError, is_affirmative
1313
from datadog_checks.base.utils.platform import Platform
14-
from datadog_checks.base.utils.subprocess_output import get_subprocess_output
14+
from datadog_checks.base.utils.subprocess_output import SubprocessOutputEmptyError, get_subprocess_output
1515
from datadog_checks.base.utils.timeout import TimeoutException, timeout
1616

1717
try:
@@ -52,6 +52,7 @@ def __init__(self, name, init_config, agentConfig, instances=None):
5252
self._mount_point_whitelist = instance.get('mount_point_whitelist', [])
5353
self._mount_point_blacklist = instance.get('mount_point_blacklist', [])
5454
self._tag_by_filesystem = is_affirmative(instance.get('tag_by_filesystem', False))
55+
self._tag_by_label = is_affirmative(instance.get('tag_by_label', True))
5556
self._device_tag_re = instance.get('device_tag_re', {})
5657
self._custom_tags = instance.get('tags', [])
5758
self._service_check_rw = is_affirmative(instance.get('service_check_rw', False))
@@ -69,6 +70,9 @@ def __init__(self, name, init_config, agentConfig, instances=None):
6970
)
7071
self._compile_pattern_filters(instance)
7172
self._compile_tag_re()
73+
self._blkid_label_re = re.compile('LABEL=\"(.*?)\"', re.I)
74+
75+
self.devices_label = {}
7276

7377
def _load_legacy_option(self, instance, option, default, legacy_name=None, operation=lambda l: l):
7478
value = instance.get(option, default)
@@ -85,6 +89,8 @@ def _load_legacy_option(self, instance, option, default, legacy_name=None, opera
8589

8690
def check(self, instance):
8791
"""Get disk space/inode stats"""
92+
if self._tag_by_label and Platform.is_linux():
93+
self.devices_label = self._get_devices_label()
8894
# Windows and Mac will always have psutil
8995
# (we have packaged for both of them)
9096
if self._psutil():
@@ -134,6 +140,9 @@ def collect_metrics_psutil(self):
134140
if regex.match(device_name):
135141
tags.extend(device_tags)
136142

143+
if self.devices_label.get(device_name):
144+
tags.append(self.devices_label.get(device_name))
145+
137146
# legacy check names c: vs psutil name C:\\
138147
if Platform.is_win32():
139148
device_name = device_name.strip('\\').lower()
@@ -284,6 +293,8 @@ def collect_latency_metrics(self):
284293
write_time_pct = disk.write_time * 100 / 1000
285294
metric_tags = [] if self._custom_tags is None else self._custom_tags[:]
286295
metric_tags.append('device:{}'.format(disk_name))
296+
if self.devices_label.get(disk_name):
297+
metric_tags.append(self.devices_label.get(disk_name))
287298
self.rate(self.METRIC_DISK.format('read_time_pct'), read_time_pct, tags=metric_tags)
288299
self.rate(self.METRIC_DISK.format('write_time_pct'), write_time_pct, tags=metric_tags)
289300
except AttributeError as e:
@@ -308,6 +319,10 @@ def collect_metrics_manually(self):
308319
if regex.match(device_name):
309320
tags += device_tags
310321
tags.append('device:{}'.format(device_name))
322+
323+
if self.devices_label.get(device_name):
324+
tags.append(self.devices_label.get(device_name))
325+
311326
for metric_name, value in iteritems(self._collect_metrics_manually(device)):
312327
self.gauge(metric_name, value, tags=tags)
313328

@@ -455,3 +470,24 @@ def _compile_tag_re(self):
455470
except TypeError:
456471
self.log.warning('{} is not a valid regular expression and will be ignored'.format(regex_str))
457472
self._device_tag_re = device_tag_list
473+
474+
def _get_devices_label(self):
475+
"""
476+
Get every label to create tags
477+
"""
478+
devices_label = {}
479+
try:
480+
blkid_out, _, _ = get_subprocess_output(['blkid'], self.log)
481+
all_devices = [l.split(':', 1) for l in blkid_out.splitlines()]
482+
483+
for d in all_devices:
484+
# Line sample
485+
# /dev/sda1: LABEL="MYLABEL" UUID="5eea373d-db36-4ce2-8c71-12ce544e8559" TYPE="ext4"
486+
labels = self._blkid_label_re.findall(d[1])
487+
if labels:
488+
devices_label[d[0]] = 'label:{}'.format(labels[0])
489+
490+
except SubprocessOutputEmptyError:
491+
self.log.debug("Couldn't use blkid to have device labels")
492+
493+
return devices_label

disk/tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ def dd_environment(instance_basic_volume):
3030

3131
@pytest.fixture(scope='session')
3232
def instance_basic_volume():
33-
return {'use_mount': 'false'}
33+
return {'use_mount': 'false', 'tag_by_label': False}
3434

3535

3636
@pytest.fixture(scope='session')
3737
def instance_basic_mount():
38-
return {'use_mount': 'true'}
38+
return {'use_mount': 'true', 'tag_by_label': False}
3939

4040

4141
@pytest.fixture(scope='session')

disk/tests/fixtures/blkid

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/dev/sda1: UUID="9RbyZC-LtBG-J6RS-EH7g-wYMT-wQP2-wftnVi" TYPE="LVM2_member" PARTUUID="1e4b9125-01"
2+
/dev/mapper/vagrant--vg-root: LABEL="DATA" UUID="5eea373d-db36-4ce2-8c71-12ce544e8559" TYPE="ext4"
3+
/dev/mapper/vagrant--vg-swap_1: UUID="1678c43d-0654-4c4a-87f1-90dffa21ed34" TYPE="swap"

disk/tests/mocks.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ def mock_df_output(fname):
1515
return f.read(), '', ''
1616

1717

18+
def mock_blkid_output():
19+
"""
20+
Load fixtures from tests/fixtures/ folder and return a tuple matching the
21+
return value of `get_subprocess_output`
22+
"""
23+
with open(os.path.join(HERE, 'fixtures', 'blkid')) as f:
24+
return f.read(), '', ''
25+
26+
1827
class MockPart(object):
1928
def __init__(
2029
self, device=DEFAULT_DEVICE_NAME, fstype=DEFAULT_FILE_SYSTEM, mountpoint=DEFAULT_MOUNT_POINT, opts='ro'

disk/tests/test_filter.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ def test_bad_config_string_regex():
3434
}
3535
c = Disk('disk', None, {}, [instance])
3636

37-
assert c._file_system_whitelist == re.compile('test', re.I)
38-
assert c._file_system_blacklist == re.compile('test|iso9660$', re.I)
39-
assert c._device_whitelist == re.compile('test', IGNORE_CASE)
40-
assert c._device_blacklist == re.compile('test', IGNORE_CASE)
41-
assert c._mount_point_whitelist == re.compile('test', IGNORE_CASE)
42-
assert c._mount_point_blacklist == re.compile('test', IGNORE_CASE)
37+
assert c._file_system_whitelist.pattern == 'test'
38+
assert c._file_system_blacklist.pattern == 'test|iso9660$'
39+
assert c._device_whitelist.pattern == 'test'
40+
assert c._device_blacklist.pattern == 'test'
41+
assert c._mount_point_whitelist.pattern == 'test'
42+
assert c._mount_point_blacklist.pattern == 'test'
4343

4444

4545
def test_ignore_empty_regex():
@@ -53,12 +53,12 @@ def test_ignore_empty_regex():
5353
}
5454
c = Disk('disk', None, {}, [instance])
5555

56-
assert c._file_system_whitelist == re.compile('test', re.I)
57-
assert c._file_system_blacklist == re.compile('test|iso9660$', re.I)
58-
assert c._device_whitelist == re.compile('test', IGNORE_CASE)
59-
assert c._device_blacklist == re.compile('test', IGNORE_CASE)
60-
assert c._mount_point_whitelist == re.compile('test', IGNORE_CASE)
61-
assert c._mount_point_blacklist == re.compile('test', IGNORE_CASE)
56+
assert c._file_system_whitelist.pattern == 'test'
57+
assert c._file_system_blacklist.pattern == 'test|iso9660$'
58+
assert c._device_whitelist.pattern == 'test'
59+
assert c._device_blacklist.pattern == 'test'
60+
assert c._mount_point_whitelist.pattern == 'test'
61+
assert c._mount_point_blacklist.pattern == 'test'
6262

6363

6464
def test_exclude_bad_devices():

disk/tests/test_unit.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from datadog_checks.disk import Disk
1111

1212
from .common import DEFAULT_DEVICE_NAME, DEFAULT_FILE_SYSTEM, DEFAULT_MOUNT_POINT
13-
from .mocks import MockInodesMetrics, mock_df_output
13+
from .mocks import MockInodesMetrics, mock_blkid_output, mock_df_output
1414
from .utils import requires_unix
1515

1616

@@ -56,7 +56,7 @@ def test_psutil(aggregator, gauge_metrics, rate_metrics):
5656
Mock psutil and run the check
5757
"""
5858
for tag_by in ['true', 'false']:
59-
instance = {'tag_by_filesystem': tag_by}
59+
instance = {'tag_by_filesystem': tag_by, 'tag_by_label': False}
6060
c = Disk('disk', None, {}, [instance])
6161
c.check(instance)
6262

@@ -83,7 +83,7 @@ def test_psutil_rw(aggregator):
8383
"""
8484
Check for 'ro' option in the mounts
8585
"""
86-
instance = {'service_check_rw': 'yes'}
86+
instance = {'service_check_rw': 'yes', 'tag_by_label': False}
8787
c = Disk('disk', None, {}, [instance])
8888
c.check(instance)
8989

@@ -113,24 +113,33 @@ def test_device_tagging(aggregator, gauge_metrics, rate_metrics):
113113
'use_mount': 'no',
114114
'device_tag_re': {'{}.*'.format(DEFAULT_DEVICE_NAME[:-1]): 'type:dev,tag:two'},
115115
'tags': ['optional:tags1'],
116+
'tag_by_label': False,
116117
}
117118
c = Disk('disk', None, {}, [instance])
118-
c.check(instance)
119+
120+
with mock.patch('datadog_checks.disk.disk.Disk._get_devices_label'):
121+
# _get_devices_label is only called on linux, so devices_label is manually filled
122+
# to make the test run on everything
123+
c.devices_label = {DEFAULT_DEVICE_NAME: 'label:mylab'}
124+
c.check(instance)
119125

120126
# Assert metrics
121-
tags = ['type:dev', 'tag:two', 'device:{}'.format(DEFAULT_DEVICE_NAME), 'optional:tags1']
127+
tags = ['type:dev', 'tag:two', 'device:{}'.format(DEFAULT_DEVICE_NAME), 'optional:tags1', 'label:mylab']
128+
122129
for name, value in iteritems(gauge_metrics):
123130
aggregator.assert_metric(name, value=value, tags=tags)
124131

125132
for name, value in iteritems(rate_metrics):
126-
aggregator.assert_metric(name, value=value, tags=['device:{}'.format(DEFAULT_DEVICE_NAME), 'optional:tags1'])
133+
aggregator.assert_metric(
134+
name, value=value, tags=['device:{}'.format(DEFAULT_DEVICE_NAME), 'optional:tags1', 'label:mylab']
135+
)
127136

128137
aggregator.assert_all_metrics_covered()
129138

130139

131140
@requires_unix
132141
def test_no_psutil_debian(aggregator, gauge_metrics):
133-
instance = {'use_mount': 'no', 'excluded_filesystems': ['tmpfs']}
142+
instance = {'use_mount': 'no', 'excluded_filesystems': ['tmpfs'], 'tag_by_label': False}
134143
c = Disk('disk', None, {}, [instance])
135144
# disable psutil
136145
c._psutil = lambda: False
@@ -155,7 +164,12 @@ def test_no_psutil_debian(aggregator, gauge_metrics):
155164

156165
@requires_unix
157166
def test_no_psutil_freebsd(aggregator, gauge_metrics):
158-
instance = {'use_mount': 'no', 'excluded_filesystems': ['devfs'], 'excluded_disk_re': 'zroot/.+'}
167+
instance = {
168+
'use_mount': 'no',
169+
'excluded_filesystems': ['devfs'],
170+
'excluded_disk_re': 'zroot/.+',
171+
'tag_by_label': False,
172+
}
159173
c = Disk('disk', None, {}, [instance])
160174
# disable psutil
161175
c._psutil = lambda: False
@@ -178,7 +192,12 @@ def test_no_psutil_freebsd(aggregator, gauge_metrics):
178192

179193
@requires_unix
180194
def test_no_psutil_centos(aggregator, gauge_metrics):
181-
instance = {'use_mount': 'no', 'excluded_filesystems': ['devfs', 'tmpfs'], 'excluded_disks': ['/dev/sda1']}
195+
instance = {
196+
'use_mount': 'no',
197+
'excluded_filesystems': ['devfs', 'tmpfs'],
198+
'excluded_disks': ['/dev/sda1'],
199+
'tag_by_label': False,
200+
}
182201
c = Disk('disk', None, {}, [instance])
183202
# disable psutil
184203
c._psutil = lambda: False
@@ -198,3 +217,15 @@ def test_no_psutil_centos(aggregator, gauge_metrics):
198217
aggregator.assert_metric(name, tags=['device:{}'.format(device)])
199218

200219
aggregator.assert_all_metrics_covered()
220+
221+
222+
def test_get_devices_label():
223+
c = Disk('disk', None, {}, [{}])
224+
225+
with mock.patch(
226+
"datadog_checks.disk.disk.get_subprocess_output",
227+
return_value=mock_blkid_output(),
228+
__name__='get_subprocess_output',
229+
):
230+
labels = c._get_devices_label()
231+
assert labels.get("/dev/mapper/vagrant--vg-root") == "label:DATA"

0 commit comments

Comments
 (0)