summaryrefslogtreecommitdiff
path: root/src/pmgadgets/pmgsys.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmgadgets/pmgsys.py')
-rwxr-xr-xsrc/pmgadgets/pmgsys.py380
1 files changed, 380 insertions, 0 deletions
diff --git a/src/pmgadgets/pmgsys.py b/src/pmgadgets/pmgsys.py
new file mode 100755
index 0000000..5f8a1df
--- /dev/null
+++ b/src/pmgadgets/pmgsys.py
@@ -0,0 +1,380 @@
+#!/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
+# 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.
+#
+
+""" Rewrite of pmgsys (originally a C++ application) in python
+
+ Needs to handle the following options at some point:
+ -h (host), -cpudelta (interval), -l (label), -v (verbose),
+ -c (config), -zoom (factor), ... rest through to pmgadgets.
+"""
+
+from sys import argv
+from pcp import pmapi
+from math import sqrt
+from cpmapi import PM_TYPE_U32
+
+PCPARGS = "" # for pmgadgets-launched child processes to use
+
+# magic numbers, from original pmgsys algorithms
+CPUDELTA = 0.5
+LOADDELTA = 5
+FONTASCENT = 7
+DOLABEL = 1
+HSPACE = 5
+VSPACE = 2
+CPUWIDTH = 30
+CPUHEIGHT = 4
+LOADWIDTH = CPUWIDTH
+LOADHEIGHT = 4 * CPUHEIGHT + 3 * VSPACE
+MEMWIDTH = CPUHEIGHT
+MEMHEIGHT = LOADHEIGHT
+DISKSIZE = 6
+NETWIDTH = CPUWIDTH
+NETHEIGHT = CPUHEIGHT
+
+class Machine(object):
+ def __init__(self, context):
+ # pcp context
+ self.context = context
+ # hardware bits
+ self.ncpu = 0
+ self.ndisk = 0
+ self.niface = 0
+ self.ndiskmaps = 0
+ self.memory = 0
+ # external names
+ self.cpus = []
+ self.disks = []
+ self.parts = []
+ self.ifaces = []
+ self.diskmaps = []
+
+ def get_hinv(self):
+ """ Extract counts of CPUs, disks, interfaces and memory size
+ """
+ hinv = ('hinv.ncpu', 'hinv.ndisk', 'hinv.ninterface', 'hinv.physmem')
+ pmids = self.context.pmLookupName(hinv)
+ descs = self.context.pmLookupDescs(pmids)
+ result = self.context.pmFetch(pmids)
+ hardware = [0, 0, 0, 0]
+ for x in xrange(4):
+ atom = self.context.pmExtractValue(
+ result.contents.get_valfmt(x),
+ result.contents.get_vlist(x, 0),
+ descs[x].contents.type, PM_TYPE_U32)
+ hardware[x] = atom.ul
+ context.pmFreeResult(result)
+ self.ncpu = hardware[0]
+ self.ndisk = hardware[1]
+ self.niface = hardware[2]
+ self.memory = hardware[3]
+
+ def get_names(self):
+ """ Extract names of CPUs, disks and network interfaces.
+ """
+ inst = ('kernel.percpu.cpu.user', # expand CPU names
+ 'disk.dev.total', # expand disk names
+ 'disk.partitions.total', # expand disk partition names
+ 'network.interface.total.bytes') # expand network interface names
+ pmids = self.context.pmLookupName(inst)
+ descs = self.context.pmLookupDescs(pmids)
+
+ (inst, self.cpus) = self.context.pmGetInDom(descs[0])
+ (inst, self.disks) = self.context.pmGetInDom(descs[1])
+ (inst, self.parts) = self.context.pmGetInDom(descs[2])
+ (inst, self.ifaces) = self.context.pmGetInDom(descs[3])
+
+ def details(self):
+ print "CPUs: ", self.ncpu
+ print "CPU names: ", self.cpus
+ print "Disks: ", self.ndisk
+ print "Disk names: ", self.disks
+ print "Partition names: ", self.parts
+ print "Interfaces: ", self.niface
+ print "Interface names: ", self.ifaces
+ print "Memory: ", self.memory
+
+ def get_diskmaps(self):
+ """ Produce disk -> partition mappings
+ This means grouping "sda1 sda2 sda3 sdb1" into two mappings
+ - "sda" -> (sda1, sda2, sda3) and "sdb" -> (sdb1); done via
+ the disk.dev.total and disk.partitions.total metrics.
+ (original: controller -> disk mappings, but ENODATA)
+ """
+ return 0
+
+ def inventory(self):
+ """ Wrap calls to getting counts and subsystem names
+ """
+ self.get_hinv()
+ self.get_names()
+ self.get_diskmaps()
+
+ def gadgetize(self):
+ """ Generate a pmgadgets configuration for this host
+ """
+ print "pmgadgets 1", # follow with command line
+ for arg in argv:
+ print "\"%s\"" % (arg),
+ print
+
+ rows = 1
+ ctiles = int((self.ncpu - 1) / 4 + 1) # always at least one cpu
+ ntiles = int((self.niface - 1) / 4 + 1)
+ if ctiles > 3:
+ cr = int(math.sqrt(ctiles))
+ if cr > rows:
+ rows = cr
+ if ntiles > 3:
+ nr = int(math.sqrt(ntiles))
+ if nr > rows:
+ rows = nr
+
+ baseY = VSPACE
+ if DOLABEL == 1:
+ y = FONTASCENT + VSPACE
+ hostname = self.context.pmGetContextHostName()
+ print "_label %d %d \"%s\"" % (HSPACE, y, hostname)
+ baseY += y
+ baseX = maxX = HSPACE
+ maxY = baseY
+
+ print "_actions cpuActions ("
+ print " \"pmchart\"\t\t\"pmchart -c CPU%s\"" % (PCPARGS)
+ print " \"mpvis *\"\t\t\"mpvis%s\" _default" % (PCPARGS)
+ # original had IRIX gr_top and gr_osview tools next;
+ # perhaps some fine day we could implement these as
+ # pmgadgets front-end tools (certainly the latter)
+ print ")"
+
+ y = baseY + FONTASCENT
+ x = baseX
+ print "_label %d %d \"CPU\"" % (x, y)
+ print " _actions cpuActions\n"
+ # original: "these should match the colours in mpvis"
+ print "_colourlist cpuColours (blue3 red3 yellow3 cyan3 green3)"
+
+ # place the CPU bars
+ y += VSPACE
+ ccols = int((ctiles + rows - 1) / rows)
+
+ cpu = 0
+ for rc in range(0, self.ncpu):
+ for ct in range(0, ccols):
+ tc = 0
+ while (cpu < self.ncpu and tc < 4):
+ print "_multibar %d %d %d %d" % (x, y, CPUWIDTH, CPUHEIGHT)
+ print(" _update %f" % (CPUDELTA)).rstrip('0').rstrip('.')
+ print " _metrics ("
+ print "\tkernel.percpu.cpu.user[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.sys[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.intr[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.wait.total[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.idle[\"%s\"]" % (self.cpus[cpu])
+ print " )"
+ print " _maximum 0.0\n"
+ print " _colourlist cpuColours"
+ print " _actions cpuActions\n"
+ cpu += 1
+ tc += 1
+ y += VSPACE + CPUHEIGHT
+
+ if maxY < y:
+ maxY = y
+ y -= (VSPACE + CPUHEIGHT) * tc
+ x += HSPACE + CPUWIDTH
+
+ y += (CPUHEIGHT + VSPACE) * 4 + VSPACE
+ if (maxX < x):
+ maxX = x
+ x = baseX
+
+ baseX += (HSPACE + CPUWIDTH) * ccols
+
+ # The load gadget and its label
+ print "_actions loadActions ("
+ print " \"pmchart *\"",
+ print "\t\"pmchart -c LoadAvg%s\" _default" % (PCPARGS)
+ # original had IRIX gr_top here
+ print ")"
+ print "_label %d %d \"Load\"" % (baseX, baseY + FONTASCENT)
+ print " _actions loadActions"
+ print
+
+ y = VSPACE + baseY + FONTASCENT
+
+ i = y + LOADHEIGHT
+ if (i > maxY):
+ maxY = i
+
+ print "_bargraph %d %d %d %d" % (baseX, y, LOADWIDTH, LOADHEIGHT)
+ print(" _update %f" % (LOADDELTA)).rstrip('0').rstrip('.')
+ print " _metric kernel.all.load[\"1 minute\"]"
+ print " _max 1.0"
+ print " _actions loadActions"
+
+ # For more than one row, stack LoadAvg on top of Memory.
+ #
+ # Move baseX just after the right hand side of the memory, so
+ # we don't have to do anything special for the netifs. For the
+ # sake of argument, consider total width occupied by memory
+ # gauges equal to total width of loadavg graph
+ if (rows > 1):
+ y += LOADHEIGHT + VSPACE
+ baseX += LOADWIDTH + HSPACE
+ else:
+ y += baseY
+ baseX += LOADWIDTH * 2 + HSPACE * 2
+
+ # The memory gadgets and their label (platform-specific!)
+ x = baseX - LOADWIDTH - HSPACE
+ y += FONTASCENT
+ print "_label %d %d \"Mem\"\n" % (x, y)
+ print "_colourlist memColours (cyan1 red yellow green)\n"
+ y += VSPACE
+ print "_multibar %d %d %d %d" % (x, y, MEMWIDTH, MEMHEIGHT)
+ print " _update 0.5"
+ print " _metrics ("
+ print "\tmem.util.cached"
+ print "\tmem.util.bufmem"
+ print "\tmem.util.other"
+ print "\tmem.util.free"
+ print " )"
+ print " _colourlist memColours"
+ x += HSPACE + MEMWIDTH
+ print "_bar %d %d %d %d" % (x, y, MEMWIDTH, MEMHEIGHT)
+ print " _metric swap.pagesout"
+ print " _vertical"
+
+ # Check for the max horizontal offset
+ i = y + MEMHEIGHT
+ if (i > maxY):
+ maxY = i
+
+ # The network bars and their label
+ print "_colourlist netColours (aquamarine orange)"
+ print "_actions netActions ("
+ print " \"pmchart-packets *\"",
+ print "\t\"pmchart -c NetPackets%s\" _default" % (PCPARGS)
+ print " \"pmchart-bytes\"",
+ print "\t\t\"pmchart -c NetBytes%s\"" % (PCPARGS)
+ # original had netstat within an xterm here, next
+ print ")"
+
+ y = baseY + FONTASCENT
+ x = baseX
+
+ print "_label %d %d \"Net\"" % (x, y)
+ print " _actions netActions\n"
+
+ y += VSPACE
+
+ ncols = int((ntiles + rows - 1) / rows)
+
+ ni = 0
+ while ni < self.niface:
+ for nt in range(0, ncols):
+ tc = 0
+ while ni < self.niface and tc < 4:
+ print "_multibar %d %d %d %d" % (x, y, NETWIDTH, NETHEIGHT)
+ print " _metrics ("
+ print "\tnetwork.interface.in.bytes[\"%s\"]" % (self.ifaces[ni])
+ print "\tnetwork.interface.out.bytes[\"%s\"]" % (self.ifaces[ni])
+ print " )"
+ print "_colourlist netColours"
+ print " _actions netActions"
+ y += NETHEIGHT + VSPACE
+
+ tc += 1
+ ni += 1
+
+ if maxY < y:
+ maxY = y
+ y -= (NETHEIGHT + VSPACE) * tc
+ x += NETWIDTH + HSPACE
+ if maxX < x:
+ maxX = x
+ y += (NETHEIGHT + VSPACE) * 4 + VSPACE
+ x = baseX
+ print
+
+ # Disks
+ dir = 1
+
+ print "_actions diskActions ("
+ print " \"pmchart\"",
+ print "\t\t\"pmchart -c Disk%s\"" % (PCPARGS)
+ print " \"dkvis *\"",
+ print "\t\t\"dkvis%s\" _default" % (PCPARGS)
+ print ")"
+
+ x = HSPACE
+ y = maxY + FONTASCENT + 2 * VSPACE
+ print "_label %d %d \"Disk\"" % (x, y)
+ print " _actions diskActions\n"
+ print "_legend diskLegend ("
+ print " _default green3"
+ print " 15 yellow"
+ print " 40 orange"
+ print " 75 red"
+ print ")"
+
+ x += CPUWIDTH + HSPACE
+ # this only works if FONTASCENT >= ledSize
+ y -= DISKSIZE
+ thickness = 4
+ halfDiskSize = int((DISKSIZE - thickness) / 2)
+
+ for i in range(0, self.ndiskmaps):
+ mapping = self.diskmaps[i]
+ for j in range(0, len(mappings)):
+ if j > 0:
+ if oldX < x: # moved to right
+ lx = x - VSPACE - 1
+ ly = y + halfDiskSize
+ lw = VSPACE + 2
+ lh = thickness
+ elif oldX > x: # moved to left
+ lx = oldX - VSPACE - 1
+ ly = y + halfDiskSize
+ lw = VSPACE + 2
+ lh = thickness
+ else: # moved down
+ lx = x + halfDiskSize
+ ly = oldY + DISKSIZE - 1
+ lw = thickness
+ lh = VSPACE + 2
+ print "_line %d %d %d %d" % (lx, ly, lw, lh)
+ print "_led %d %d %d %d" % (x, y, DISKSIZE, DISKSIZE)
+ print " _metric disk.dev.total[\"%s\"]" % (mapping.name())
+ print " _legend diskLegend"
+ print " _actions diskActions"
+ oldX = x
+ oldY = y
+ xStep = dir * (DISKSIZE + VSPACE) # use VSPACE (tighter packing)
+ x += xStep
+ if x > maxX - DISKSIZE or x <= HSPACE:
+ x -= xStep
+ y += DISKSIZE + VSPACE
+ dir = -dir
+
+
+if __name__ == '__main__':
+ context = pmapi.pmContext()
+ machine = Machine(context)
+ machine.inventory()
+ # machine.details()
+ machine.gadgetize()
+