Commit | Line | Data |
---|---|---|
9e5ccbbf PP |
1 | # The MIT License (MIT) |
2 | # | |
3 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> | |
4 | # 2015 - Antoine Busque <abusque@efficios.com> | |
5 | # 2015 - Philippe Proulx <pproulx@efficios.com> | |
6 | # | |
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 | # of this software and associated documentation files (the "Software"), to deal | |
9 | # in the Software without restriction, including without limitation the rights | |
10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | # copies of the Software, and to permit persons to whom the Software is | |
12 | # furnished to do so, subject to the following conditions: | |
13 | # | |
14 | # The above copyright notice and this permission notice shall be included in | |
15 | # all copies or substantial portions of the Software. | |
16 | # | |
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
23 | # SOFTWARE. | |
24 | ||
9e5ccbbf PP |
25 | import operator |
26 | from . import mi | |
0b250a71 AB |
27 | from ..core import cputop |
28 | from .command import Command | |
29 | from ..ascii_graph import Pyasciigraph | |
9e5ccbbf PP |
30 | |
31 | ||
32 | class Cputop(Command): | |
33 | _DESC = """The cputop command.""" | |
34 | _ANALYSIS_CLASS = cputop.Cputop | |
35 | _MI_TITLE = 'Top CPU usage' | |
36 | _MI_DESCRIPTION = 'Per-TID, per-CPU, and total top CPU usage' | |
37 | _MI_TAGS = [mi.Tags.CPU, mi.Tags.TOP] | |
38 | _MI_TABLE_CLASS_PER_PROC = 'per-process' | |
39 | _MI_TABLE_CLASS_PER_CPU = 'per-cpu' | |
40 | _MI_TABLE_CLASS_TOTAL = 'total' | |
41 | _MI_TABLE_CLASS_SUMMARY = 'summary' | |
42 | _MI_TABLE_CLASSES = [ | |
43 | ( | |
44 | _MI_TABLE_CLASS_PER_PROC, | |
45 | 'Per-TID top CPU usage', [ | |
46 | ('process', 'Process', mi.Process), | |
47 | ('migrations', 'Migration count', mi.Integer, 'migrations'), | |
48 | ('usage', 'CPU usage', mi.Ratio), | |
49 | ] | |
50 | ), | |
51 | ( | |
52 | _MI_TABLE_CLASS_PER_CPU, | |
53 | 'Per-CPU top CPU usage', [ | |
54 | ('cpu', 'CPU', mi.Cpu), | |
55 | ('usage', 'CPU usage', mi.Ratio), | |
56 | ]), | |
57 | ( | |
58 | _MI_TABLE_CLASS_TOTAL, | |
59 | 'Total CPU usage', [ | |
60 | ('usage', 'CPU usage', mi.Ratio), | |
61 | ] | |
62 | ), | |
63 | ( | |
64 | _MI_TABLE_CLASS_SUMMARY, | |
65 | 'CPU usage - summary', [ | |
66 | ('time_range', 'Time range', mi.TimeRange), | |
67 | ('usage', 'Total CPU usage', mi.Ratio), | |
68 | ] | |
69 | ), | |
70 | ] | |
71 | ||
72 | def _filter_process(self, proc): | |
73 | # Exclude swapper | |
74 | if proc.tid == 0: | |
75 | return False | |
76 | ||
77 | if self._args.proc_list and proc.comm not in self._args.proc_list: | |
78 | return False | |
79 | ||
80 | return True | |
81 | ||
82 | def _analysis_tick(self, begin_ns, end_ns): | |
83 | per_tid_table = self._get_per_tid_usage_result_table(begin_ns, end_ns) | |
84 | per_cpu_table = self._get_per_cpu_usage_result_table(begin_ns, end_ns) | |
85 | total_table = self._get_total_usage_result_table(begin_ns, end_ns) | |
86 | ||
87 | if self._mi_mode: | |
88 | self._mi_append_result_table(per_tid_table) | |
89 | self._mi_append_result_table(per_cpu_table) | |
90 | self._mi_append_result_table(total_table) | |
91 | else: | |
92 | self._print_date(begin_ns, end_ns) | |
93 | self._print_per_tid_usage(per_tid_table) | |
94 | self._print_per_cpu_usage(per_cpu_table) | |
95 | ||
96 | if total_table: | |
97 | self._print_total_cpu_usage(total_table) | |
98 | ||
99 | def _create_summary_result_tables(self): | |
100 | total_tables = self._mi_get_result_tables(self._MI_TABLE_CLASS_TOTAL) | |
101 | begin = total_tables[0].timerange.begin | |
102 | end = total_tables[-1].timerange.end | |
103 | summary_table = \ | |
104 | self._mi_create_result_table(self._MI_TABLE_CLASS_SUMMARY, | |
105 | begin, end) | |
106 | ||
107 | for total_table in total_tables: | |
108 | usage = total_table.rows[0].usage | |
109 | summary_table.append_row( | |
110 | time_range=total_table.timerange, | |
111 | usage=usage, | |
112 | ) | |
113 | ||
114 | self._mi_clear_result_tables() | |
115 | self._mi_append_result_table(summary_table) | |
116 | ||
117 | def _get_per_tid_usage_result_table(self, begin_ns, end_ns): | |
118 | result_table = \ | |
119 | self._mi_create_result_table(self._MI_TABLE_CLASS_PER_PROC, | |
120 | begin_ns, end_ns) | |
121 | count = 0 | |
122 | ||
123 | for tid in sorted(self._analysis.tids.values(), | |
124 | key=operator.attrgetter('usage_percent'), | |
125 | reverse=True): | |
126 | if not self._filter_process(tid): | |
127 | continue | |
128 | ||
129 | result_table.append_row( | |
130 | process=mi.Process(tid.comm, tid=tid.tid), | |
131 | migrations=mi.Integer(tid.migrate_count), | |
132 | usage=mi.Ratio.from_percentage(tid.usage_percent) | |
133 | ) | |
134 | count += 1 | |
135 | ||
136 | if self._args.limit > 0 and count >= self._args.limit: | |
137 | break | |
138 | ||
139 | return result_table | |
140 | ||
141 | def _get_per_cpu_usage_result_table(self, begin_ns, end_ns): | |
142 | result_table = \ | |
143 | self._mi_create_result_table(self._MI_TABLE_CLASS_PER_CPU, | |
144 | begin_ns, end_ns) | |
145 | ||
146 | for cpu in sorted(self._analysis.cpus.values(), | |
147 | key=operator.attrgetter('usage_percent'), | |
148 | reverse=True): | |
149 | result_table.append_row( | |
150 | cpu=mi.Cpu(cpu.cpu_id), | |
151 | usage=mi.Ratio.from_percentage(cpu.usage_percent) | |
152 | ) | |
153 | ||
154 | return result_table | |
155 | ||
156 | def _get_total_usage_result_table(self, begin_ns, end_ns): | |
157 | result_table = \ | |
158 | self._mi_create_result_table(self._MI_TABLE_CLASS_TOTAL, | |
159 | begin_ns, end_ns) | |
160 | ||
161 | cpu_count = len(self.state.cpus) | |
162 | usage_percent = 0 | |
163 | ||
164 | if not cpu_count: | |
165 | return | |
166 | ||
167 | for cpu in sorted(self._analysis.cpus.values(), | |
168 | key=operator.attrgetter('usage_percent'), | |
169 | reverse=True): | |
170 | usage_percent += cpu.usage_percent | |
171 | ||
172 | # average per CPU | |
173 | usage_percent /= cpu_count | |
174 | result_table.append_row( | |
175 | usage=mi.Ratio.from_percentage(usage_percent), | |
176 | ) | |
177 | ||
178 | return result_table | |
179 | ||
180 | def _print_per_tid_usage(self, result_table): | |
181 | graph = Pyasciigraph() | |
182 | values = [] | |
183 | ||
184 | for row in result_table.rows: | |
185 | process_do = row.process | |
186 | migration_count = row.migrations.value | |
187 | output_str = '%s (%d)' % (process_do.name, process_do.tid) | |
188 | ||
189 | if migration_count > 0: | |
190 | output_str += ', %d migrations' % (migration_count) | |
191 | ||
192 | values.append((output_str, row.usage.to_percentage())) | |
193 | ||
194 | for line in graph.graph('Per-TID CPU Usage', values, unit=' %'): | |
195 | print(line) | |
196 | ||
197 | def _print_per_cpu_usage(self, result_table): | |
198 | graph = Pyasciigraph() | |
199 | values = [] | |
200 | ||
201 | for row in result_table.rows: | |
202 | cpu = row.cpu | |
203 | values.append(('CPU %d' % cpu.id, row.usage.to_percentage())) | |
204 | ||
205 | for line in graph.graph('Per-CPU Usage', values, unit=' %'): | |
206 | print(line) | |
207 | ||
208 | def _print_total_cpu_usage(self, result_table): | |
209 | usage_percent = result_table.rows[0].usage.to_percentage() | |
210 | print('\nTotal CPU Usage: %0.02f%%\n' % usage_percent) | |
211 | ||
212 | def _add_arguments(self, ap): | |
213 | Command._add_proc_filter_args(ap) | |
214 | ||
215 | ||
216 | def _run(mi_mode): | |
217 | cputopcmd = Cputop(mi_mode=mi_mode) | |
218 | cputopcmd.run() | |
219 | ||
220 | ||
221 | # entry point (human) | |
222 | def run(): | |
223 | _run(mi_mode=False) | |
224 | ||
225 | ||
226 | # entry point (MI) | |
227 | def run_mi(): | |
228 | _run(mi_mode=True) |