summaryrefslogtreecommitdiff
path: root/src/pmdas/dmcache
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2014-10-26 12:33:50 +0400
committerIgor Pashev <pashev.igor@gmail.com>2014-10-26 12:33:50 +0400
commit47e6e7c84f008a53061e661f31ae96629bc694ef (patch)
tree648a07f3b5b9d67ce19b0fd72e8caa1175c98f1a /src/pmdas/dmcache
downloadpcp-debian.tar.gz
Debian 3.9.10debian/3.9.10debian
Diffstat (limited to 'src/pmdas/dmcache')
-rw-r--r--src/pmdas/dmcache/GNUmakefile37
-rw-r--r--src/pmdas/dmcache/Install35
-rw-r--r--src/pmdas/dmcache/Remove25
-rw-r--r--src/pmdas/dmcache/pmdadmcache.python264
4 files changed, 361 insertions, 0 deletions
diff --git a/src/pmdas/dmcache/GNUmakefile b/src/pmdas/dmcache/GNUmakefile
new file mode 100644
index 0000000..243f08f
--- /dev/null
+++ b/src/pmdas/dmcache/GNUmakefile
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = dmcache
+PYSCRIPT = pmda$(IAM).python
+LSRCFILES = Install Remove $(PYSCRIPT)
+
+DOMAIN = DMCACHE
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h $(IAM).log
+
+default_pcp default: check_domain
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(PYSCRIPT) $(PMDADIR)/$(PYSCRIPT)
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PYTHONRULE)
diff --git a/src/pmdas/dmcache/Install b/src/pmdas/dmcache/Install
new file mode 100644
index 0000000..64ef3fa
--- /dev/null
+++ b/src/pmdas/dmcache/Install
@@ -0,0 +1,35 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Device Mapper Cache PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dmcache
+python_opt=true
+daemon_opt=false
+forced_restart=true
+
+which dmsetup >/dev/null 2>&1
+if [ $? -ne 0 ]
+then
+ echo "Device Mapper 'dmsetup' binary not found, cannot proceed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/dmcache/Remove b/src/pmdas/dmcache/Remove
new file mode 100644
index 0000000..6586d55
--- /dev/null
+++ b/src/pmdas/dmcache/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Device Mapper Cache PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dmcache
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/dmcache/pmdadmcache.python b/src/pmdas/dmcache/pmdadmcache.python
new file mode 100644
index 0000000..35db1a0
--- /dev/null
+++ b/src/pmdas/dmcache/pmdadmcache.python
@@ -0,0 +1,264 @@
+'''
+Performance Metrics Domain Agent exporting Device Mapper Cache metrics.
+'''
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+from pcp.pmda import PMDA, pmdaMetric, pmdaIndom, pmdaUnits
+from ctypes import POINTER, Structure, cast, c_ulonglong, c_uint
+from cpmapi import (PM_SEM_DISCRETE, PM_SEM_INSTANT, PM_SEM_COUNTER,
+ PM_ERR_INST, PM_ERR_PMID, PM_SPACE_BYTE, PM_SPACE_KBYTE,
+ PM_TYPE_U32, PM_TYPE_U64, PM_TYPE_STRING, PM_COUNT_ONE)
+from subprocess import PIPE, Popen
+from os import getenv
+
+IOMODES = { 'writeback': 1, 'writethrough': 2, 'passthrough': 3 }
+
+class DmCacheStats(Structure):
+ ''' Metric values associated with each dm-cache target '''
+ _fields_ = [("size", c_ulonglong),
+ ("metadata_block_size", c_uint),
+ ("metadata_used", c_ulonglong),
+ ("metadata_total", c_ulonglong),
+ ("cache_block_size", c_uint),
+ ("cache_used", c_ulonglong),
+ ("cache_total", c_ulonglong),
+ ("read_hits", c_uint),
+ ("read_misses", c_uint),
+ ("write_hits", c_uint),
+ ("write_misses", c_uint),
+ ("demotions", c_uint),
+ ("promotions", c_uint),
+ ("dirty", c_ulonglong),
+ ("iomode", c_uint)]
+
+ def __init__(self, text):
+ Structure.__init__(self)
+ self.parse(text)
+
+ def parse(self, line):
+ '''
+ Parse an individual line of dmcache statistics text. The format is:
+ <name>: <start> <end> <target> [Values...]
+ Values are as in Documentation/device-mapper/cache.txt Status section:
+ <metadata block size> <#used metadata blocks>/<#total metadata blocks>
+ <cache block size> <#used cache blocks>/<#total cache blocks>
+ <#read hits> <#read misses> <#write hits> <#write misses>
+ <#demotions> <#promotions> <#dirty> <#features> <features>*
+ '''
+ data = line.replace('/', ' ').split(' ')
+ if len(data) < 19:
+ raise ValueError(line)
+ mbsize = long(data[4]) * 512 # metadata block size (from sectors)
+ cbsize = long(data[7]) * 512 # cachedev block size (from sectors)
+
+ self.size = long(data[2]) - long(data[1])
+ self.metadata_block_size = mbsize
+ self.metadata_used = long(data[5]) * mbsize / 1024
+ self.metadata_total = long(data[6]) * mbsize / 1024
+ self.cache_block_size = cbsize
+ self.cache_used = long(data[8]) * cbsize / 1024
+ self.cache_total = long(data[9]) * cbsize / 1024
+ self.read_hits = long(data[10])
+ self.read_misses = long(data[11])
+ self.write_hits = long(data[12])
+ self.write_misses = long(data[13])
+ self.demotions = long(data[14])
+ self.promotions = long(data[15])
+ self.dirty = long(data[16]) * cbsize / 1024
+ self.iomode = IOMODES[data[18]]
+
+ def io_mode(self):
+ ''' Human-readable representation of numeric io_mode encoding '''
+ for mode in IOMODES:
+ if self.iomode == IOMODES[mode]:
+ return mode
+ return 'unknown'
+
+class DmCachePMDA(PMDA):
+ '''
+ Performance Metrics Domain Agent exporting dm-cache module metrics.
+ Install and make basic use of it, if you use dm-cache, as follows:
+ # $PCP_PMDAS_DIR/dmcache/Install
+ $ pminfo -fmdtT dmcache
+ '''
+
+ def __init__(self, name, domain):
+ '''
+ Setup metric table (see drivers/md/dm-cache-target.c::cache_status),
+ the cache devices instance domain, and our callback routines.
+ '''
+ PMDA.__init__(self, name, domain)
+
+ # dm-cache devices instance domain
+ self.caches = {}
+ self.dmstatus = getenv('DM_STATUS', 'dmsetup status --target=cache')
+
+ self.cache_indom = self.indom(0)
+ self.add_indom(pmdaIndom(self.cache_indom, self.caches))
+
+ self.set_fetch(self.dmcache_refresh)
+ self.set_fetch_callback(self.dmcache_fetch_callback)
+
+ self.add_metric('dmcache.size',
+ pmdaMetric(self.pmid(0, 0),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Total size of each cache device (Kbytes)')
+
+ self.add_metric('dmcache.metadata.block_size',
+ pmdaMetric(self.pmid(0, 1),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_BYTE, 0, 0)),
+ 'Fixed block size for each metadata block in bytes')
+ self.add_metric('dmcache.metadata.used',
+ pmdaMetric(self.pmid(0, 2),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_INSTANT,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Number of metadata blocks used')
+ self.add_metric('dmcache.metadata.total',
+ pmdaMetric(self.pmid(0, 3),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Total number of metadata blocks available')
+
+ self.add_metric('dmcache.cache.block_size',
+ pmdaMetric(self.pmid(0, 4),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_BYTE, 0, 0)),
+ 'Fixed block size for each metadata block in bytes')
+ self.add_metric('dmcache.cache.used',
+ pmdaMetric(self.pmid(0, 5),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_INSTANT,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Number of cache blocks used')
+ self.add_metric('dmcache.cache.total',
+ pmdaMetric(self.pmid(0, 6),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Total number of cache blocks available')
+
+ self.add_metric('dmcache.read_hits',
+ pmdaMetric(self.pmid(0, 7),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a READ bio has been mapped to the cache')
+ self.add_metric('dmcache.read_misses',
+ pmdaMetric(self.pmid(0, 8),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a READ bio has been mapped to the origin')
+ self.add_metric('dmcache.write_hits',
+ pmdaMetric(self.pmid(0, 9),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a WRITE bio has been mapped to the cache')
+ self.add_metric('dmcache.write_misses',
+ pmdaMetric(self.pmid(0, 10),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a WRITE bio has been mapped to the origin')
+
+ self.add_metric('dmcache.demotions',
+ pmdaMetric(self.pmid(0, 11),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a block has been removed from the cache')
+ self.add_metric('dmcache.promotions',
+ pmdaMetric(self.pmid(0, 12),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a block has been moved to the cache')
+
+ self.add_metric('dmcache.dirty',
+ pmdaMetric(self.pmid(0, 13),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_INSTANT,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Number of blocks in the cache that differ from the origin')
+ self.add_metric('dmcache.io_mode_code',
+ pmdaMetric(self.pmid(0, 14),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(0, 0, 0, 0, 0, 0)),
+ 'Cache mode code - writeback, writethrough or passthrough')
+ self.add_metric('dmcache.io_mode',
+ pmdaMetric(self.pmid(1, 0),
+ PM_TYPE_STRING, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(0, 0, 0, 0, 0, 0)),
+ 'Cache mode string - writeback, writethrough or passthrough')
+
+ def dmcache_refresh(self):
+ ''' Refresh the values and instances for all device mapper caches '''
+ pipe = Popen(self.dmstatus, shell = True, stdout = PIPE, stderr = PIPE)
+ output, errors = pipe.communicate()
+ if errors:
+ self.err("dmcache_refresh: %s error: %s" % (self.dmstatus, errors))
+
+ self.caches.clear()
+ for line in output.splitlines():
+ if 'No devices found' in line:
+ continue
+ name = line[:line.find(':')] # extract cache name
+ self.caches[name] = DmCacheStats(line) # extract stats values
+ self.replace_indom(self.cache_indom, self.caches)
+
+ def dmcache_fetch_callback(self, cluster, item, inst):
+ '''
+ Look up value associated with requested instance (inst) of a
+ given metric (cluster:item) PMID, returning a [value,status]
+ '''
+ value = self.inst_lookup(self.cache_indom, inst)
+ if (value == None):
+ return [PM_ERR_INST, 0]
+ value = cast(value, POINTER(DmCacheStats))
+ cache = value.contents
+
+ if cluster == 1 and item == 0:
+ return [cache.io_mode(), 1] # human-readable string encoding
+ elif cluster != 0:
+ return [PM_ERR_PMID, 0]
+
+ if item == 0:
+ return [cache.size, 1]
+ elif item == 1:
+ return [cache.metadata_block_size, 1]
+ elif item == 2:
+ return [cache.metadata_used, 1]
+ elif item == 3:
+ return [cache.metadata_total, 1]
+ elif item == 4:
+ return [cache.cache_block_size, 1]
+ elif item == 5:
+ return [cache.cache_used, 1]
+ elif item == 6:
+ return [cache.cache_total, 1]
+ elif item == 7:
+ return [cache.read_hits, 1]
+ elif item == 8:
+ return [cache.read_misses, 1]
+ elif item == 9:
+ return [cache.write_hits, 1]
+ elif item == 10:
+ return [cache.write_misses, 1]
+ elif item == 11:
+ return [cache.demotions, 1]
+ elif item == 12:
+ return [cache.promotions, 1]
+ elif item == 13:
+ return [cache.dirty, 1]
+ elif item == 14:
+ return [cache.iomode, 1]
+ return [PM_ERR_PMID, 0]
+
+if __name__ == '__main__':
+ DmCachePMDA('dmcache', 129).run()