3
# Copyright (c) 2006-2012 Red Hat, Inc. <http://www.redhat.com>
4
# This file is part of GlusterFS.
6
# This file is licensed to you under your choice of the GNU Lesser
7
# General Public License, version 3 or any later version (LGPLv3 or
8
# later), or the GNU General Public License, version 2 (GPLv2), in all
9
# cases as published by the Free Software Foundation.
12
# texttable - module for creating simple ASCII tables
13
# Incorporated from texttable.py downloaded from
14
# http://jefke.free.fr/stuff/python/texttable/texttable-0.7.0.tar.gz
20
if sys.version >= '2.3':
22
elif sys.version >= '2.2':
23
from optparse import textwrap
25
from optik import textwrap
27
sys.stderr.write("Can't import textwrap module!\n")
33
(True, False) = (1, 0)
36
"""Redefining len here so it will be able to work with non-ASCII characters
38
if not isinstance(iterable, str):
39
return iterable.__len__()
42
return len(unicode(iterable, 'utf'))
44
return iterable.__len__()
46
class ArraySizeError(Exception):
47
"""Exception raised when specified rows don't fit the required size
50
def __init__(self, msg):
52
Exception.__init__(self, msg, '')
64
def __init__(self, max_width=80):
67
- max_width is an integer, specifying the maximum width of the table
68
- if set to 0, size is unlimited, therefore cells won't be wrapped
73
self._max_width = max_width
74
self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \
76
self.set_chars(['-', '|', '+', '='])
82
- reset rows and header
85
self._hline_string = None
90
def header(self, array):
91
"""Specify the header of the table
94
self._check_row_size(array)
95
self._header = map(str, array)
97
def add_row(self, array):
98
"""Add a row in the rows stack
100
- cells can contain newlines and tabs
103
self._check_row_size(array)
104
self._rows.append(map(str, array))
106
def add_rows(self, rows, header=True):
107
"""Add several rows in the rows stack
109
- The 'rows' argument can be either an iterator returning arrays,
110
or a by-dimensional array
111
- 'header' specifies if the first row should be used as the header
115
# nb: don't use 'iter' on by-dimensional arrays, to get a
116
# usable code for python 2.1
118
if hasattr(rows, '__iter__') and hasattr(rows, 'next'):
119
self.header(rows.next())
126
def set_chars(self, array):
127
"""Set the characters used to draw lines between rows and columns
129
- the array should contain 4 fields:
131
[horizontal, vertical, corner, header]
139
raise ArraySizeError, "array should contain 4 characters"
140
array = [ x[:1] for x in [ str(s) for s in array ] ]
141
(self._char_horiz, self._char_vert,
142
self._char_corner, self._char_header) = array
144
def set_deco(self, deco):
145
"""Set the table decoration
147
- 'deco' can be a combinaison of:
149
Texttable.BORDER: Border around the table
150
Texttable.HEADER: Horizontal line below the header
151
Texttable.HLINES: Horizontal lines between rows
152
Texttable.VLINES: Vertical lines between columns
154
All of them are enabled by default
158
Texttable.BORDER | Texttable.HEADER
163
def set_cols_align(self, array):
164
"""Set the desired columns alignment
166
- the elements of the array should be either "l", "c" or "r":
168
* "l": column flushed left
169
* "c": column centered
170
* "r": column flushed right
173
self._check_row_size(array)
176
def set_cols_valign(self, array):
177
"""Set the desired columns vertical alignment
179
- the elements of the array should be either "t", "m" or "b":
181
* "t": column aligned on the top of the cell
182
* "m": column aligned on the middle of the cell
183
* "b": column aligned on the bottom of the cell
186
self._check_row_size(array)
189
def set_cols_width(self, array):
190
"""Set the desired columns width
192
- the elements of the array should be integers, specifying the
193
width of each column. For example:
198
self._check_row_size(array)
200
array = map(int, array)
201
if reduce(min, array) <= 0:
204
sys.stderr.write("Wrong argument in column width specification\n")
211
- the table is returned as a whole string
214
if not self._header and not self._rows:
216
self._compute_cols_width()
219
if self._has_border():
222
out += self._draw_line(self._header, isheader=True)
223
if self._has_header():
224
out += self._hline_header()
226
for row in self._rows:
228
out += self._draw_line(row)
229
if self._has_hlines() and length < len(self._rows):
231
if self._has_border():
235
def _check_row_size(self, array):
236
"""Check that the specified array fits the previous rows size
239
if not self._row_size:
240
self._row_size = len(array)
241
elif self._row_size != len(array):
242
raise ArraySizeError, "array should contain %d elements" \
245
def _has_vlines(self):
246
"""Return a boolean, if vlines are required or not
249
return self._deco & Texttable.VLINES > 0
251
def _has_hlines(self):
252
"""Return a boolean, if hlines are required or not
255
return self._deco & Texttable.HLINES > 0
257
def _has_border(self):
258
"""Return a boolean, if border is required or not
261
return self._deco & Texttable.BORDER > 0
263
def _has_header(self):
264
"""Return a boolean, if header line is required or not
267
return self._deco & Texttable.HEADER > 0
269
def _hline_header(self):
270
"""Print header's horizontal line
273
return self._build_hline(True)
276
"""Print an horizontal line
279
if not self._hline_string:
280
self._hline_string = self._build_hline()
281
return self._hline_string
283
def _build_hline(self, is_header=False):
284
"""Return a string used to separated rows or separate header from
287
horiz = self._char_horiz
289
horiz = self._char_header
290
# compute cell separator
291
s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()],
294
l = s.join([horiz*n for n in self._width])
295
# add border if needed
296
if self._has_border():
297
l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz,
303
def _len_cell(self, cell):
304
"""Return the width of the cell
306
Special characters are taken into account to return the width of the
307
cell, such like newlines and tabs
310
cell_lines = cell.split('\n')
312
for line in cell_lines:
314
parts = line.split('\t')
315
for part, i in zip(parts, range(1, len(parts) + 1)):
316
length = length + len(part)
318
length = (length/8 + 1)*8
319
maxi = max(maxi, length)
322
def _compute_cols_width(self):
323
"""Return an array with the width of each column
325
If a specific width has been specified, exit. If the total of the
326
columns width exceed the table desired width, another width will be
327
computed to fit, and cells will be wrapped.
330
if hasattr(self, "_width"):
334
maxi = [ self._len_cell(x) for x in self._header ]
335
for row in self._rows:
336
for cell,i in zip(row, range(len(row))):
338
maxi[i] = max(maxi[i], self._len_cell(cell))
339
except (TypeError, IndexError):
340
maxi.append(self._len_cell(cell))
342
length = reduce(lambda x,y: x+y, maxi)
343
if self._max_width and length + items*3 + 1 > self._max_width:
344
maxi = [(self._max_width - items*3 -1) / items \
345
for n in range(items)]
348
def _check_align(self):
349
"""Check if alignment has been specified, set default one if not
352
if not hasattr(self, "_align"):
353
self._align = ["l"]*self._row_size
354
if not hasattr(self, "_valign"):
355
self._valign = ["t"]*self._row_size
357
def _draw_line(self, line, isheader=False):
360
Loop over a single cell length, over all the cells
363
line = self._splitit(line, isheader)
366
for i in range(len(line[0])):
367
if self._has_border():
368
out += "%s " % self._char_vert
370
for cell, width, align in zip(line, self._width, self._align):
373
fill = width - len(cell_line)
377
out += "%s " % (fill * space + cell_line)
379
out += "%s " % (fill/2 * space + cell_line \
380
+ (fill/2 + fill%2) * space)
382
out += "%s " % (cell_line + fill * space)
383
if length < len(line):
384
out += "%s " % [space, self._char_vert][self._has_vlines()]
385
out += "%s\n" % ['', self._char_vert][self._has_border()]
388
def _splitit(self, line, isheader):
389
"""Split each element of line to fit the column width
391
Each element is turned into a list, result of the wrapping of the
392
string to the desired width
396
for cell, width in zip(line, self._width):
398
for c in cell.split('\n'):
399
array.extend(textwrap.wrap(unicode(c, 'utf'), width))
400
line_wrapped.append(array)
401
max_cell_lines = reduce(max, map(len, line_wrapped))
402
for cell, valign in zip(line_wrapped, self._valign):
406
missing = max_cell_lines - len(cell)
407
cell[:0] = [""] * (missing / 2)
408
cell.extend([""] * (missing / 2 + missing % 2))
410
cell[:0] = [""] * (max_cell_lines - len(cell))
412
cell.extend([""] * (max_cell_lines - len(cell)))
416
# Copyright (c) 2010-2011 Gluster, Inc. <http://www.gluster.com>
417
# This file is part of GlusterFS.
419
# GlusterFS is free software; you can redistribute it and/or modify
420
# it under the terms of the GNU General Public License as published
421
# by the Free Software Foundation; either version 3 of the License,
422
# or (at your option) any later version.
424
# GlusterFS is distributed in the hope that it will be useful, but
425
# WITHOUT ANY WARRANTY; without even the implied warranty of
426
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
427
# General Public License for more details.
429
# You should have received a copy of the GNU General Public License
430
# along with this program. If not, see
431
# <http://www.gnu.org/licenses/>.
433
graph_available = True
437
import matplotlib.pyplot as plt
439
graph_available = False
444
from optparse import OptionParser
446
# Global dict-of-dict holding the latency data
447
# latency[xlator-name][op-name]
454
"""Collect latency data from the file object f and store it in
455
the global variable @latencies"""
457
# example dump file line:
458
# fuse.latency.TRUNCATE=3147.000,4
461
m = re.search ("(\w+)\.\w+.(\w+)=(\w+\.\w+),(\w+),(\w+.\w+)", line)
462
if m and float(m.group(3)) != 0:
469
if not xlator in latencies.keys():
470
latencies[xlator] = dict()
472
if not xlator in counts.keys():
473
counts[xlator] = dict()
475
if not xlator in totals.keys():
476
totals[xlator] = dict()
478
latencies[xlator][op] = time
479
counts[xlator][op] = count
480
totals[xlator][op] = total
483
def calc_latency_heights (xlator_order):
484
heights = map (lambda x: [], xlator_order)
486
N = len (xlator_order)
490
k = latencies[xl].keys()
493
if i == len (xlator_order) - 1:
495
heights[i] = [float (latencies[xl][key]) for key in k]
498
next_xl = xlator_order[i+1]
499
this_xl_time = [latencies[xl][key] for key in k]
500
next_xl_time = [latencies[next_xl][key] for key in k]
502
heights[i] = map (lambda x, y: float (x) - float (y),
503
this_xl_time, next_xl_time)
506
# have sufficient number of colors
507
colors = ["violet", "blue", "green", "yellow", "orange", "red"]
509
def latency_profile (title, xlator_order, mode):
510
heights = calc_latency_heights (xlator_order)
512
N = len (latencies[xlator_order[0]].keys())
513
Nxl = len (xlator_order)
517
pieces = map (lambda x: [], xlator_order)
518
bottoms = map (lambda x: [], xlator_order)
520
bottoms[Nxl-1] = map (lambda x: 0, latencies[xlator_order[0]].keys())
522
k = latencies[xlator_order[0]].keys()
525
for i in range (Nxl-1):
526
xl = xlator_order[i+1]
527
bottoms[i] = [float(latencies[xl][key]) for key in k]
530
print "\n%sLatency profile for %s\n" % (' '*20, title)
531
print "Average latency (microseconds):\n"
535
table.set_cols_align(["l", "r"] + ["r"] * len(xlator_order))
538
header = ['OP', 'OP Average (us)'] + xlator_order
542
sum = reduce (lambda x, y: x + y, [heights[xlator_order.index(xl)][k.index(op)] for xl in xlator_order],
546
row += ["%5.2f" % sum]
548
for xl in xlator_order:
549
op_index = k.index(op)
550
row += ["%5.2f" % (heights[xlator_order.index(xl)][op_index])]
554
def row_sort(r1, r2):
565
rows.sort(row_sort, reverse=True)
566
table.add_rows([header] + rows)
569
elif mode == 'graph':
571
pieces[i] = plt.bar (ind, heights[i], width, color=colors[i],
574
plt.ylabel ("Average Latency (microseconds)")
575
plt.title ("Latency Profile for '%s'" % title)
576
k = latencies[xlator_order[0]].keys()
578
plt.xticks (ind+width/2., k)
580
m = round (max(map (float, latencies[xlator_order[0]].values())), -2)
581
plt.yticks (np.arange(0, m + m*0.1, m/10))
582
plt.legend (map (lambda p: p[0], pieces), xlator_order)
586
print "Unknown mode specified!"
590
def fop_distribution (title, xlator_order, mode):
591
plt.ylabel ("Percentage of calls")
592
plt.title ("FOP distribution for '%s'" % title)
593
k = counts[xlator_order[0]].keys()
596
N = len (latencies[xlator_order[0]].keys())
601
top_xl = xlator_order[0]
603
total += int(counts[top_xl][op])
608
heights.append (float(counts[top_xl][op])/total * 100)
611
print "\n%sFOP distribution for %s\n" % (' '*20, title)
612
print "Total number of calls: %d\n" % total
616
table.set_cols_align(["l", "r", "r"])
619
header = ["OP", "% of Calls", "Count"]
622
row = [op, "%5.2f" % (float(counts[top_xl][op])/total * 100), counts[top_xl][op]]
625
def row_sort(r1, r2):
636
rows.sort(row_sort, reverse=True)
637
table.add_rows([header] + rows)
640
elif mode == 'graph':
641
bars = plt.bar (ind, heights, width, color="red")
644
height = bar.get_height()
645
plt.text (bar.get_x()+bar.get_width()/2., 1.05*height,
646
"%d%%" % int(height))
648
plt.xticks(ind+width/2., k)
649
plt.yticks(np.arange (0, 110, 10))
653
print "mode not specified!"
657
def calc_workload_heights (xlator_order, scaling):
658
workload_heights = map (lambda x: [], xlator_order)
660
top_xl = xlator_order[0]
662
N = len (xlator_order)
666
k = totals[xl].keys()
669
if i == len (xlator_order) - 1:
671
workload_heights[i] = [float (totals[xl][key]) / float(totals[top_xl][key]) * scaling[k.index(key)] for key in k]
674
next_xl = xlator_order[i+1]
675
this_xl_time = [float(totals[xl][key]) / float(totals[top_xl][key]) * scaling[k.index(key)] for key in k]
676
next_xl_time = [float(totals[next_xl][key]) / float(totals[top_xl][key]) * scaling[k.index(key)] for key in k]
678
workload_heights[i] = map (lambda x, y: (float (x) - float (y)),
679
this_xl_time, next_xl_time)
681
return workload_heights
683
def workload_profile(title, xlator_order, mode):
684
plt.ylabel ("Percentage of Total Time")
685
plt.title ("Workload Profile for '%s'" % title)
686
k = totals[xlator_order[0]].keys()
689
N = len(totals[xlator_order[0]].keys())
690
Nxl = len(xlator_order)
695
top_xl = xlator_order[0]
697
total += float(totals[top_xl][op])
702
p_heights.append (float(totals[top_xl][op])/total * 100)
704
heights = calc_workload_heights (xlator_order, p_heights)
706
pieces = map (lambda x: [], xlator_order)
707
bottoms = map (lambda x: [], xlator_order)
709
bottoms[Nxl-1] = map (lambda x: 0, totals[xlator_order[0]].keys())
711
for i in range (Nxl-1):
712
xl = xlator_order[i+1]
713
k = totals[xl].keys()
716
bottoms[i] = [float(totals[xl][key]) / float(totals[top_xl][key]) * p_heights[k.index(key)] for key in k]
719
print "\n%sWorkload profile for %s\n" % (' '*20, title)
720
print "Total Time: %d microseconds = %.1f seconds = %.1f minutes\n" % (total, total / 1000000.0, total / 6000000.0)
723
table.set_cols_align(["l", "r"] + ["r"] * len(xlator_order))
726
header = ['OP', 'OP Total (%)'] + xlator_order
730
sum = reduce (lambda x, y: x + y, [heights[xlator_order.index(xl)][k.index(op)] for xl in xlator_order],
733
row += ["%5.2f" % sum]
735
for xl in xlator_order:
736
op_index = k.index(op)
737
row += ["%5.2f" % heights[xlator_order.index(xl)][op_index]]
741
def row_sort(r1, r2):
752
rows.sort(row_sort, reverse=True)
753
table.add_rows([header] + rows)
756
elif mode == 'graph':
758
pieces[i] = plt.bar (ind, heights[i], width, color=colors[i],
762
bar = pieces[Nxl-1][k.index(key)]
763
plt.text (bar.get_x() + bar.get_width()/2., 1.05*p_heights[k.index(key)],
764
"%d%%" % int(p_heights[k.index(key)]))
766
plt.xticks(ind+width/2., k)
767
plt.yticks(np.arange (0, 110, 10))
768
plt.legend (map (lambda p: p[0], pieces), xlator_order)
772
print "Unknown mode specified!"
777
parser = OptionParser(usage="usage: %prog [-l | -d | -w] -x <xlator order> <state dump file>")
778
parser.add_option("-l", "--latency", dest="latency", action="store_true",
779
help="Produce latency profile")
780
parser.add_option("-d", "--distribution", dest="distribution", action="store_true",
781
help="Produce distribution of FOPs")
782
parser.add_option("-w", "--workload", dest="workload", action="store_true",
783
help="Produce workload profile")
784
parser.add_option("-t", "--title", dest="title", help="Set the title of the graph")
785
parser.add_option("-x", "--xlator-order", dest="xlator_order", help="Specify the order of xlators")
786
parser.add_option("-m", "--mode", dest="mode", help="Output format, can be text[default] or graph")
788
(options, args) = parser.parse_args()
791
parser.error("Incorrect number of arguments")
793
if (options.xlator_order):
794
xlator_order = options.xlator_order.split()
796
print "xlator order must be specified"
799
collect_data(file (args[0], 'r'))
804
if options.mode == 'graph' and graph_available == False:
805
print "matplotlib not available, falling back to text mode"
808
if (options.latency):
809
latency_profile (options.title, xlator_order, mode)
811
if (options.distribution):
812
fop_distribution(options.title, xlator_order, mode)
814
if (options.workload):
815
workload_profile(options.title, xlator_order, mode)