Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | #!/usr/bin/env python3 |
2 | # | |
3 | # The MIT License (MIT) | |
4 | # | |
a3fa57c0 | 5 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
6cd52af3 | 6 | # 2015 - Antoine Busque <abusque@efficios.com> |
4ed24f86 JD |
7 | # |
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
9 | # of this software and associated documentation files (the "Software"), to deal | |
10 | # in the Software without restriction, including without limitation the rights | |
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
12 | # copies of the Software, and to permit persons to whom the Software is | |
13 | # furnished to do so, subject to the following conditions: | |
14 | # | |
15 | # The above copyright notice and this permission notice shall be included in | |
16 | # all copies or substantial portions of the Software. | |
17 | # | |
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
24 | # SOFTWARE. | |
25 | ||
a9c9a2a6 | 26 | from .command import Command |
6cd52af3 | 27 | import lttnganalyses.io |
a9c9a2a6 JD |
28 | from linuxautomaton import common, sv |
29 | from ascii_graph import Pyasciigraph | |
30 | import operator | |
31 | import statistics | |
32 | ||
33 | ||
087e5d00 | 34 | class IoAnalysisCommand(Command): |
a9c9a2a6 JD |
35 | _VERSION = '0.1.0' |
36 | _DESC = """The I/O command.""" | |
37 | ||
ef49c8de AB |
38 | _LATENCY_STATS_FORMAT = '{:<14} {:>14} {:>14} {:>14} {:>14} {:>14}' |
39 | _SECTION_SEPARATOR_STRING = '-' * 89 | |
40 | ||
a9c9a2a6 JD |
41 | def __init__(self): |
42 | super().__init__(self._add_arguments, | |
43 | enable_proc_filter_args=True, | |
44 | enable_max_min_args=True, | |
45 | enable_max_min_size_arg=True, | |
1c0f0e3c | 46 | enable_log_arg=True) |
a9c9a2a6 JD |
47 | |
48 | def _validate_transform_args(self): | |
1c0f0e3c JD |
49 | self._arg_usage = self._args.usage |
50 | self._arg_stats = self._args.latencystats | |
275bdbb4 | 51 | self._arg_latencytop = self._args.latencytop |
1c0f0e3c JD |
52 | self._arg_freq = self._args.latencyfreq |
53 | self._arg_freq_resolution = self._args.freq_resolution | |
54 | ||
275bdbb4 | 55 | def _default_args(self, stats, log, freq, usage, latencytop): |
1c0f0e3c JD |
56 | if stats: |
57 | self._arg_stats = True | |
58 | if log: | |
59 | self._arg_log = True | |
60 | if freq: | |
61 | self._arg_freq = True | |
62 | if usage: | |
63 | self._arg_usage = True | |
275bdbb4 JD |
64 | if latencytop: |
65 | self._arg_latencytop = True | |
1c0f0e3c | 66 | |
275bdbb4 JD |
67 | def run(self, stats=False, log=False, freq=False, usage=False, |
68 | latencytop=False): | |
a9c9a2a6 JD |
69 | # parse arguments first |
70 | self._parse_args() | |
71 | # validate, transform and save specific arguments | |
72 | self._validate_transform_args() | |
1c0f0e3c | 73 | # handle the default args for different executables |
275bdbb4 | 74 | self._default_args(stats, log, freq, usage, latencytop) |
a9c9a2a6 JD |
75 | # open the trace |
76 | self._open_trace() | |
77 | # create the appropriate analysis/analyses | |
78 | self._create_analysis() | |
79 | # run the analysis | |
7a68fd89 | 80 | self._run_analysis(self._reset_total, self._refresh) |
a9c9a2a6 | 81 | # print results |
da6d0842 | 82 | self._print_results(self.start_ns, self.trace_end_ts) |
a9c9a2a6 JD |
83 | # close the trace |
84 | self._close_trace() | |
85 | ||
1c0f0e3c JD |
86 | def run_stats(self): |
87 | self.run(stats=True) | |
88 | ||
275bdbb4 JD |
89 | def run_latencytop(self): |
90 | self.run(latencytop=True) | |
91 | ||
1c0f0e3c JD |
92 | def run_log(self): |
93 | self.run(log=True) | |
94 | ||
95 | def run_freq(self): | |
96 | self.run(freq=True) | |
97 | ||
98 | def run_usage(self): | |
99 | self.run(usage=True) | |
100 | ||
a9c9a2a6 | 101 | def _create_analysis(self): |
6cd52af3 | 102 | self._analysis = lttnganalyses.io.IoAnalysis(self.state) |
a9c9a2a6 | 103 | |
a9c9a2a6 | 104 | def _refresh(self, begin, end): |
da6d0842 | 105 | self._print_results(begin, end) |
a9c9a2a6 JD |
106 | self._reset_total(end) |
107 | ||
882fcd19 | 108 | # Filter predicates |
882fcd19 AB |
109 | def _filter_size(self, size): |
110 | if size is None: | |
111 | return True | |
112 | if self._arg_maxsize is not None and size > self._arg_maxsize: | |
113 | return False | |
114 | if self._arg_minsize is not None and size < self._arg_minsize: | |
115 | return False | |
116 | return True | |
117 | ||
118 | def _filter_latency(self, duration): | |
119 | if self._arg_max is not None and (duration/1000) > self._arg_max: | |
120 | return False | |
121 | if self._arg_min is not None and (duration/1000) < self._arg_min: | |
122 | return False | |
123 | return True | |
124 | ||
ef49c8de | 125 | def _filter_time_range(self, begin, end): |
acf400da | 126 | return not (self._arg_begin and self._arg_end and end and |
ef49c8de AB |
127 | begin > self._arg_end) |
128 | ||
129 | def _filter_io_request(self, io_rq): | |
130 | proc = self._analysis.tids[io_rq.tid] | |
131 | return self._filter_process(proc) and \ | |
132 | self._filter_size(io_rq.size) and \ | |
133 | self._filter_latency(io_rq.duration) and \ | |
134 | self._filter_time_range(io_rq.begin_ts, io_rq.end_ts) | |
135 | ||
88cfa546 AB |
136 | def _print_ascii_graph(self, input_list, get_datum_cb, graph_label, |
137 | graph_args={}): | |
138 | """Print an ascii graph for given data | |
139 | ||
140 | This method wraps the ascii_graph module and facilitates the | |
141 | printing of a graph with a limited number of lines. | |
142 | ||
143 | Args: | |
144 | input_list (list): A list of objects from which the data | |
145 | for the graph will be generated. | |
146 | ||
147 | get_datum_cb (function): function that takes a single | |
148 | object from the input list as an argument, and returns a | |
149 | datum tuple for the graph, of the form (string, int). The | |
150 | string element is printed as is in the graph, and the int | |
151 | is the numeric value corresponding to this graph entry. | |
152 | ||
153 | graph_label (string): Label used to identify the printed | |
154 | graph. | |
155 | ||
156 | graph_args (dict, optional): Dict of keyword args to be | |
157 | passed to the graph() function as is. | |
158 | """ | |
159 | count = 0 | |
160 | limit = self._arg_limit | |
161 | graph = Pyasciigraph() | |
162 | data = [] | |
163 | ||
164 | for elem in input_list: | |
165 | datum = get_datum_cb(elem) | |
166 | if datum is not None: | |
167 | data.append(datum) | |
168 | count += 1 | |
169 | if limit is not None and count >= limit: | |
170 | break | |
171 | ||
172 | for line in graph.graph(graph_label, data, **graph_args): | |
173 | print(line) | |
174 | ||
91765fd2 AB |
175 | # I/O Top output methods |
176 | def _get_read_datum(self, proc_stats): | |
177 | if not self._filter_process(proc_stats): | |
178 | return None | |
179 | ||
180 | if proc_stats.pid is None: | |
181 | pid_str = 'unknown (tid=%d)' % (proc_stats.tid) | |
182 | else: | |
183 | pid_str = str(proc_stats.pid) | |
184 | ||
185 | format_str = '{:>10} {:<25} {:>9} file {:>9} net {:>9} unknown' | |
186 | output_str = format_str.format( | |
187 | common.convert_size(proc_stats.total_read, padding_after=True), | |
188 | '%s (%s)' % (proc_stats.comm, pid_str), | |
189 | common.convert_size(proc_stats.disk_read, padding_after=True), | |
190 | common.convert_size(proc_stats.net_read, padding_after=True), | |
191 | common.convert_size(proc_stats.unk_read, padding_after=True)) | |
192 | ||
193 | return (output_str, proc_stats.total_read) | |
194 | ||
195 | def _get_write_datum(self, proc_stats): | |
196 | if not self._filter_process(proc_stats): | |
197 | return None | |
198 | ||
199 | if proc_stats.pid is None: | |
200 | pid_str = 'unknown (tid=%d)' % (proc_stats.tid) | |
201 | else: | |
202 | pid_str = str(proc_stats.pid) | |
203 | ||
204 | format_str = '{:>10} {:<25} {:>9} file {:>9} net {:>9} unknown' | |
205 | output_str = format_str.format( | |
206 | common.convert_size(proc_stats.total_write, padding_after=True), | |
207 | '%s (%s)' % (proc_stats.comm, pid_str), | |
208 | common.convert_size(proc_stats.disk_write, padding_after=True), | |
209 | common.convert_size(proc_stats.net_write, padding_after=True), | |
210 | common.convert_size(proc_stats.unk_write, padding_after=True)) | |
211 | ||
212 | return (output_str, proc_stats.total_write) | |
213 | ||
214 | def _get_block_read_datum(self, proc_stats): | |
215 | if not self._filter_process(proc_stats) or proc_stats.block_read == 0: | |
216 | return None | |
217 | ||
218 | comm = proc_stats.comm | |
219 | if not comm: | |
220 | comm = 'unknown' | |
221 | ||
222 | if proc_stats.pid is None: | |
223 | pid_str = 'unknown (tid=%d)' % (proc_stats.tid) | |
224 | else: | |
225 | pid_str = str(proc_stats.pid) | |
226 | ||
227 | format_str = '{:>10} {:<22}' | |
228 | output_str = format_str.format( | |
229 | common.convert_size(proc_stats.block_read, padding_after=True), | |
230 | '%s (pid=%s)' % (comm, pid_str)) | |
231 | ||
232 | return (output_str, proc_stats.block_read) | |
233 | ||
234 | def _get_block_write_datum(self, proc_stats): | |
235 | if not self._filter_process(proc_stats) or \ | |
236 | proc_stats.block_write == 0: | |
237 | return None | |
238 | ||
239 | comm = proc_stats.comm | |
240 | if not comm: | |
241 | comm = 'unknown' | |
242 | ||
243 | if proc_stats.pid is None: | |
244 | pid_str = 'unknown (tid=%d)' % (proc_stats.tid) | |
245 | else: | |
246 | pid_str = str(proc_stats.pid) | |
247 | ||
248 | format_str = '{:>10} {:<22}' | |
249 | output_str = format_str.format( | |
250 | common.convert_size(proc_stats.block_write, padding_after=True), | |
251 | '%s (pid=%s)' % (comm, pid_str)) | |
252 | ||
253 | return (output_str, proc_stats.block_write) | |
254 | ||
255 | def _get_total_rq_sectors_datum(self, disk): | |
256 | if disk.total_rq_sectors == 0: | |
257 | return None | |
258 | ||
259 | return (disk.disk_name, disk.total_rq_sectors) | |
260 | ||
261 | def _get_rq_count_datum(self, disk): | |
262 | if disk.rq_count == 0: | |
263 | return None | |
264 | ||
265 | return (disk.disk_name, disk.rq_count) | |
266 | ||
267 | def _get_avg_disk_latency_datum(self, disk): | |
268 | if disk.rq_count == 0: | |
269 | return None | |
270 | ||
271 | avg_latency = ((disk.total_rq_duration / disk.rq_count) / | |
272 | common.MSEC_PER_NSEC) | |
273 | avg_latency = round(avg_latency, 3) | |
274 | ||
275 | return ('%s' % disk.disk_name, avg_latency) | |
276 | ||
277 | def _get_net_recv_bytes_datum(self, iface): | |
278 | return ('%s %s' % (common.convert_size(iface.recv_bytes), iface.name), | |
acf400da | 279 | iface.recv_bytes) |
91765fd2 AB |
280 | |
281 | def _get_net_sent_bytes_datum(self, iface): | |
282 | return ('%s %s' % (common.convert_size(iface.sent_bytes), iface.name), | |
acf400da | 283 | iface.sent_bytes) |
91765fd2 | 284 | |
e0e5f1fd AB |
285 | def _get_file_read_datum(self, file_stats): |
286 | if file_stats.read == 0: | |
287 | return None | |
288 | ||
289 | fd_by_pid_str = '' | |
290 | for pid, fd in file_stats.fd_by_pid.items(): | |
291 | comm = self._analysis.tids[pid].comm | |
292 | fd_by_pid_str += 'fd %d in %s (%s) ' % (fd, comm, pid) | |
293 | ||
294 | format_str = '{:>10} {} {}' | |
295 | output_str = format_str.format( | |
296 | common.convert_size(file_stats.read, padding_after=True), | |
297 | file_stats.filename, | |
298 | fd_by_pid_str) | |
299 | ||
300 | return (output_str, file_stats.read) | |
301 | ||
302 | def _get_file_write_datum(self, file_stats): | |
303 | if file_stats.write == 0: | |
304 | return None | |
305 | ||
306 | fd_by_pid_str = '' | |
307 | for pid, fd in file_stats.fd_by_pid.items(): | |
308 | comm = self._analysis.tids[pid].comm | |
309 | fd_by_pid_str += 'fd %d in %s (%s) ' % (fd, comm, pid) | |
310 | ||
311 | format_str = '{:>10} {} {}' | |
312 | output_str = format_str.format( | |
313 | common.convert_size(file_stats.write, padding_after=True), | |
314 | file_stats.filename, | |
315 | fd_by_pid_str) | |
316 | ||
317 | return (output_str, file_stats.write) | |
318 | ||
91765fd2 AB |
319 | def _output_read(self): |
320 | input_list = sorted(self._analysis.tids.values(), | |
321 | key=operator.attrgetter('total_read'), | |
322 | reverse=True) | |
323 | label = 'Per-process I/O Read' | |
324 | graph_args = {'with_value': False} | |
325 | self._print_ascii_graph(input_list, self._get_read_datum, label, | |
326 | graph_args) | |
327 | ||
328 | def _output_write(self): | |
329 | input_list = sorted(self._analysis.tids.values(), | |
330 | key=operator.attrgetter('total_write'), | |
331 | reverse=True) | |
332 | label = 'Per-process I/O Write' | |
333 | graph_args = {'with_value': False} | |
334 | self._print_ascii_graph(input_list, self._get_write_datum, label, | |
335 | graph_args) | |
336 | ||
337 | def _output_block_read(self): | |
338 | input_list = sorted(self._analysis.tids.values(), | |
339 | key=operator.attrgetter('block_read'), | |
340 | reverse=True) | |
341 | label = 'Block I/O Read' | |
342 | graph_args = {'with_value': False} | |
343 | self._print_ascii_graph(input_list, self._get_block_read_datum, | |
344 | label, graph_args) | |
345 | ||
346 | def _output_block_write(self): | |
347 | input_list = sorted(self._analysis.tids.values(), | |
348 | key=operator.attrgetter('block_write'), | |
349 | reverse=True) | |
350 | label = 'Block I/O Write' | |
351 | graph_args = {'with_value': False} | |
352 | self._print_ascii_graph(input_list, self._get_block_write_datum, | |
353 | label, graph_args) | |
354 | ||
355 | def _output_total_rq_sectors(self): | |
356 | input_list = sorted(self._analysis.disks.values(), | |
357 | key=operator.attrgetter('total_rq_sectors'), | |
358 | reverse=True) | |
359 | label = 'Disk requests sector count' | |
360 | graph_args = {'unit': ' sectors'} | |
361 | self._print_ascii_graph(input_list, self._get_total_rq_sectors_datum, | |
362 | label, graph_args) | |
363 | ||
364 | def _output_rq_count(self): | |
365 | input_list = sorted(self._analysis.disks.values(), | |
366 | key=operator.attrgetter('rq_count'), | |
367 | reverse=True) | |
368 | label = 'Disk request count' | |
369 | graph_args = {'unit': ' requests'} | |
370 | self._print_ascii_graph(input_list, self._get_rq_count_datum, | |
371 | label, graph_args) | |
372 | ||
373 | def _output_avg_disk_latency(self): | |
374 | input_list = self._analysis.disks.values() | |
375 | label = 'Disk request average latency' | |
376 | graph_args = {'unit': ' ms', 'sort': 2} | |
377 | self._print_ascii_graph(input_list, self._get_avg_disk_latency_datum, | |
378 | label, graph_args) | |
379 | ||
380 | def _output_net_recv_bytes(self): | |
381 | input_list = sorted(self._analysis.ifaces.values(), | |
382 | key=operator.attrgetter('recv_bytes'), | |
383 | reverse=True) | |
384 | label = 'Network received bytes' | |
385 | graph_args = {'with_value': False} | |
386 | self._print_ascii_graph(input_list, self._get_net_recv_bytes_datum, | |
387 | label, graph_args) | |
388 | ||
389 | def _output_net_sent_bytes(self): | |
390 | input_list = sorted(self._analysis.ifaces.values(), | |
391 | key=operator.attrgetter('sent_bytes'), | |
392 | reverse=True) | |
393 | label = 'Network sent bytes' | |
394 | graph_args = {'with_value': False} | |
395 | self._print_ascii_graph(input_list, self._get_net_sent_bytes_datum, | |
396 | label, graph_args) | |
397 | ||
e0e5f1fd AB |
398 | def _output_file_read(self, files): |
399 | input_list = sorted(files.values(), | |
400 | key=lambda file_stats: file_stats.read, | |
401 | reverse=True) | |
402 | label = 'Files read' | |
403 | graph_args = {'with_value': False, 'sort': 2} | |
404 | self._print_ascii_graph(input_list, self._get_file_read_datum, | |
405 | label, graph_args) | |
a9c9a2a6 | 406 | |
e0e5f1fd AB |
407 | def _output_file_write(self, files): |
408 | input_list = sorted(files.values(), | |
409 | key=lambda file_stats: file_stats.write, | |
410 | reverse=True) | |
411 | label = 'Files write' | |
412 | graph_args = {'with_value': False, 'sort': 2} | |
413 | self._print_ascii_graph(input_list, self._get_file_write_datum, | |
414 | label, graph_args) | |
a9c9a2a6 | 415 | |
91765fd2 | 416 | def _output_file_read_write(self): |
e0e5f1fd AB |
417 | files = self._analysis.get_files_stats(self._arg_pid_list, |
418 | self._arg_proc_list) | |
419 | self._output_file_read(files) | |
420 | self._output_file_write(files) | |
a9c9a2a6 JD |
421 | |
422 | def iotop_output(self): | |
91765fd2 AB |
423 | self._output_read() |
424 | self._output_write() | |
425 | self._output_file_read_write() | |
426 | self._output_block_read() | |
427 | self._output_block_write() | |
428 | self._output_total_rq_sectors() | |
429 | self._output_rq_count() | |
430 | self._output_avg_disk_latency() | |
431 | self._output_net_recv_bytes() | |
432 | self._output_net_sent_bytes() | |
433 | ||
434 | # IO Latency output methods | |
a9c9a2a6 JD |
435 | def iolatency_freq_histogram(self, _min, _max, res, rq_list, title): |
436 | step = (_max - _min) / res | |
437 | if step == 0: | |
438 | return | |
439 | buckets = [] | |
440 | values = [] | |
441 | graph = Pyasciigraph() | |
442 | for i in range(res): | |
443 | buckets.append(i * step) | |
444 | values.append(0) | |
445 | for i in rq_list: | |
446 | v = i / 1000 | |
447 | b = min(int((v-_min)/step), res - 1) | |
448 | values[b] += 1 | |
449 | g = [] | |
450 | i = 0 | |
451 | for v in values: | |
73b71522 | 452 | g.append(('%0.03f' % (i * step + _min), v)) |
a9c9a2a6 | 453 | i += 1 |
09071fb9 | 454 | for line in graph.graph(title, g, info_before=True, count=True): |
a9c9a2a6 | 455 | print(line) |
73b71522 | 456 | print('') |
a9c9a2a6 | 457 | |
a9c9a2a6 JD |
458 | # iolatency functions |
459 | def iolatency_output_disk(self): | |
460 | for dev in self.state.disks.keys(): | |
461 | d = self.state.disks[dev] | |
462 | if d.max is None: | |
463 | self.compute_disk_stats(d) | |
464 | if d.count is not None: | |
465 | self.iolatency_freq_histogram(d.min, d.max, | |
466 | self._arg_freq_resolution, | |
467 | d.rq_values, | |
73b71522 AB |
468 | 'Frequency distribution for ' |
469 | 'disk %s (usec)' % | |
a9c9a2a6 JD |
470 | (d.prettyname)) |
471 | ||
472 | def iolatency_output(self): | |
ef49c8de | 473 | self._output_disk_latency_freq() |
a9c9a2a6 JD |
474 | |
475 | def iolatency_syscalls_output(self): | |
476 | s = self.syscalls_stats | |
73b71522 | 477 | print('') |
a9c9a2a6 JD |
478 | if s.open_count > 0: |
479 | self.iolatency_freq_histogram(s.open_min/1000, s.open_max/1000, | |
480 | self._arg_freq_resolution, s.open_rq, | |
73b71522 | 481 | 'Open latency distribution (usec)') |
a9c9a2a6 JD |
482 | if s.read_count > 0: |
483 | self.iolatency_freq_histogram(s.read_min/1000, s.read_max/1000, | |
484 | self._arg_freq_resolution, s.read_rq, | |
73b71522 | 485 | 'Read latency distribution (usec)') |
a9c9a2a6 JD |
486 | if s.write_count > 0: |
487 | self.iolatency_freq_histogram(s.write_min/1000, s.write_max/1000, | |
488 | self._arg_freq_resolution, | |
489 | s.write_rq, | |
73b71522 | 490 | 'Write latency distribution (usec)') |
a9c9a2a6 JD |
491 | if s.sync_count > 0: |
492 | self.iolatency_freq_histogram(s.sync_min/1000, s.sync_max/1000, | |
493 | self._arg_freq_resolution, s.sync_rq, | |
73b71522 | 494 | 'Sync latency distribution (usec)') |
a9c9a2a6 JD |
495 | |
496 | def iolatency_syscalls_list_output(self, title, rq_list, | |
497 | sortkey, reverse): | |
498 | limit = self._arg_limit | |
499 | count = 0 | |
500 | outrange_legend = False | |
5faa923d | 501 | if not rq_list: |
a9c9a2a6 JD |
502 | return |
503 | print(title) | |
504 | if self._arg_extra: | |
73b71522 AB |
505 | extra_fmt = '{:<48}' |
506 | extra_title = '{:<8} {:<8} {:<8} {:<8} {:<8} {:<8} '.format( | |
507 | 'Dirtied', 'Alloc', 'Free', 'Written', 'Kswap', 'Cleared') | |
a9c9a2a6 | 508 | else: |
73b71522 AB |
509 | extra_fmt = '{:<0}' |
510 | extra_title = '' | |
511 | title_fmt = '{:<19} {:<20} {:<16} {:<23} {:<5} {:<24} {:<8} ' + \ | |
512 | extra_fmt + '{:<14}' | |
513 | fmt = '{:<40} {:<16} {:>16} {:>11} {:<24} {:<8} ' + \ | |
514 | extra_fmt + '{:<14}' | |
515 | print(title_fmt.format('Begin', 'End', 'Name', 'Duration (usec)', | |
516 | 'Size', 'Proc', 'PID', extra_title, 'Filename')) | |
a9c9a2a6 JD |
517 | for rq in sorted(rq_list, |
518 | key=operator.attrgetter(sortkey), reverse=reverse): | |
73b71522 | 519 | # only limit the output if in the 'top' view |
a9c9a2a6 JD |
520 | if reverse and count > limit: |
521 | break | |
522 | if rq.size is None: | |
73b71522 | 523 | size = 'N/A' |
a9c9a2a6 JD |
524 | else: |
525 | size = common.convert_size(rq.size) | |
526 | if self._arg_extra: | |
73b71522 | 527 | extra = '{:<8} {:<8} {:<8} {:<8} {:<8} {:<8} '.format( |
a9c9a2a6 JD |
528 | rq.dirty, rq.page_alloc, rq.page_free, rq.page_written, |
529 | rq.woke_kswapd, rq.page_cleared) | |
530 | else: | |
73b71522 AB |
531 | extra = '' |
532 | name = rq.name.replace('syscall_entry_', '').replace('sys_', '') | |
a9c9a2a6 | 533 | if rq.fd is None: |
73b71522 AB |
534 | filename = 'None' |
535 | fd = 'None' | |
a9c9a2a6 JD |
536 | else: |
537 | filename = rq.fd.filename | |
538 | fd = rq.fd.fd | |
2e781b98 JD |
539 | if rq.proc.pid is None: |
540 | pid = 'unknown (tid=%d)' % (rq.proc.tid) | |
541 | else: | |
542 | pid = rq.proc.pid | |
a9c9a2a6 JD |
543 | end = common.ns_to_hour_nsec(rq.end, self._arg_multi_day, |
544 | self._arg_gmt) | |
545 | ||
73b71522 | 546 | outrange = ' ' |
a9c9a2a6 JD |
547 | duration = rq.duration |
548 | if self._arg_begin and rq.begin < self._arg_begin: | |
73b71522 | 549 | outrange = '*' |
a9c9a2a6 JD |
550 | outrange_legend = True |
551 | if self._arg_end and rq.end > self._arg_end: | |
73b71522 | 552 | outrange = '*' |
a9c9a2a6 JD |
553 | outrange_legend = True |
554 | ||
73b71522 AB |
555 | print(fmt.format('[' + common.ns_to_hour_nsec( |
556 | rq.begin, self._arg_multi_day, self._arg_gmt) + ',' + | |
557 | end + ']' + outrange, | |
a9c9a2a6 | 558 | name, |
73b71522 | 559 | '%0.03f' % (duration/1000) + outrange, |
a9c9a2a6 | 560 | size, rq.proc.comm, |
2e781b98 | 561 | pid, extra, |
73b71522 | 562 | '%s (fd=%s)' % (filename, fd))) |
a9c9a2a6 JD |
563 | count += 1 |
564 | if outrange_legend: | |
73b71522 AB |
565 | print('*: Syscalls started and/or completed outside of the ' |
566 | 'range specified') | |
a9c9a2a6 JD |
567 | |
568 | def iolatency_syscalls_top_output(self): | |
569 | s = self.syscalls_stats | |
570 | self.iolatency_syscalls_list_output( | |
73b71522 AB |
571 | '\nTop open syscall latencies (usec)', s.all_open, |
572 | 'duration', True) | |
a9c9a2a6 | 573 | self.iolatency_syscalls_list_output( |
73b71522 AB |
574 | '\nTop read syscall latencies (usec)', s.all_read, |
575 | 'duration', True) | |
a9c9a2a6 | 576 | self.iolatency_syscalls_list_output( |
73b71522 AB |
577 | '\nTop write syscall latencies (usec)', s.all_write, |
578 | 'duration', True) | |
a9c9a2a6 | 579 | self.iolatency_syscalls_list_output( |
73b71522 AB |
580 | '\nTop sync syscall latencies (usec)', s.all_sync, |
581 | 'duration', True) | |
a9c9a2a6 JD |
582 | |
583 | def iolatency_syscalls_log_output(self): | |
584 | s = self.syscalls_stats | |
585 | self.iolatency_syscalls_list_output( | |
73b71522 | 586 | '\nLog of all I/O system calls', |
a9c9a2a6 | 587 | s.all_open + s.all_read + s.all_write + s.all_sync, |
73b71522 | 588 | 'begin', False) |
a9c9a2a6 | 589 | |
ef49c8de AB |
590 | # IO Stats output methods |
591 | def _output_latency_stats(self, name, rq_count, min_duration, max_duration, | |
592 | total_duration, rq_durations): | |
593 | if rq_count < 2: | |
594 | stdev = '?' | |
595 | else: | |
596 | stdev = '%0.03f' % (statistics.stdev(rq_durations) / 1000) | |
597 | ||
598 | avg = '%0.03f' % (total_duration / (rq_count) / 1000) | |
599 | min_duration = '%0.03f' % (min_duration / 1000) | |
600 | max_duration = '%0.03f' % (max_duration / 1000) | |
601 | ||
602 | print(IoAnalysisCommand._LATENCY_STATS_FORMAT.format( | |
603 | name, rq_count, min_duration, avg, max_duration, stdev)) | |
604 | ||
605 | def _output_latency_stats_from_requests(self, io_requests, name): | |
606 | rq_durations = [io_rq.duration for io_rq in io_requests if | |
607 | self._filter_io_request(io_rq)] | |
608 | rq_count = len(rq_durations) | |
609 | min_duration = min(rq_durations) | |
610 | max_duration = max(rq_durations) | |
611 | total_duration = sum(rq_durations) | |
612 | ||
613 | self._output_latency_stats(name, rq_count, min_duration, | |
614 | max_duration, total_duration, | |
615 | rq_durations) | |
616 | ||
617 | def _output_syscalls_latency_stats(self): | |
618 | print('\nSyscalls latency statistics (usec):') | |
619 | print(IoAnalysisCommand._LATENCY_STATS_FORMAT.format( | |
620 | 'Type', 'Count', 'Min', 'Average', 'Max', 'Stdev')) | |
621 | print(IoAnalysisCommand._SECTION_SEPARATOR_STRING) | |
622 | ||
623 | self._output_latency_stats_from_requests( | |
624 | self._analysis.open_io_requests, 'Open') | |
625 | self._output_latency_stats_from_requests( | |
626 | self._analysis.read_io_requests, 'Read') | |
627 | self._output_latency_stats_from_requests( | |
628 | self._analysis.write_io_requests, 'Write') | |
629 | self._output_latency_stats_from_requests( | |
630 | self._analysis.sync_io_requests, 'Sync') | |
631 | ||
ef49c8de AB |
632 | def _output_disk_latency_stats(self): |
633 | if not self._analysis.disks: | |
a9c9a2a6 | 634 | return |
a9c9a2a6 | 635 | |
ef49c8de AB |
636 | print('\nDisk latency statistics (usec):') |
637 | print(IoAnalysisCommand._LATENCY_STATS_FORMAT.format( | |
638 | 'Name', 'Count', 'Min', 'Average', 'Max', 'Stdev')) | |
639 | print(IoAnalysisCommand._SECTION_SEPARATOR_STRING) | |
640 | ||
641 | for disk in self._analysis.disks.values(): | |
642 | if disk.rq_count: | |
a7b98a32 | 643 | rq_durations = [rq.duration for rq in disk.rq_list] |
ef49c8de AB |
644 | self._output_latency_stats(disk.disk_name, |
645 | disk.rq_count, | |
646 | disk.min_rq_duration, | |
647 | disk.max_rq_duration, | |
648 | disk.total_rq_duration, | |
649 | rq_durations) | |
a9c9a2a6 JD |
650 | |
651 | def iostats_output(self): | |
ef49c8de AB |
652 | self._output_syscalls_latency_stats() |
653 | self._output_disk_latency_stats() | |
a9c9a2a6 | 654 | |
da6d0842 | 655 | def _print_results(self, begin_ns, end_ns): |
3664e4b0 | 656 | self._print_date(begin_ns, end_ns) |
1c0f0e3c JD |
657 | if self._arg_usage: |
658 | self.iotop_output() | |
a9c9a2a6 JD |
659 | if self._arg_stats: |
660 | self.iostats_output() | |
275bdbb4 | 661 | if self._arg_latencytop: |
a9c9a2a6 JD |
662 | self.iolatency_syscalls_top_output() |
663 | if self._arg_freq: | |
664 | self.iolatency_syscalls_output() | |
665 | self.iolatency_output() | |
666 | if self._arg_log: | |
667 | self.iolatency_syscalls_log_output() | |
668 | ||
669 | def _reset_total(self, start_ts): | |
3b1dda96 | 670 | self._analysis.reset() |
a9c9a2a6 JD |
671 | |
672 | def _add_arguments(self, ap): | |
73b71522 | 673 | ap.add_argument('--usage', action='store_true', |
1c0f0e3c | 674 | help='Show the I/O usage') |
73b71522 | 675 | ap.add_argument('--latencystats', action='store_true', |
1c0f0e3c | 676 | help='Show the I/O latency statistics') |
73b71522 | 677 | ap.add_argument('--latencytop', action='store_true', |
275bdbb4 | 678 | help='Show the I/O latency top') |
73b71522 | 679 | ap.add_argument('--latencyfreq', action='store_true', |
1c0f0e3c JD |
680 | help='Show the I/O latency frequency distribution') |
681 | ap.add_argument('--freq-resolution', type=int, default=20, | |
682 | help='Frequency distribution resolution ' | |
683 | '(default 20)') | |
a9c9a2a6 JD |
684 | |
685 | ||
686 | # entry point | |
1c0f0e3c | 687 | def runstats(): |
a9c9a2a6 | 688 | # create command |
087e5d00 | 689 | iocmd = IoAnalysisCommand() |
1c0f0e3c JD |
690 | # execute command |
691 | iocmd.run_stats() | |
692 | ||
a9c9a2a6 | 693 | |
275bdbb4 JD |
694 | def runlatencytop(): |
695 | # create command | |
087e5d00 | 696 | iocmd = IoAnalysisCommand() |
275bdbb4 JD |
697 | # execute command |
698 | iocmd.run_latencytop() | |
699 | ||
700 | ||
1c0f0e3c JD |
701 | def runlog(): |
702 | # create command | |
087e5d00 | 703 | iocmd = IoAnalysisCommand() |
1c0f0e3c JD |
704 | # execute command |
705 | iocmd.run_log() | |
706 | ||
707 | ||
708 | def runfreq(): | |
709 | # create command | |
087e5d00 | 710 | iocmd = IoAnalysisCommand() |
1c0f0e3c JD |
711 | # execute command |
712 | iocmd.run_freq() | |
713 | ||
714 | ||
715 | def runusage(): | |
716 | # create command | |
087e5d00 | 717 | iocmd = IoAnalysisCommand() |
a9c9a2a6 | 718 | # execute command |
1c0f0e3c | 719 | iocmd.run_usage() |