From f5f198419a06156b9a981bd4889ec248233c3626 Mon Sep 17 00:00:00 2001 From: Paul Cuzner Date: Mon, 7 Aug 2017 12:17:30 +1200 Subject: [PATCH] iscsi.py : iscsi collector added to create stats for iSCSI gateways The iscsi module interacts with rtslib_fb to extract LIO configuration settings to create performance and configuration metrics. --- collectors/iscsi.py | 247 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 collectors/iscsi.py diff --git a/collectors/iscsi.py b/collectors/iscsi.py new file mode 100644 index 0000000..5ad45ce --- /dev/null +++ b/collectors/iscsi.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python2 + +# requires python-rtslib_fb for LIO interaction + +import os +import time +from collectors.base import BaseCollector +from rtslib_fb import RTSRoot +from collectors.common import fread + + +class Client(object): + + def __init__(self, iqn): + self.iqn = iqn + self.name = iqn.replace('.', '-') + self.luns = {} + self.lun_count = 0 + self._cycle = 0 + + def dump(self): + client_dump = {} + lun_info = {} + client_dump[self.name] = {"luns": {}, + "lun_count": self.lun_count} + for lun_name in self.luns: + lun = self.luns[lun_name] + lun_info.update(lun.dump()) + + return {self.name: {"luns": lun_info, + "lun_count": len(lun_info)} + } + + +class LUN(object): + + def __init__(self, client, tpg_lun): + self._path = tpg_lun.storage_object.path + self._tpg_lun = tpg_lun + self._name = tpg_lun.storage_object.name + self._display_name = tpg_lun.storage_object.name.replace('.', "-") + self._so = tpg_lun.storage_object + self._client = client + self._cycle = 0 + self.size = 0 + self.iops = 0 + self.read_bytes_per_sec = 0 + self.write_bytes_per_sec = 0 + self.total_bytes_per_sec = 0 + self.active_path = 0 + + def refresh(self, cycle_id): + self._cycle = cycle_id + self.size = self._so.size + stats_path = os.path.join(self._path, 'statistics/scsi_lu') + self.iops = int(fread(os.path.join(stats_path, "num_cmds"))) + read_mb = float(fread(os.path.join(stats_path, "read_mbytes"))) + write_mb = float(fread(os.path.join(stats_path, "write_mbytes"))) + self.read_bytes_per_sec = int(read_mb * 1024 ** 2) + self.write_bytes_per_sec = int(write_mb * 1024 ** 2) + self.total_bytes_per_sec = self.read_bytes_per_sec + \ + self.write_bytes_per_sec + + if self._tpg_lun.alua_tg_pt_gp_name == 'ao': + self.active_path = 1 + else: + self.active_path = 0 + + def dump(self): + return {self._display_name: {k: getattr(self, k) for k in self.__dict__ + if not k.startswith("_")}} + + +class ISCSIGateway(BaseCollector): + """ + created on a host that has a /sys/kernel/config/target/iscsi dir + i.e. there is an iscsi gateway here! + """ + + metrics = { + "lun_count": ("lun_count", "gauge"), + "client_count": ("client_count", "gauge"), + "tpg_count": ("tpg_count", "gauge"), + "sessions": ("sessions", "gauge"), + "capacity": ("capacity", "gauge"), + "iops": ("iops", "derive"), + "read_bytes_per_sec": ("read_bytes_per_sec", "derive"), + "write_bytes_per_sec": ("write_bytes_per_sec", "derive"), + "total_bytes_per_sec": ("total_bytes_per_sec", "derive") + } + + def __init__(self, *args, **kwargs): + BaseCollector.__init__(self, *args, **kwargs) + + self._root = RTSRoot() + + self.clients = {} + self.cycle = 0 + + self.iops = 0 + self.read_bytes_per_sec = 0 + self.write_bytes_per_sec = 0 + self.total_bytes_per_sec = 0 + + def refresh(self): + """ + populate the instance by exploring rtslib + :return: + """ + self.iops = 0 + self.read_bytes_per_sec = 0 + self.write_bytes_per_sec = 0 + self.total_bytes_per_sec = 0 + + if self.cycle == 10: + self.cycle = 0 + else: + self.cycle += 1 + + for node_acl in self._root.node_acls: + + client_name = node_acl.node_wwn + + if client_name not in self.clients: + new_client = Client(client_name) + self.clients[client_name] = new_client + + client = self.clients[client_name] + client.lun_count = 0 + client._cycle = self.cycle + + for lun in node_acl.mapped_luns: + client.lun_count += 1 + tpg_lun = lun.tpg_lun + lun_name = tpg_lun.storage_object.name + if lun_name not in client.luns: + lun = LUN(client, tpg_lun) + client.luns[lun._name] = lun + else: + lun = client.luns[lun_name] + + lun.refresh(self.cycle) + + self.iops += lun.iops + self.read_bytes_per_sec += lun.read_bytes_per_sec + self.write_bytes_per_sec += lun.write_bytes_per_sec + self.total_bytes_per_sec = self.read_bytes_per_sec + \ + self.write_bytes_per_sec + + def prune(self): + """ + drop child objects held by the instance, that are no longer in the + iSCSI config i.e. don't report on old tut! + :return: + """ + for client_name in self.clients: + client = self.clients[client_name] + + for lun_name in client.luns: + lun = client.luns[lun_name] + if lun._cycle != self.cycle: + # drop the lun entry + self.logger.debug("pruning LUN '{}'".format(lun_name)) + + del client.luns[lun_name] + + if client._cycle != self.cycle: + # drop the client entry + self.logger.debug("pruning client '{}'".format(client_name)) + del self.clients[client_name] + + def dump(self): + + gw_stats = {} + client_stats = {} + + for metric in ISCSIGateway.metrics: + gw_stats[metric] = getattr(self, metric) + + for client_name in self.clients: + client = self.clients[client_name] + client_stats.update(client.dump()) + + return {"iscsi": { + "gw_name": {self.gateway_name: 0}, + "gw_stats": gw_stats, + "gw_clients": client_stats + } + } + + + def _get_so(self): + return [so for so in self._root.storage_objects] + + def _get_node_acls(self): + return [node for node in self._root.node_acls] + + def _get_tpg_count(self): + return len([tpg for tpg in self._root.tpgs]) + + def _get_lun_count(self): + return len(self._get_so()) + + def _get_session_count(self): + return len([session for session in self._root.sessions]) + + def _get_gateway_name(self): + # Only the 1st gateway is considered/supported + gw_iqn = [gw.wwn for gw in self._root.targets][0] + return gw_iqn.replace('.', '-') + + def _get_client_count(self): + return len(self._get_node_acls()) + + def _get_capacity(self): + return sum([so.size for so in self._get_so()]) + + lun_count = property(_get_lun_count) + + tpg_count = property(_get_tpg_count) + + sessions = property(_get_session_count) + + client_count = property(_get_client_count) + + gateway_name = property(_get_gateway_name) + + capacity = property(_get_capacity) + + def get_stats(self): + + start = time.time() + + # populate gateway instance with the latest configuration from rtslib + self.refresh() + + # Overtime they'll be churn in client and disks so we need to drop + # any entries from prior runs that are no longer seen in the iscsi + # configuration with the prune method + self.prune() + + end = time.time() + + self.logger.info("LIO stats took {}s".format(end - start)) + + return self.dump() + -- 2.47.3