Fix: handle max values of 0 in termgraph
[deliverable/lttng-analyses.git] / lttnganalyses / cli / termgraph.py
1 # The MIT License (MIT)
2 #
3 # Copyright (C) 2016 - Antoine Busque <abusque@efficios.com>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 # SOFTWARE.
22
23 from collections import namedtuple
24
25
26 GraphDatum = namedtuple('GraphDatum', ['value', 'value_str'])
27 BarGraphDatum = namedtuple('BarGraphDatum', ['value', 'value_str', 'label'])
28 FreqGraphDatum = namedtuple(
29 'FreqGraphDatum', ['value', 'value_str', 'lower_bound']
30 )
31
32 class Graph():
33 MAX_GRAPH_WIDTH = 80
34 BAR_CHAR = '█'
35 HR_CHAR = '#'
36
37 def __init__(self, data, get_value, get_value_str, title, unit):
38 self._data = data
39 self._get_value = get_value
40 self._title = title
41 self._unit = unit
42 self._max_value = 0
43 self._max_value_len = 0
44
45 if get_value_str is not None:
46 self._get_value_str_cb = get_value_str
47 else:
48 self._get_value_str_cb = Graph._get_value_str_default
49
50 def _transform_data(self, data):
51 graph_data = []
52
53 for datum in data:
54 graph_datum = self._get_graph_datum(datum)
55
56 if graph_datum.value > self._max_value:
57 self._max_value = graph_datum.value
58 if len(graph_datum.value_str) > self._max_value_len:
59 self._max_value_len = len(graph_datum.value_str)
60
61 graph_data.append(graph_datum)
62
63 return graph_data
64
65 def _get_value_str(self, value):
66 return self._get_value_str_cb(value)
67
68 def _get_graph_datum(self, datum):
69 value = self._get_value(datum)
70 value_str = self._get_value_str(value)
71
72 return GraphDatum(value, value_str)
73
74 def _print_header(self):
75 if self._title:
76 print(self._title)
77
78 def _print_separator(self):
79 print(self.HR_CHAR * self.MAX_GRAPH_WIDTH)
80
81 def _print_body(self):
82 raise NotImplementedError()
83
84 def print_graph(self):
85 if not self._data:
86 return
87
88 self._print_header()
89 self._print_separator()
90 self._print_body()
91 print()
92
93 @staticmethod
94 def _get_value_str_default(value):
95 if isinstance(value, float):
96 value_str = '{:0.02f}'.format(value)
97 else:
98 value_str = str(value)
99
100 return value_str
101
102
103 class BarGraph(Graph):
104 def __init__(self, data, get_value, get_label, get_value_str=None,
105 title=None, label_header=None, unit=None):
106 super().__init__(data, get_value, get_value_str, title, unit)
107
108 self._get_label = get_label
109 self._label_header = label_header
110 self._data = self._transform_data(self._data)
111
112 def _get_graph_datum(self, datum):
113 value = self._get_value(datum)
114 value_str = self._get_value_str(value)
115 label = self._get_label(datum)
116
117 return BarGraphDatum(value, value_str, label)
118
119 def _get_value_str(self, value):
120 value_str = super()._get_value_str(value)
121 if self._unit:
122 value_str += ' ' + self._unit
123
124 return value_str
125
126 def _get_graph_header(self):
127 if not self._label_header:
128 return self._title
129
130 title_len = len(self._title)
131 space_width = (self.MAX_GRAPH_WIDTH - title_len) + \
132 1 + self._max_value_len + 1
133
134 return self._title + ' ' * space_width + self._label_header
135
136 def _print_header(self):
137 header = self._get_graph_header()
138 print(header)
139
140 def _get_bar_str(self, datum):
141 if self._max_value == 0:
142 bar_width = 0
143 else:
144 bar_width = int(self.MAX_GRAPH_WIDTH * datum.value /
145 self._max_value)
146 space_width = self.MAX_GRAPH_WIDTH - bar_width
147 bar_str = self.BAR_CHAR * bar_width + ' ' * space_width
148
149 return bar_str
150
151 def _print_body(self):
152 for datum in self._data:
153 bar_str = self._get_bar_str(datum)
154 value_padding = ' ' * (self._max_value_len - len(datum.value_str))
155 print(bar_str, value_padding + datum.value_str, datum.label)
156
157
158 class FreqGraph(Graph):
159 LOWER_BOUND_WIDTH = 8
160
161 def __init__(self, data, get_value, get_lower_bound,
162 get_value_str=None, title=None, unit=None):
163 super().__init__(data, get_value, get_value_str, title, unit)
164
165 self._get_lower_bound = get_lower_bound
166 self._data = self._transform_data(self._data)
167
168 def _get_graph_datum(self, datum):
169 value = self._get_value(datum)
170 value_str = self._get_value_str(value)
171 lower_bound = self._get_lower_bound(datum)
172
173 return FreqGraphDatum(value, value_str, lower_bound)
174
175 def _print_header(self):
176 header = self._title
177 if self._unit:
178 header += ' ({})'.format(self._unit)
179
180 print(header)
181
182 def _get_bar_str(self, datum):
183 max_width = self.MAX_GRAPH_WIDTH - self.LOWER_BOUND_WIDTH
184 if self._max_value == 0:
185 bar_width = 0
186 else:
187 bar_width = int(max_width * datum.value / self._max_value)
188 space_width = max_width - bar_width
189 bar_str = self.BAR_CHAR * bar_width + ' ' * space_width
190
191 return bar_str
192
193 def _print_body(self):
194 for datum in self._data:
195 bound_str = FreqGraph._get_bound_str(datum)
196 bar_str = self._get_bar_str(datum)
197 value_padding = ' ' * (self._max_value_len - len(datum.value_str))
198 print(bound_str, bar_str, value_padding + datum.value_str)
199
200 @staticmethod
201 def _get_bound_str(datum):
202 return '{:>7.03f}'.format(datum.lower_bound)
This page took 0.038651 seconds and 5 git commands to generate.