]> git-server-git.apps.pok.os.sepia.ceph.com Git - cephmetrics.git/commitdiff
iscsi.py : iscsi collector added to create stats for iSCSI gateways
authorPaul Cuzner <pcuzner@redhat.com>
Mon, 7 Aug 2017 00:17:30 +0000 (12:17 +1200)
committerPaul Cuzner <pcuzner@redhat.com>
Mon, 7 Aug 2017 00:17:30 +0000 (12:17 +1200)
The iscsi module interacts with rtslib_fb to extract LIO configuration
settings to create performance and configuration metrics.

collectors/iscsi.py [new file with mode: 0644]

diff --git a/collectors/iscsi.py b/collectors/iscsi.py
new file mode 100644 (file)
index 0000000..5ad45ce
--- /dev/null
@@ -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()
+