docs: describe the pmdaroot process interfaces
[pcp.git] / src / pmgadgets / pmgsys.py
blob5e5fec88f72deedde8da8d1288309a02d7c42b85
1 #!/usr/bin/pcp python
3 # Copyright (C) 2014 Red Hat.
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # for more details.
16 """ Rewrite of pmgsys (originally a C++ application) in python
18 Needs to handle the following options at some point:
19 -h (host), -cpudelta (interval), -l (label), -v (verbose),
20 -c (config), -zoom (factor), ... rest through to pmgadgets.
21 """
23 from sys import argv
24 from pcp import pmapi
25 from math import sqrt
26 from cpmapi import PM_TYPE_U32
28 PCPARGS = "" # for pmgadgets-launched child processes to use
30 # magic numbers, from original pmgsys algorithms
31 CPUDELTA = 0.5
32 LOADDELTA = 5
33 FONTASCENT = 7
34 DOLABEL = 1
35 HSPACE = 5
36 VSPACE = 2
37 CPUWIDTH = 30
38 CPUHEIGHT = 4
39 LOADWIDTH = CPUWIDTH
40 LOADHEIGHT = 4 * CPUHEIGHT + 3 * VSPACE
41 MEMWIDTH = CPUHEIGHT
42 MEMHEIGHT = LOADHEIGHT
43 DISKSIZE = 6
44 NETWIDTH = CPUWIDTH
45 NETHEIGHT = CPUHEIGHT
47 class Machine(object):
48 def __init__(self, context):
49 # pcp context
50 self.context = context
51 # hardware bits
52 self.ncpu = 0
53 self.ndisk = 0
54 self.niface = 0
55 self.ndiskmaps = 0
56 self.memory = 0
57 # external names
58 self.cpus = []
59 self.disks = []
60 self.parts = []
61 self.ifaces = []
62 self.diskmaps = []
64 def get_hinv(self):
65 """ Extract counts of CPUs, disks, interfaces and memory size
66 """
67 hinv = ('hinv.ncpu', 'hinv.ndisk', 'hinv.ninterface', 'hinv.physmem')
68 pmids = self.context.pmLookupName(hinv)
69 descs = self.context.pmLookupDescs(pmids)
70 result = self.context.pmFetch(pmids)
71 hardware = [0, 0, 0, 0]
72 for x in xrange(4):
73 atom = self.context.pmExtractValue(
74 result.contents.get_valfmt(x),
75 result.contents.get_vlist(x, 0),
76 descs[x].contents.type, PM_TYPE_U32)
77 hardware[x] = atom.ul
78 context.pmFreeResult(result)
79 self.ncpu = hardware[0]
80 self.ndisk = hardware[1]
81 self.niface = hardware[2]
82 self.memory = hardware[3]
84 def get_names(self):
85 """ Extract names of CPUs, disks and network interfaces.
86 """
87 inst = ('kernel.percpu.cpu.user', # expand CPU names
88 'disk.dev.total', # expand disk names
89 'disk.partitions.total', # expand disk partition names
90 'network.interface.total.bytes') # expand network interface names
91 pmids = self.context.pmLookupName(inst)
92 descs = self.context.pmLookupDescs(pmids)
94 (inst, self.cpus) = self.context.pmGetInDom(descs[0])
95 (inst, self.disks) = self.context.pmGetInDom(descs[1])
96 (inst, self.parts) = self.context.pmGetInDom(descs[2])
97 (inst, self.ifaces) = self.context.pmGetInDom(descs[3])
99 def details(self):
100 print "CPUs: ", self.ncpu
101 print "CPU names: ", self.cpus
102 print "Disks: ", self.ndisk
103 print "Disk names: ", self.disks
104 print "Partition names: ", self.parts
105 print "Interfaces: ", self.niface
106 print "Interface names: ", self.ifaces
107 print "Memory: ", self.memory
109 def get_diskmaps(self):
110 """ Produce disk -> partition mappings
111 This means grouping "sda1 sda2 sda3 sdb1" into two mappings
112 - "sda" -> (sda1, sda2, sda3) and "sdb" -> (sdb1); done via
113 the disk.dev.total and disk.partitions.total metrics.
114 (original: controller -> disk mappings, but ENODATA)
116 return 0
118 def inventory(self):
119 """ Wrap calls to getting counts and subsystem names
121 self.get_hinv()
122 self.get_names()
123 self.get_diskmaps()
125 def gadgetize(self):
126 """ Generate a pmgadgets configuration for this host
128 print "pmgadgets 1", # follow with command line
129 for arg in argv:
130 print "\"%s\"" % (arg),
131 print
133 rows = 1
134 ctiles = int((self.ncpu - 1) / 4 + 1) # always at least one cpu
135 ntiles = int((self.niface - 1) / 4 + 1)
136 if ctiles > 3:
137 cr = int(math.sqrt(ctiles))
138 if cr > rows:
139 rows = cr
140 if ntiles > 3:
141 nr = int(math.sqrt(ntiles))
142 if nr > rows:
143 rows = nr
145 baseY = VSPACE
146 if DOLABEL == 1:
147 y = FONTASCENT + VSPACE
148 hostname = self.context.pmGetContextHostName()
149 print "_label %d %d \"%s\"" % (HSPACE, y, hostname)
150 baseY += y
151 baseX = maxX = HSPACE
152 maxY = baseY
154 print "_actions cpuActions ("
155 print " \"pmchart\"\t\t\"pmchart -c CPU%s\"" % (PCPARGS)
156 print " \"mpvis *\"\t\t\"mpvis%s\" _default" % (PCPARGS)
157 # original had IRIX gr_top and gr_osview tools next;
158 # perhaps some fine day we could implement these as
159 # pmgadgets front-end tools (certainly the latter)
160 print ")"
162 y = baseY + FONTASCENT
163 x = baseX
164 print "_label %d %d \"CPU\"" % (x, y)
165 print " _actions cpuActions\n"
166 # original: "these should match the colours in mpvis"
167 print "_colourlist cpuColours (blue3 red3 yellow3 cyan3 green3)"
169 # place the CPU bars
170 y += VSPACE
171 ccols = int((ctiles + rows - 1) / rows)
173 cpu = 0
174 for rc in range(0, self.ncpu):
175 for ct in range(0, ccols):
176 tc = 0
177 while (cpu < self.ncpu and tc < 4):
178 print "_multibar %d %d %d %d" % (x, y, CPUWIDTH, CPUHEIGHT)
179 print(" _update %f" % (CPUDELTA)).rstrip('0').rstrip('.')
180 print " _metrics ("
181 print "\tkernel.percpu.cpu.user[\"%s\"]" % (self.cpus[cpu])
182 print "\tkernel.percpu.cpu.sys[\"%s\"]" % (self.cpus[cpu])
183 print "\tkernel.percpu.cpu.intr[\"%s\"]" % (self.cpus[cpu])
184 print "\tkernel.percpu.cpu.wait.total[\"%s\"]" % (self.cpus[cpu])
185 print "\tkernel.percpu.cpu.idle[\"%s\"]" % (self.cpus[cpu])
186 print " )"
187 print " _maximum 0.0\n"
188 print " _colourlist cpuColours"
189 print " _actions cpuActions\n"
190 cpu += 1
191 tc += 1
192 y += VSPACE + CPUHEIGHT
194 if maxY < y:
195 maxY = y
196 y -= (VSPACE + CPUHEIGHT) * tc
197 x += HSPACE + CPUWIDTH
199 y += (CPUHEIGHT + VSPACE) * 4 + VSPACE
200 if (maxX < x):
201 maxX = x
202 x = baseX
204 baseX += (HSPACE + CPUWIDTH) * ccols
206 # The load gadget and its label
207 print "_actions loadActions ("
208 print " \"pmchart *\"",
209 print "\t\"pmchart -c LoadAvg%s\" _default" % (PCPARGS)
210 # original had IRIX gr_top here
211 print ")"
212 print "_label %d %d \"Load\"" % (baseX, baseY + FONTASCENT)
213 print " _actions loadActions"
214 print
216 y = VSPACE + baseY + FONTASCENT
218 i = y + LOADHEIGHT
219 if (i > maxY):
220 maxY = i
222 print "_bargraph %d %d %d %d" % (baseX, y, LOADWIDTH, LOADHEIGHT)
223 print(" _update %f" % (LOADDELTA)).rstrip('0').rstrip('.')
224 print " _metric kernel.all.load[\"1 minute\"]"
225 print " _max 1.0"
226 print " _actions loadActions"
228 # For more than one row, stack LoadAvg on top of Memory.
230 # Move baseX just after the right hand side of the memory, so
231 # we don't have to do anything special for the netifs. For the
232 # sake of argument, consider total width occupied by memory
233 # gauges equal to total width of loadavg graph
234 if (rows > 1):
235 y += LOADHEIGHT + VSPACE
236 baseX += LOADWIDTH + HSPACE
237 else:
238 y += baseY
239 baseX += LOADWIDTH * 2 + HSPACE * 2
241 # The memory gadgets and their label (platform-specific!)
242 x = baseX - LOADWIDTH - HSPACE
243 y += FONTASCENT
244 print "_label %d %d \"Mem\"\n" % (x, y)
245 print "_colourlist memColours (cyan1 red yellow green)\n"
246 y += VSPACE
247 print "_multibar %d %d %d %d" % (x, y, MEMWIDTH, MEMHEIGHT)
248 print " _update 0.5"
249 print " _metrics ("
250 print "\tmem.util.cached"
251 print "\tmem.util.bufmem"
252 print "\tmem.util.other"
253 print "\tmem.util.free"
254 print " )"
255 print " _colourlist memColours"
256 x += HSPACE + MEMWIDTH
257 print "_bar %d %d %d %d" % (x, y, MEMWIDTH, MEMHEIGHT)
258 print " _metric swap.pagesout"
259 print " _vertical"
261 # Check for the max horizontal offset
262 i = y + MEMHEIGHT
263 if (i > maxY):
264 maxY = i
266 # The network bars and their label
267 print "_colourlist netColours (aquamarine orange)"
268 print "_actions netActions ("
269 print " \"pmchart-packets *\"",
270 print "\t\"pmchart -c NetPackets%s\" _default" % (PCPARGS)
271 print " \"pmchart-bytes\"",
272 print "\t\t\"pmchart -c NetBytes%s\"" % (PCPARGS)
273 # original had netstat within an xterm here, next
274 print ")"
276 y = baseY + FONTASCENT
277 x = baseX
279 print "_label %d %d \"Net\"" % (x, y)
280 print " _actions netActions\n"
282 y += VSPACE
284 ncols = int((ntiles + rows - 1) / rows)
286 ni = 0
287 while ni < self.niface:
288 for nt in range(0, ncols):
289 tc = 0
290 while ni < self.niface and tc < 4:
291 print "_multibar %d %d %d %d" % (x, y, NETWIDTH, NETHEIGHT)
292 print " _metrics ("
293 print "\tnetwork.interface.in.bytes[\"%s\"]" % (self.ifaces[ni])
294 print "\tnetwork.interface.out.bytes[\"%s\"]" % (self.ifaces[ni])
295 print " )"
296 print "_colourlist netColours"
297 print " _actions netActions"
298 y += NETHEIGHT + VSPACE
300 tc += 1
301 ni += 1
303 if maxY < y:
304 maxY = y
305 y -= (NETHEIGHT + VSPACE) * tc
306 x += NETWIDTH + HSPACE
307 if maxX < x:
308 maxX = x
309 y += (NETHEIGHT + VSPACE) * 4 + VSPACE
310 x = baseX
311 print
313 # Disks
314 dir = 1
316 print "_actions diskActions ("
317 print " \"pmchart\"",
318 print "\t\t\"pmchart -c Disk%s\"" % (PCPARGS)
319 print " \"dkvis *\"",
320 print "\t\t\"dkvis%s\" _default" % (PCPARGS)
321 print ")"
323 x = HSPACE
324 y = maxY + FONTASCENT + 2 * VSPACE
325 print "_label %d %d \"Disk\"" % (x, y)
326 print " _actions diskActions\n"
327 print "_legend diskLegend ("
328 print " _default green3"
329 print " 15 yellow"
330 print " 40 orange"
331 print " 75 red"
332 print ")"
334 x += CPUWIDTH + HSPACE
335 # this only works if FONTASCENT >= ledSize
336 y -= DISKSIZE
337 thickness = 4
338 halfDiskSize = int((DISKSIZE - thickness) / 2)
340 for i in range(0, self.ndiskmaps):
341 mapping = self.diskmaps[i]
342 for j in range(0, len(mappings)):
343 if j > 0:
344 if oldX < x: # moved to right
345 lx = x - VSPACE - 1
346 ly = y + halfDiskSize
347 lw = VSPACE + 2
348 lh = thickness
349 elif oldX > x: # moved to left
350 lx = oldX - VSPACE - 1
351 ly = y + halfDiskSize
352 lw = VSPACE + 2
353 lh = thickness
354 else: # moved down
355 lx = x + halfDiskSize
356 ly = oldY + DISKSIZE - 1
357 lw = thickness
358 lh = VSPACE + 2
359 print "_line %d %d %d %d" % (lx, ly, lw, lh)
360 print "_led %d %d %d %d" % (x, y, DISKSIZE, DISKSIZE)
361 print " _metric disk.dev.total[\"%s\"]" % (mapping.name())
362 print " _legend diskLegend"
363 print " _actions diskActions"
364 oldX = x
365 oldY = y
366 xStep = dir * (DISKSIZE + VSPACE) # use VSPACE (tighter packing)
367 x += xStep
368 if x > maxX - DISKSIZE or x <= HSPACE:
369 x -= xStep
370 y += DISKSIZE + VSPACE
371 dir = -dir
374 if __name__ == '__main__':
375 context = pmapi.pmContext()
376 machine = Machine(context)
377 machine.inventory()
378 # machine.details()
379 machine.gadgetize()