+++ /dev/null
-#!/usr/bin/env python
-#
-# Copyright (c) 2012 Pierre-Francois Carpentier <carpentier.pf@gmail.com>
-#
-# https://github.com/kakwa/py-ascii-graph/
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-from __future__ import unicode_literals
-import sys
-import os
-
-
-class Pyasciigraph:
- def __init__(self, line_length=79, min_graph_length=50,
- separator_length=2):
- """Constructor of Pyasciigraph
-
- :param int line_length: the max number of char on a line
- if any line cannot be shorter,
- it will go over this limit
- :param int min_graph_length: the min number of char used by the graph
- :param int separator_length: the length of field separator
- """
- self.line_length = line_length
- self.separator_length = separator_length
- self.min_graph_length = min_graph_length
-
- def _u(self, x):
- if sys.version < '3':
- import codecs
- return codecs.unicode_escape_decode(x)[0]
- else:
- return x
-
- def _get_maximum(self, data):
- all_max = {}
- all_max['value_max_length'] = 0
- all_max['info_max_length'] = 0
- all_max['max_value'] = 0
-
- for (info, value) in data:
- if value > all_max['max_value']:
- all_max['max_value'] = value
-
- if len(info) > all_max['info_max_length']:
- all_max['info_max_length'] = len(info)
-
- if len(str(value)) > all_max['value_max_length']:
- all_max['value_max_length'] = len(str(value))
- return all_max
-
- def _gen_graph_string(self, value, max_value, graph_length, start_value):
- if max_value == 0:
- number_of_square = int(value * graph_length)
- else:
- number_of_square = int(value * graph_length / max_value)
- number_of_space = int(start_value - number_of_square)
- return '█' * number_of_square + self._u(' ') * number_of_space
-
- def _console_size(self):
- TERMSIZE = 80
- return int(os.environ.get('COLUMNS', TERMSIZE)) - 1
-
- def _gen_info_string(self, info, start_info, line_length, info_before):
- number_of_space = (line_length - start_info - len(info))
- if info_before:
- return self._u(' ') * number_of_space + info
- else:
- return info + self._u(' ') * number_of_space
-
- def _gen_value_string(self, value, start_value, start_info, unit, count):
- if not count:
- v = str("%0.02f" % value)
- else:
- # we don't want to add .00 to count values (only integers)
- v = str(value)
- number_space = start_info -\
- start_value -\
- len(v) -\
- self.separator_length
-
- return ' ' * number_space +\
- v + str(unit) +\
- ' ' * self.separator_length
-
- def _sanitize_string(self, string):
- # get the type of a unicode string
- unicode_type = type(self._u('t'))
- input_type = type(string)
- if input_type is str:
- if sys.version_info.major < 3: # pragma: no cover
- info = string
- else:
- info = string
- elif input_type is unicode_type:
- info = string
- elif input_type is int or input_type is float:
- if sys.version_info.major < 3: # pragma: no cover
- info = string
- else:
- info = str(string)
- return info
-
- def _sanitize_data(self, data):
- ret = []
- for item in data:
- ret.append((self._sanitize_string(item[0]), item[1]))
- return ret
-
- def graph(self, label, data, sort=0, with_value=True, unit="",
- info_before=False, count=False):
- """function generating the graph
-
- :param string label: the label of the graph
- :param iterable data: the data (list of tuple (info, value))
- info must be "castable" to a unicode string
- value must be an int or a float
- :param int sort: flag sorted
- 0: not sorted (same order as given) (default)
- 1: increasing order
- 2: decreasing order
- :param boolean with_value: flag printing value
- True: print the numeric value (default)
- False: don't print the numeric value
- :rtype: a list of strings (each lines)
-
- """
- result = []
- san_data = self._sanitize_data(data)
- san_label = self._sanitize_string(label)
-
- if sort == 1:
- san_data = sorted(san_data, key=lambda value: value[1],
- reverse=False)
- elif sort == 2:
- san_data = sorted(san_data, key=lambda value: value[1],
- reverse=True)
-
- all_max = self._get_maximum(san_data)
-
- real_line_length = max(self.line_length, len(label))
-
- min_line_length = self.min_graph_length +\
- 2 * self.separator_length +\
- all_max['value_max_length'] +\
- all_max['info_max_length']
-
- if min_line_length < real_line_length:
- # calcul of where to start info
- start_info = self.line_length -\
- all_max['info_max_length']
- # calcul of where to start value
- start_value = start_info -\
- self.separator_length -\
- all_max['value_max_length']
- # calcul of where to end graph
- graph_length = start_value -\
- self.separator_length
- else:
- # calcul of where to start value
- start_value = self.min_graph_length +\
- self.separator_length
- # calcul of where to start info
- start_info = start_value +\
- all_max['value_max_length'] +\
- self.separator_length
- # calcul of where to end graph
- graph_length = self.min_graph_length
- # calcul of the real line length
- real_line_length = min_line_length
-
- real_line_length = min(real_line_length, self._console_size())
- result.append(san_label)
- result.append(self._u('#') * real_line_length)
-
- for item in san_data:
- info = item[0]
- value = item[1]
-
- graph_string = self._gen_graph_string(
- value,
- all_max['max_value'],
- graph_length,
- start_value)
-
- if with_value:
- value_string = self._gen_value_string(
- value,
- start_value,
- start_info, unit, count)
- else:
- value_string = ""
-
- info_string = self._gen_info_string(
- info,
- start_info,
- real_line_length, info_before)
- if info_before:
- new_line = info_string + " " + graph_string + value_string
- else:
- new_line = graph_string + value_string + info_string
- result.append(new_line)
-
- return result
-
-if __name__ == '__main__':
- test = [('long_label', 423), ('sl', 1234), ('line3', 531),
- ('line4', 200), ('line5', 834)]
- graph = Pyasciigraph()
- for line in graph.graph('test print', test):
- print(line)
from ..common import format_utils
from .command import Command
from ..linuxautomaton import common
-from ..ascii_graph import Pyasciigraph
_UsageTables = collections.namedtuple('_UsageTables', [
return syscall_tables + disk_tables
def _print_one_freq(self, result_table):
- if not result_table.rows:
- return
-
- graph = Pyasciigraph()
- graph_data = []
-
- for row in result_table.rows:
- graph_data.append(('%0.03f' % row.latency_lower.to_us(),
- row.count.value))
-
- title = '{} {} (usec)'.format(result_table.title,
- result_table.subtitle)
- graph_lines = graph.graph(
- title,
- graph_data,
- info_before=True,
- count=True
+ graph = termgraph.FreqGraph(
+ data=result_table.rows,
+ get_value=lambda row: row.count.value,
+ get_lower_bound=lambda row: row.latency_lower.to_us(),
+ title='{} {}'.format(result_table.title, result_table.subtitle),
+ unit='µs'
)
- for line in graph_lines:
- print(line)
-
- print()
+ graph.print_graph()
def _print_freq(self, freq_tables):
for freq_table in freq_tables:
import statistics
import sys
from . import mi
+from . import termgraph
from .command import Command
from ..core import irq as core_irq
-from ..ascii_graph import Pyasciigraph
from ..linuxautomaton import common, sv
return statistics.stdev(raise_latencies)
def _print_frequency_distribution(self, freq_table):
- graph = Pyasciigraph()
- graph_data = []
-
- for row in freq_table.rows:
- # The graph data format is a tuple (info, value). Here info
- # is the lower bound of the bucket, value the bucket's count
- lower_bound_us = row.duration_lower.to_us()
- count = row.count.value
-
- graph_data.append(('%0.03f' % lower_bound_us, count))
-
- title_fmt = 'Handler duration frequency distribution {} (usec)'
-
- graph_lines = graph.graph(
- title_fmt.format(freq_table.subtitle),
- graph_data,
- info_before=True,
- count=True
+ title_fmt = 'Handler duration frequency distribution {}'
+
+ graph = termgraph.FreqGraph(
+ data=freq_table.rows,
+ get_value=lambda row: row.count.value,
+ get_lower_bound=lambda row: row.duration_lower.to_us(),
+ title=title_fmt.format(freq_table.subtitle),
+ unit='µs'
)
- for line in graph_lines:
- print(line)
+ graph.print_graph()
def _filter_irq(self, irq):
if type(irq) is sv.HardIRQ:
import statistics
import collections
from . import mi
+from . import termgraph
from ..core import sched
from .command import Command
from ..linuxautomaton import common
-from ..ascii_graph import Pyasciigraph
_SchedStats = collections.namedtuple('_SchedStats', [
print(row_str)
def _print_frequency_distribution(self, freq_table):
- graph = Pyasciigraph()
- graph_data = []
-
- for row in freq_table.rows:
- # The graph data format is a tuple (info, value). Here info
- # is the lower bound of the bucket, value the bucket's count
- lower_bound_us = row.duration_lower.to_us()
- count = row.count.value
-
- graph_data.append(('%0.03f' % lower_bound_us, count))
-
- title_fmt = 'Scheduling latency (µs) frequency distribution - {}'
-
- graph_lines = graph.graph(
- title_fmt.format(freq_table.subtitle),
- graph_data,
- info_before=True,
- count=True
+ title_fmt = 'Scheduling latency frequency distribution - {}'
+
+ graph = termgraph.FreqGraph(
+ data=freq_table.rows,
+ get_value=lambda row: row.count.value,
+ get_lower_bound=lambda row: row.duration_lower.to_us(),
+ title=title_fmt.format(freq_table.subtitle),
+ unit='µs'
)
- for line in graph_lines:
- print(line)
+ graph.print_graph()
+
def _print_freq(self, freq_tables):
for freq_table in freq_tables:
- if freq_table.rows:
- print()
- self._print_frequency_distribution(freq_table)
+ self._print_frequency_distribution(freq_table)
def _validate_transform_args(self, args):
# If neither --total nor --per-prio are specified, default
from collections import namedtuple
-GraphDatum = namedtuple('GraphDatum', ['value', 'value_str', 'label'])
-
-
-class BarGraph():
- MAX_BAR_WIDTH = 80
+GraphDatum = namedtuple('GraphDatum', ['value', 'value_str'])
+BarGraphDatum = namedtuple('BarGraphDatum', ['value', 'value_str', 'label'])
+FreqGraphDatum = namedtuple(
+ 'FreqGraphDatum', ['value', 'value_str', 'lower_bound']
+)
+
+class Graph():
+ MAX_GRAPH_WIDTH = 80
BAR_CHAR = '█'
HR_CHAR = '#'
- def __init__(self, data, get_value, get_label, get_value_str=None,
- title=None, label_header=None, unit=None):
- self._max_value = 0
- self._max_value_len = 0
+ def __init__(self, data, get_value, get_value_str, title, unit):
+ self._data = data
+ self._get_value = get_value
self._title = title
- self._label_header = label_header
self._unit = unit
+ self._max_value = 0
+ self._max_value_len = 0
- self._data = self._transform_data(
- data, get_value, get_value_str, get_label
- )
+ if get_value_str is not None:
+ self._get_value_str_cb = get_value_str
+ else:
+ self._get_value_str_cb = Graph._get_value_str_default
- def _transform_data(self, data, get_value, get_value_str, get_label):
+ def _transform_data(self, data):
graph_data = []
- if get_value_str is None:
- get_value_str = self._get_value_str
for datum in data:
- value = get_value(datum)
- value_str = get_value_str(value)
- value_len = len(value_str)
- label = get_label(datum)
+ graph_datum = self._get_graph_datum(datum)
- if value > self._max_value:
- self._max_value = value
- if value_len > self._max_value_len:
- self._max_value_len = value_len
-
- graph_data.append(GraphDatum(value, value_str, label))
+ if graph_datum.value > self._max_value:
+ self._max_value = graph_datum.value
+ if len(graph_datum.value_str) > self._max_value_len:
+ self._max_value_len = len(graph_datum.value_str)
+ graph_data.append(graph_datum)
return graph_data
- def _get_graph_header(self):
- if not self._label_header:
- return self._title
+ def _get_value_str(self, value):
+ return self._get_value_str_cb(value)
- title_len = len(self._title)
- space_width = (self.MAX_BAR_WIDTH - title_len) + \
- 1 + self._max_value_len + 1
+ def _get_graph_datum(self, datum):
+ value = self._get_value(datum)
+ value_str = self._get_value_str(value)
- return self._title + ' ' * space_width + self._label_header
+ return GraphDatum(value, value_str)
- def _get_value_str(self, value):
+ def _print_header(self):
+ if self._title:
+ print(self._title)
+
+ def _print_separator(self):
+ print(self.HR_CHAR * self.MAX_GRAPH_WIDTH)
+
+ def _print_body(self):
+ raise NotImplementedError()
+
+ def print_graph(self):
+ if not self._data:
+ return
+
+ self._print_header()
+ self._print_separator()
+ self._print_body()
+ print()
+
+ @staticmethod
+ def _get_value_str_default(value):
if isinstance(value, float):
value_str = '{:0.02f}'.format(value)
else:
value_str = str(value)
+ return value_str
+
+
+class BarGraph(Graph):
+ def __init__(self, data, get_value, get_label, get_value_str=None,
+ title=None, label_header=None, unit=None):
+ super().__init__(data, get_value, get_value_str, title, unit)
+
+ self._get_label = get_label
+ self._label_header = label_header
+ self._data = self._transform_data(self._data)
+
+ def _get_graph_datum(self, datum):
+ value = self._get_value(datum)
+ value_str = self._get_value_str(value)
+ label = self._get_label(datum)
+
+ return BarGraphDatum(value, value_str, label)
+
+ def _get_value_str(self, value):
+ value_str = super()._get_value_str(value)
if self._unit:
value_str += ' ' + self._unit
return value_str
+ def _get_graph_header(self):
+ if not self._label_header:
+ return self._title
+
+ title_len = len(self._title)
+ space_width = (self.MAX_GRAPH_WIDTH - title_len) + \
+ 1 + self._max_value_len + 1
+
+ return self._title + ' ' * space_width + self._label_header
+
+ def _print_header(self):
+ header = self._get_graph_header()
+ print(header)
+
def _get_bar_str(self, datum):
- bar_width = int(self.MAX_BAR_WIDTH * datum.value / self._max_value)
- space_width = self.MAX_BAR_WIDTH - bar_width
+ bar_width = int(self.MAX_GRAPH_WIDTH * datum.value / self._max_value)
+ space_width = self.MAX_GRAPH_WIDTH - bar_width
bar_str = self.BAR_CHAR * bar_width + ' ' * space_width
return bar_str
- def print_graph(self):
- if not self._data:
- return
+ def _print_body(self):
+ for datum in self._data:
+ bar_str = self._get_bar_str(datum)
+ value_padding = ' ' * (self._max_value_len - len(datum.value_str))
+ print(bar_str, value_padding + datum.value_str, datum.label)
- header = self._get_graph_header()
+
+class FreqGraph(Graph):
+ LOWER_BOUND_WIDTH = 8
+
+ def __init__(self, data, get_value, get_lower_bound,
+ get_value_str=None, title=None, unit=None):
+ super().__init__(data, get_value, get_value_str, title, unit)
+
+ self._get_lower_bound = get_lower_bound
+ self._data = self._transform_data(self._data)
+
+ def _get_graph_datum(self, datum):
+ value = self._get_value(datum)
+ value_str = self._get_value_str(value)
+ lower_bound = self._get_lower_bound(datum)
+
+ return FreqGraphDatum(value, value_str, lower_bound)
+
+ def _print_header(self):
+ header = self._title
+ if self._unit:
+ header += ' ({})'.format(self._unit)
print(header)
- print(self.HR_CHAR * self.MAX_BAR_WIDTH)
+
+ def _get_bar_str(self, datum):
+ max_width = self.MAX_GRAPH_WIDTH - self.LOWER_BOUND_WIDTH
+ bar_width = int(max_width * datum.value / self._max_value)
+ space_width = max_width - bar_width
+ bar_str = self.BAR_CHAR * bar_width + ' ' * space_width
+
+ return bar_str
+
+ def _print_body(self):
for datum in self._data:
+ bound_str = FreqGraph._get_bound_str(datum)
bar_str = self._get_bar_str(datum)
value_padding = ' ' * (self._max_value_len - len(datum.value_str))
- print(bar_str, value_padding + datum.value_str, datum.label)
+ print(bound_str, bar_str, value_padding + datum.value_str)
- print()
+ @staticmethod
+ def _get_bound_str(datum):
+ return '{:>7.03f}'.format(datum.lower_bound)