diff options
Diffstat (limited to 'src/pmiostat/pmiostat.py')
-rwxr-xr-x | src/pmiostat/pmiostat.py | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/src/pmiostat/pmiostat.py b/src/pmiostat/pmiostat.py new file mode 100755 index 0000000..e4dbaeb --- /dev/null +++ b/src/pmiostat/pmiostat.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# +# 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 +# Iostat 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. +# +# pylint: disable=C0103,R0914,R0902 +""" Display disk and device-mapper I/O statistics """ + +import sys +from pcp import pmapi, pmcc +from cpmapi import PM_TYPE_U64, PM_CONTEXT_ARCHIVE, PM_SPACE_KBYTE + +IOSTAT_SD_METRICS = [ "disk.dev.read", "disk.dev.read_bytes", + "disk.dev.write", "disk.dev.write_bytes", + "disk.dev.read_merge", "disk.dev.write_merge", + "disk.dev.blkread", "disk.dev.blkwrite", + "disk.dev.read_rawactive", "disk.dev.write_rawactive", + "disk.dev.avactive"] + +IOSTAT_DM_METRICS = [ "disk.dm.read", "disk.dm.read_bytes", + "disk.dm.write", "disk.dm.write_bytes", + "disk.dm.read_merge", "disk.dm.write_merge", + "disk.dm.blkread", "disk.dm.blkwrite", + "disk.dm.read_rawactive", "disk.dm.write_rawactive", + "disk.dm.avactive"] + +class IostatReport(pmcc.MetricGroupPrinter): + Hcount = 0 + def timeStampDelta(self, group): + c = 1000000.0 * group.timestamp.tv_sec + group.timestamp.tv_usec + p = 1000000.0 * group.prevTimestamp.tv_sec + group.prevTimestamp.tv_usec + return (c - p) / 1000000.0 + + def instlist(self, group, name): + return dict(map(lambda x: (x[1], x[2]), group[name].netValues)).keys() + + def curVals(self, group, name): + return dict(map(lambda x: (x[1], x[2]), group[name].netValues)) + + def prevVals(self, group, name): + return dict(map(lambda x: (x[1], x[2]), group[name].netPrevValues)) + + def report(self, manager): + if "dm" in IostatOptions.xflag: + subtree = "disk.dm" + else: + subtree = "disk.dev" + group = manager["iostat"] + + if group[subtree + ".read_merge"].netPrevValues == None: + # need two fetches to report rate converted counter metrics + return + + instlist = self.instlist(group, subtree + ".read") + dt = self.timeStampDelta(group) + timestamp = group.contextCache.pmCtime(long(group.timestamp)).rstrip() + + c_rrqm = self.curVals(group, subtree + ".read_merge") + p_rrqm = self.prevVals(group, subtree + ".read_merge") + + c_wrqm = self.curVals(group, subtree + ".write_merge") + p_wrqm = self.prevVals(group, subtree + ".write_merge") + + c_r = self.curVals(group, subtree + ".read") + p_r = self.prevVals(group, subtree + ".read") + + c_w = self.curVals(group, subtree + ".write") + p_w = self.prevVals(group, subtree + ".write") + + c_rkb = self.curVals(group, subtree + ".read_bytes") + p_rkb = self.prevVals(group, subtree + ".read_bytes") + + c_wkb = self.curVals(group, subtree + ".write_bytes") + p_wkb = self.prevVals(group, subtree + ".write_bytes") + + c_ractive = self.curVals(group, subtree + ".read_rawactive") + p_ractive = self.prevVals(group, subtree + ".read_rawactive") + + c_wactive = self.curVals(group, subtree + ".write_rawactive") + p_wactive = self.prevVals(group, subtree + ".write_rawactive") + + c_avactive = self.curVals(group, subtree + ".avactive") + p_avactive = self.prevVals(group, subtree + ".avactive") + + # check availability + if p_rrqm == {} or p_wrqm == {} or p_r == {} or p_w == {} or p_rkb == {} \ + or p_wkb == {} or p_ractive == {} or p_wactive == {} or p_avactive == {}: + # no values (near start of archive?) + return + + if "h" not in IostatOptions.xflag: + self.Hcount += 1 + if self.Hcount == 24: + self.Hcount = 1 + if self.Hcount == 1: + if "t" in IostatOptions.xflag: + heading = ('# Timestamp', 'Device', 'rrqm/s', 'wrqm/s', 'r/s', 'w/s', 'rkB/s', 'wkB/s', + 'avgrq-sz', 'avgqu-sz', 'await', 'r_await', 'w_await', '%util') + print "%-24s %-12s %7s %7s %6s %6s %8s %8s %8s %8s %7s %7s %7s %5s" % heading + else: + heading = ('# Device', 'rrqm/s', 'wrqm/s', 'r/s', 'w/s', 'rkB/s', 'wkB/s', + 'avgrq-sz', 'avgqu-sz', 'await', 'r_await', 'w_await', '%util') + print "%-12s %7s %7s %6s %6s %8s %8s %8s %8s %7s %7s %7s %5s" % heading + + for inst in instlist: + # basic stats + rrqm = (c_rrqm[inst] - p_rrqm[inst]) / dt + wrqm = (c_wrqm[inst] - p_wrqm[inst]) / dt + r = (c_r[inst] - p_r[inst]) / dt + w = (c_w[inst] - p_w[inst]) / dt + rkb = (c_rkb[inst] - p_rkb[inst]) / dt + wkb = (c_wkb[inst] - p_wkb[inst]) / dt + + # totals + tot_rios = (float)(c_r[inst] - p_r[inst]) + tot_wios = (float)(c_w[inst] - p_w[inst]) + tot_ios = (float)(tot_rios + tot_wios) + + # total active time in seconds (same units as dt) + tot_active = (float)(c_avactive[inst] - p_avactive[inst]) / 1000.0 + + avgrqsz = avgqsz = await = r_await = w_await = util = 0.0 + + # average request size units are KB (sysstat reports in units of sectors) + if tot_ios: + avgrqsz = (float)((c_rkb[inst] - p_rkb[inst]) + (c_wkb[inst] - p_wkb[inst])) / tot_ios + + # average queue length + avgqsz = (float)((c_ractive[inst] - p_ractive[inst]) + (c_wactive[inst] - p_wactive[inst])) / dt / 1000.0 + + # await, r_await, w_await + if tot_ios: + await = ((c_ractive[inst] - p_ractive[inst]) + (c_wactive[inst] - p_wactive[inst])) / tot_ios + + if tot_rios: + r_await = (c_ractive[inst] - p_ractive[inst]) / tot_rios + + if tot_wios: + w_await = (c_wactive[inst] - p_wactive[inst]) / tot_wios + + # device utilization (percentage of active time / interval) + if tot_active: + util = 100.0 * tot_active / dt + + if "t" in IostatOptions.xflag: + print "%-24s %-12s %7.1f %7.1f %6.1f %6.1f %8.1f %8.1f %8.2f %8.2f %7.1f %7.1f %7.1f %5.1f" \ + % (timestamp, inst, rrqm, wrqm, r, w, rkb, wkb, avgrqsz, avgqsz, await, r_await, w_await, util) + else: + print "%-12s %7.1f %7.1f %6.1f %6.1f %8.1f %8.1f %8.2f %8.2f %7.1f %7.1f %7.1f %5.1f" \ + % (inst, rrqm, wrqm, r, w, rkb, wkb, avgrqsz, avgqsz, await, r_await, w_await, util) + + +class IostatOptions(pmapi.pmOptions): + # class attributes + xflag = [] + + def extraOptions(self, opt, optarg, index): + if opt == "x": + IostatOptions.xflag = optarg.replace(',', ' ').split(' ') + else: + print "Warning: option '", opt, "' not recognised" + + def __init__(self): + pmapi.pmOptions.__init__(self, "A:a:D:h:O:S:s:T:t:VZ:z?x:") + self.pmSetLongOptionHeader("General options:") + self.pmSetLongOptionAlign() + self.pmSetLongOptionArchive() + self.pmSetLongOptionDebug() + self.pmSetLongOptionHost() + self.pmSetLongOptionOrigin() + self.pmSetLongOptionStart() + self.pmSetLongOptionSamples() + self.pmSetLongOptionFinish() + self.pmSetLongOptionInterval() + self.pmSetLongOptionVersion() + self.pmSetLongOptionTimeZone() + self.pmSetLongOptionHostZone() + self.pmSetLongOptionHelp() + self.pmSetOptionCallback(self.extraOptions) + self.pmSetLongOptionText(" -x comma separated extended options: [dm][,t][,h]") + self.pmSetLongOptionText(" dm show device-mapper statistics (default is sd devices)") + self.pmSetLongOptionText(" t precede every line with a timestamp in ctime format"); + self.pmSetLongOptionText(" h suppress headings"); + +if __name__ == '__main__': + try: + manager = pmcc.MetricGroupManager.builder(IostatOptions(), sys.argv) + if "dm" in IostatOptions.xflag : + manager["iostat"] = IOSTAT_DM_METRICS + else: + manager["iostat"] = IOSTAT_SD_METRICS + manager.printer = IostatReport() + sts = manager.run() + sys.exit(sts) + except pmapi.pmErr, error: + print '%s: %s\n' % (error.progname(), error.message()) + except pmapi.pmUsageErr, usage: + print usage.message() + sys.exit(1) + except KeyboardInterrupt: + pass |