''' 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: : [Values...] Values are as in Documentation/device-mapper/cache.txt Status section: <#used metadata blocks>/<#total metadata blocks> <#used cache blocks>/<#total cache blocks> <#read hits> <#read misses> <#write hits> <#write misses> <#demotions> <#promotions> <#dirty> <#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()