File: //usr/local/packages/@appstore/ActiveInsight/collectors/diskstat_collector.py
#!/usr/bin/python
import glob
import json
import os
import subprocess
import time
import flashcache_util
_stat = {}
_time = 0
_prev_stat = {}
_prev_time = 0
class DiskStats:
def __init__(self, rd_ios, wr_ios, rd_sectors, wr_sectors, rd_ticks, wr_ticks, io_ticks):
self.rd_ios = rd_ios
self.wr_ios = wr_ios
self.rd_sectors = rd_sectors
self.wr_sectors = wr_sectors
self.rd_ticks = rd_ticks
self.wr_ticks = wr_ticks
self.io_ticks = io_ticks
def disk_slot_mapping(paths):
try:
p = subprocess.run(['/usr/syno/bin/syno_mib_disk_mapping'],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True)
mapping = json.loads(p.stdout.decode("utf-8").strip())
except FileNotFoundError:
return {}
slot = {}
for path in paths:
temp = mapping.get(path)
if temp:
slot[path] = temp
return slot
def parse_stat(raw_stat):
stats = {}
for line in raw_stat:
if not line:
continue
temp_stat = line.split()
stats[temp_stat[2]] = DiskStats(
float(temp_stat[3]),
float(temp_stat[7]),
float(temp_stat[5]),
float(temp_stat[9]),
float(temp_stat[6]),
float(temp_stat[10]),
float(temp_stat[12]),
)
return stats
def get_stat():
try:
with open('/proc/diskstats', 'r') as f:
raw_stat = f.read().split('\n')
t_now = time.time()
except Exception as e:
raise e
return parse_stat(raw_stat), t_now
def calc_each_metric(stat1, time1, stat2, time2):
delta_time = time1 - time2
if not stat2 or delta_time < 1:
return None
metric = {}
delta = {
'rd_ios': stat1.rd_ios - stat2.rd_ios,
'wr_ios': stat1.wr_ios - stat2.wr_ios,
'rd_sectors': stat1.rd_sectors - stat2.rd_sectors,
'wr_sectors': stat1.wr_sectors - stat2.wr_sectors,
'rd_ticks': stat1.rd_ticks - stat2.rd_ticks,
'wr_ticks': stat1.wr_ticks - stat2.wr_ticks,
'io_ticks': stat1.io_ticks - stat2.io_ticks
}
for v in delta.values():
if v < 0:
return None
metric['read_iops'] = delta['rd_ios'] / delta_time
metric['write_iops'] = delta['wr_ios'] / delta_time
metric['read_throughput'] = delta['rd_sectors'] / delta_time * 512
metric['write_throughput'] = delta['wr_sectors'] / delta_time * 512
metric['read_io_latency'] = delta['rd_ticks'] / \
delta['rd_ios'] * 1000 if delta['rd_ios'] != 0 else 0
metric['write_io_latency'] = delta['wr_ticks'] / \
delta['wr_ios'] * 1000 if delta['wr_ios'] != 0 else 0
metric['utilization'] = delta['io_ticks'] / delta_time / 1000
if metric['utilization'] > 1:
metric['utilization'] = 1.0
return metric
def get_metrics(stat1, time1, stat2, time2):
metric = {}
for dev, value in stat1.items():
metric[dev] = calc_each_metric(value, time1, stat2.get(dev), time2)
return metric
def get_disk_dev():
dev = []
ret = {}
disks = glob.glob('/run/synostorage/disks/*')
for disk in disks:
temp = disk.split('/')[-1]
if os.path.exists('/dev/'+temp):
dev.append(temp)
for path, slot in disk_slot_mapping(dev).items():
ret[path] = [slot]
return ret
def get_volume_dev():
try:
with open('/etc/fstab', 'r') as f:
fstab = f.readlines()
except Exception as e:
raise e
# get the device path of all volumes
# ex: {'cachedev_0': 'volume1', 'md2': 'volume2', 'vg1-volume1': 'volume3', 'vg1000-lv': 'volume4', 'drbd0': 'volume5'}
dev_path = {}
for line in fstab:
if not line:
continue
tmp_tab = line.strip().split()
if tmp_tab[1].startswith('/volume'):
split_path = tmp_tab[0].split('/')
if split_path[2].startswith('vg'):
if split_path[3].startswith('lv'): # vg100X-lv
tmp_key = split_path[2] + '-lv'
else: # vgX-volumeX
tmp_key = split_path[2] + '-' + split_path[3]
elif split_path[2].startswith('drbd'): # drbd-vgX-volumeX
tmp_key = os.readlink(tmp_tab[0]).split('/')[-1] # drbdX
else:
# cachedev_X, mdX, sdXX
tmp_key = split_path[-1]
dev_path[tmp_key] = tmp_tab[1].strip('/')
dev = {}
# travere all /sys/block/dm-*/name to get the dm volume mapping
# ex: {'dm-4': 'volume2', 'dm-3': 'volume1'}
for dm_name, dev_name in get_dm_name_dev_name_map().items():
if dev_path.get(dev_name):
dev[dm_name] = [dev_path[dev_name]]
# get all non dm volume
for temp_dev, vol in dev_path.items():
if not temp_dev.startswith('cryptvol') and not temp_dev.startswith('cachedev') and not temp_dev.startswith('vg'):
dev[temp_dev] = [vol]
return dev
def get_dm_name_dev_name_map():
"""e.g. {'dm-1': 'cachedev_0', 'dm-2': 'vg1-volume_1'}, dm_name: e.g. dm-1"""
info = {}
dm_paths = glob.glob('/sys/block/dm-*')
for path in dm_paths:
try:
with open(path+'/dm/name', 'r') as f:
dm_name = path.split('/')[-1]
info[dm_name] = f.read().rstrip()
except Exception as e:
continue
return info
def get_flashcache_dev():
if not flashcache_util.is_support_cache():
return {}
ret, infos = flashcache_util.get_infos('--active-insight-cache-devs')
if not ret:
return {}
# e.g. {'cachedev_0': 'dm-1'}
dev_name_dm_name_map = {}
for dm_name, dev_name in get_dm_name_dev_name_map().items():
dev_name_dm_name_map[dev_name] = dm_name
dev = {}
for info in infos:
labels = flashcache_util.get_lables(info)
dm_name = dev_name_dm_name_map.get(info['dev_name'])
if dm_name is None:
# when cache is not enabled yet
continue
dev[dm_name] = labels
return dev
def get_raid_dev():
mdX = glob.glob('/sys/block/md*')
dev = {}
for i in mdX:
temp_dev = i.split('/')[-1]
dev[temp_dev] = [temp_dev]
return dev
def _zip_object(list1, list2):
return {list1[index]: list2[index] for index, _ in enumerate(list1)}
def _add_gauge_metric(app, name, value, label=None):
if label:
return {
'application': app,
'name': name,
'type': 'gauge',
'label': label,
'value': value,
}
else:
return {
'application': app,
'name': name,
'type': 'gauge',
'value': value,
}
def add_metric_family(metrics, app, dev, labels):
if not dev:
return []
result = []
for dev_name, label in dev.items():
if dev_name not in metrics:
raise Exception(
"Failed to get device diskstat: No such device ({})".format(dev_name))
result.append(_add_gauge_metric(
app, 'read_iops', metrics[dev_name]['read_iops'] if metrics.get(dev_name) else 0, label=_zip_object(labels, label)))
result.append(_add_gauge_metric(
app, 'write_iops', metrics[dev_name]['write_iops'] if metrics.get(dev_name) else 0, label=_zip_object(labels, label)))
result.append(_add_gauge_metric(
app, 'read_io_latency', metrics[dev_name]['read_io_latency'] if metrics.get(dev_name) else 0, label=_zip_object(labels, label)))
result.append(_add_gauge_metric(
app, 'write_io_latency', metrics[dev_name]['write_io_latency'] if metrics.get(dev_name) else 0, label=_zip_object(labels, label)))
result.append(_add_gauge_metric(
app, 'read_throughput', metrics[dev_name]['read_throughput'] if metrics.get(dev_name) else 0, label=_zip_object(labels, label)))
result.append(_add_gauge_metric(
app, 'write_throughput', metrics[dev_name]['write_throughput'] if metrics.get(dev_name) else 0, label=_zip_object(labels, label)))
result.append(_add_gauge_metric(
app, 'utilization', metrics[dev_name]['utilization'] if metrics.get(dev_name) else 0, label=_zip_object(labels, label)))
return result
def collect_1m():
global _prev_stat, _prev_time, _stat, _time
_prev_stat = _stat
_prev_time = _time
_stat, _time = get_stat()
result = []
metrics = get_metrics(_stat, _time, _prev_stat, _prev_time)
if not metrics:
return result
result.extend(add_metric_family(metrics, 'disk', get_disk_dev(), ['slot']))
result.extend(add_metric_family(metrics, 'volume', get_volume_dev(), ['volume']))
result.extend(add_metric_family(metrics, 'flashcache', get_flashcache_dev(), ['volume', 'ssd_id', 'type']))
result.extend(add_metric_family(metrics, 'raid', get_raid_dev(), ['dev']))
return result