Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | # The MIT License (MIT) |
2 | # | |
a3fa57c0 | 3 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
b68fb35b | 4 | # 2015 - Antoine Busque <abusque@efficios.com> |
cee855a2 | 5 | # 2015 - Philippe Proulx <pproulx@efficios.com> |
4ed24f86 JD |
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 | ||
a04c353a | 25 | from .command import Command |
56936af2 | 26 | from ..core import syscalls |
a0acc08c | 27 | from . import mi |
a04c353a | 28 | import operator |
21167679 JD |
29 | import statistics |
30 | import errno | |
a04c353a JD |
31 | |
32 | ||
33 | class SyscallsAnalysis(Command): | |
b68fb35b | 34 | _DESC = """The syscallstats command.""" |
b6d9132b | 35 | _ANALYSIS_CLASS = syscalls.SyscallsAnalysis |
a0acc08c PP |
36 | _MI_TITLE = 'System call statistics' |
37 | _MI_DESCRIPTION = 'Per-TID and global system call statistics' | |
38 | _MI_TAGS = [mi.Tags.SYSCALL, mi.Tags.STATS] | |
39 | _MI_TABLE_CLASS_PER_TID_STATS = 'per-tid' | |
40 | _MI_TABLE_CLASS_TOTAL = 'total' | |
41 | _MI_TABLE_CLASS_SUMMARY = 'summary' | |
42 | _MI_TABLE_CLASSES = [ | |
43 | ( | |
44 | _MI_TABLE_CLASS_PER_TID_STATS, | |
45 | 'System call statistics', [ | |
46 | ('syscall', 'System call', mi.Syscall), | |
47 | ('count', 'Call count', mi.Integer, 'calls'), | |
48 | ('min_duration', 'Minimum call duration', mi.Duration), | |
49 | ('avg_duration', 'Average call duration', mi.Duration), | |
50 | ('max_duration', 'Maximum call duration', mi.Duration), | |
b9f05f8d AB |
51 | ('stdev_duration', 'Call duration standard deviation', |
52 | mi.Duration), | |
a0acc08c PP |
53 | ('return_values', 'Return values count', mi.String), |
54 | ] | |
55 | ), | |
56 | ( | |
57 | _MI_TABLE_CLASS_TOTAL, | |
58 | 'Per-TID system call statistics', [ | |
59 | ('process', 'Process', mi.Process), | |
60 | ('count', 'Total system call count', mi.Integer, 'calls'), | |
61 | ] | |
62 | ), | |
63 | ( | |
64 | _MI_TABLE_CLASS_SUMMARY, | |
65 | 'System call statistics - summary', [ | |
66 | ('time_range', 'Time range', mi.TimeRange), | |
67 | ('process', 'Process', mi.Process), | |
68 | ('count', 'Total system call count', mi.Integer, 'calls'), | |
69 | ] | |
70 | ), | |
71 | ] | |
72 | ||
73 | def _analysis_tick(self, begin_ns, end_ns): | |
74 | total_table, per_tid_tables = self._get_result_tables(begin_ns, end_ns) | |
75 | ||
76 | if self._mi_mode: | |
77 | self._mi_append_result_tables(per_tid_tables) | |
78 | self._mi_append_result_table(total_table) | |
79 | else: | |
80 | self._print_date(begin_ns, end_ns) | |
81 | self._print_results(total_table, per_tid_tables) | |
82 | ||
83 | def _post_analysis(self): | |
84 | if not self._mi_mode: | |
85 | return | |
86 | ||
87 | if len(self._mi_get_result_tables(self._MI_TABLE_CLASS_TOTAL)) > 1: | |
88 | self._create_summary_result_table() | |
89 | ||
90 | self._mi_print() | |
91 | ||
92 | def _create_summary_result_table(self): | |
93 | total_tables = self._mi_get_result_tables(self._MI_TABLE_CLASS_TOTAL) | |
94 | begin = total_tables[0].timerange.begin | |
95 | end = total_tables[-1].timerange.end | |
96 | summary_table = \ | |
97 | self._mi_create_result_table(self._MI_TABLE_CLASS_SUMMARY, | |
98 | begin, end) | |
99 | ||
100 | for total_table in total_tables: | |
101 | for row in total_table.rows: | |
102 | process = row.process | |
103 | count = row.count | |
104 | summary_table.append_row( | |
105 | time_range=total_table.timerange, | |
106 | process=process, | |
107 | count=count, | |
108 | ) | |
109 | ||
110 | self._mi_clear_result_tables() | |
111 | self._mi_append_result_table(summary_table) | |
112 | ||
113 | def _get_result_tables(self, begin_ns, end_ns): | |
114 | per_tid_tables = [] | |
115 | total_table = self._mi_create_result_table(self._MI_TABLE_CLASS_TOTAL, | |
116 | begin_ns, end_ns) | |
100a92b2 AB |
117 | |
118 | for proc_stats in sorted(self._analysis.tids.values(), | |
119 | key=operator.attrgetter('total_syscalls'), | |
120 | reverse=True): | |
121 | if not self._filter_process(proc_stats) or \ | |
122 | proc_stats.total_syscalls == 0: | |
21167679 | 123 | continue |
ab094171 | 124 | |
100a92b2 | 125 | pid = proc_stats.pid |
a0acc08c | 126 | |
100a92b2 | 127 | if proc_stats.pid is None: |
ab094171 | 128 | pid = '?' |
ab094171 | 129 | |
a0acc08c PP |
130 | subtitle = '%s (%s, TID: %d)' % (proc_stats.comm, pid, |
131 | proc_stats.tid) | |
132 | result_table = \ | |
b9f05f8d AB |
133 | self._mi_create_result_table( |
134 | self._MI_TABLE_CLASS_PER_TID_STATS, begin_ns, end_ns, | |
135 | subtitle) | |
ab094171 | 136 | |
100a92b2 | 137 | for syscall in sorted(proc_stats.syscalls.values(), |
a04c353a JD |
138 | key=operator.attrgetter('count'), |
139 | reverse=True): | |
100a92b2 AB |
140 | durations = [] |
141 | return_count = {} | |
142 | ||
143 | for syscall_event in syscall.syscalls_list: | |
144 | durations.append(syscall_event.duration) | |
145 | ||
146 | if syscall_event.ret >= 0: | |
147 | return_key = 'success' | |
21167679 | 148 | else: |
debd33f3 | 149 | try: |
100a92b2 AB |
150 | return_key = errno.errorcode[-syscall_event.ret] |
151 | except KeyError: | |
152 | return_key = str(syscall_event.ret) | |
153 | ||
154 | if return_key not in return_count: | |
155 | return_count[return_key] = 1 | |
156 | ||
157 | return_count[return_key] += 1 | |
158 | ||
100a92b2 | 159 | if len(durations) > 2: |
a0acc08c | 160 | stdev = mi.Duration(statistics.stdev(durations)) |
21167679 | 161 | else: |
a0acc08c PP |
162 | stdev = mi.Unknown() |
163 | ||
164 | result_table.append_row( | |
165 | syscall=mi.Syscall(syscall.name), | |
166 | count=mi.Integer(syscall.count), | |
167 | min_duration=mi.Duration(syscall.min_duration), | |
b9f05f8d AB |
168 | avg_duration=mi.Duration(syscall.total_duration / |
169 | syscall.count), | |
a0acc08c PP |
170 | max_duration=mi.Duration(syscall.max_duration), |
171 | stdev_duration=stdev, | |
172 | return_values=mi.String(str(return_count)), | |
173 | ) | |
174 | ||
175 | per_tid_tables.append(result_table) | |
176 | total_table.append_row( | |
177 | process=mi.Process(proc_stats.comm, pid=proc_stats.pid, | |
178 | tid=proc_stats.tid), | |
179 | count=mi.Integer(proc_stats.total_syscalls), | |
180 | ) | |
181 | ||
182 | return total_table, per_tid_tables | |
183 | ||
184 | def _print_results(self, total_table, per_tid_tables): | |
185 | line_format = '{:<38} {:>14} {:>14} {:>14} {:>12} {:>10} {:<14}' | |
186 | ||
187 | print('Per-TID syscalls statistics (usec)') | |
188 | total_calls = 0 | |
189 | ||
190 | for total_row, table in zip(total_table.rows, per_tid_tables): | |
191 | print(line_format.format(table.subtitle, | |
192 | 'Count', 'Min', 'Average', 'Max', | |
193 | 'Stdev', 'Return values')) | |
194 | for row in table.rows: | |
195 | syscall_name = row.syscall.name | |
196 | syscall_count = row.count.value | |
197 | min_duration = round(row.min_duration.to_us(), 3) | |
198 | avg_duration = round(row.avg_duration.to_us(), 3) | |
199 | max_duration = round(row.max_duration.to_us(), 3) | |
200 | ||
201 | if type(row.stdev_duration) is mi.Unknown: | |
73b71522 | 202 | stdev = '?' |
a0acc08c PP |
203 | else: |
204 | stdev = round(row.stdev_duration.to_us(), 3) | |
100a92b2 | 205 | |
a0acc08c | 206 | proc_total_calls = total_row.count.value |
100a92b2 | 207 | print(line_format.format( |
a0acc08c PP |
208 | ' - ' + syscall_name, syscall_count, min_duration, |
209 | avg_duration, max_duration, stdev, | |
210 | row.return_values.value)) | |
100a92b2 | 211 | |
a0acc08c | 212 | print(line_format.format('Total:', proc_total_calls, |
100a92b2 | 213 | '', '', '', '', '')) |
73b71522 | 214 | print('-' * 113) |
a0acc08c | 215 | total_calls += proc_total_calls |
a04c353a | 216 | |
a0acc08c | 217 | print('\nTotal syscalls: %d' % (total_calls)) |
a04c353a | 218 | |
a04c353a | 219 | def _add_arguments(self, ap): |
b6d9132b | 220 | Command._add_proc_filter_args(ap) |
a04c353a JD |
221 | |
222 | ||
a0acc08c PP |
223 | def _run(mi_mode): |
224 | syscallscmd = SyscallsAnalysis(mi_mode=mi_mode) | |
a04c353a | 225 | syscallscmd.run() |
a0acc08c PP |
226 | |
227 | ||
228 | # entry point (human) | |
229 | def run(): | |
230 | _run(mi_mode=False) | |
231 | ||
232 | ||
233 | # entry point (MI) | |
234 | def run_mi(): | |
235 | _run(mi_mode=True) |