begin io syscall analysis
[deliverable/lttng-analyses.git] / lttnganalysescli / lttnganalysescli / io.py
CommitLineData
a9c9a2a6
JD
1from .command import Command
2import lttnganalyses.syscalls
3from linuxautomaton import common, sv
4from ascii_graph import Pyasciigraph
5import operator
6import statistics
7
8
9class IoAnalysis(Command):
10 _VERSION = '0.1.0'
11 _DESC = """The I/O command."""
12
13 def __init__(self):
14 super().__init__(self._add_arguments,
15 enable_proc_filter_args=True,
16 enable_max_min_args=True,
17 enable_max_min_size_arg=True,
18 enable_freq_arg=True,
19 enable_log_arg=True,
20 enable_stats_arg=True)
21
22 def _validate_transform_args(self):
23 self._arg_extra = self._args.extra
24
25 def run(self):
26 # parse arguments first
27 self._parse_args()
28 # validate, transform and save specific arguments
29 self._validate_transform_args()
30 # open the trace
31 self._open_trace()
32 # create the appropriate analysis/analyses
33 self._create_analysis()
34 # run the analysis
35 self._run_analysis(self._reset_total, self._refresh)
36 # process the results
37 self._compute_stats()
38 # print results
39 self._print_results(self.start_ns, self.trace_end_ts, final=1)
40 # close the trace
41 self._close_trace()
42
43 def _create_analysis(self):
44 self._analysis = lttnganalyses.syscalls.SyscallsAnalysis(
45 self._automaton.state)
46
47 def _compute_stats(self):
48 self.state = self._automaton.state
49 pass
50
51 def _refresh(self, begin, end):
52 self._compute_stats()
53 self._print_results(begin, end, final=0)
54 self._reset_total(end)
55
56 def add_fd_dict(self, tid, fd, files):
57 if fd.read == 0 and fd.write == 0:
58 return
59 if fd.filename.startswith("pipe") or \
60 fd.filename.startswith("socket") or \
61 fd.filename.startswith("anon_inode") or \
62 fd.filename.startswith("unknown"):
63 filename = "%s (%s)" % (fd.filename, tid.comm)
64 files[filename] = {}
65 files[filename]["read"] = fd.read
66 files[filename]["write"] = fd.write
67 files[filename]["name"] = filename
68 files[filename]["other"] = ["fd %d in %s (%d)" % (fd.fd,
69 tid.comm, tid.pid)]
70 else:
71 # merge counters of shared files
72 filename = fd.filename
73 if filename not in files.keys():
74 files[filename] = {}
75 files[filename]["read"] = fd.read
76 files[filename]["write"] = fd.write
77 files[filename]["name"] = filename
78 files[filename]["other"] = ["fd %d in %s (%d)" %
79 (fd.fd, tid.comm, tid.pid)]
80 files[filename]["tids"] = [tid.tid]
81 else:
82 files[filename]["read"] += fd.read
83 files[filename]["write"] += fd.write
84 files[filename]["other"].append("fd %d in %s (%d)" %
85 (fd.fd, tid.comm,
86 tid.pid))
87
88 def create_files_dict(self):
89 files = {}
90 for tid in self.state.tids.values():
91 if not self.filter_process(tid):
92 continue
93 for fd in tid.fds.values():
94 self.add_fd_dict(tid, fd, files)
95 for fd in tid.closed_fds.values():
96 self.add_fd_dict(tid, fd, files)
97 return files
98
99 # iotop functions
100 def iotop_output_print_file_read(self, files):
101 count = 0
102 limit = self._arg_limit
103 graph = Pyasciigraph()
104 values = []
105 sorted_f = sorted(files.items(), key=lambda files: files[1]['read'],
106 reverse=True)
107 for f in sorted_f:
108 if f[1]["read"] == 0:
109 continue
110 info_fmt = "{:>10}".format(common.convert_size(f[1]["read"],
111 padding_after=True))
112 values.append(("%s %s %s" % (info_fmt,
113 f[1]["name"],
114 str(f[1]["other"])[1:-1]),
115 f[1]["read"]))
116 count = count + 1
117 if limit > 0 and count >= limit:
118 break
119 for line in graph.graph('Files Read', values, sort=2,
120 with_value=False):
121 print(line)
122
123 def iotop_output_print_file_write(self, files):
124 # Compute files read
125 count = 0
126 limit = self._arg_limit
127 graph = Pyasciigraph()
128 values = []
129 sorted_f = sorted(files.items(), key=lambda files: files[1]['write'],
130 reverse=True)
131 for f in sorted_f:
132 if f[1]["write"] == 0:
133 continue
134 info_fmt = "{:>10}".format(common.convert_size(f[1]["write"],
135 padding_after=True))
136 values.append(("%s %s %s" % (info_fmt,
137 f[1]["name"],
138 str(f[1]["other"])[1:-1]),
139 f[1]["write"]))
140 count = count + 1
141 if limit > 0 and count >= limit:
142 break
143 for line in graph.graph('Files Write', values, sort=2,
144 with_value=False):
145 print(line)
146
147 def iotop_output_file_read_write(self):
148 files = self.create_files_dict()
149 self.iotop_output_print_file_read(files)
150 self.iotop_output_print_file_write(files)
151
152 def filter_process(self, proc):
153 if self._arg_proc_list and proc.comm not in self._arg_proc_list:
154 return False
155 if self._arg_pid_list and str(proc.pid) not in self._arg_pid_list:
156 return False
157 return True
158
159 def filter_size(self, size):
160 # don't filter sync and open
161 if size is None:
162 return True
163 if self._arg_maxsize is not None and size > self._arg_maxsize:
164 return False
165 if self._arg_minsize is not None and size < self._arg_minsize:
166 return False
167 return True
168
169 def filter_latency(self, duration):
170 if self._arg_max is not None and (duration/1000) > self._arg_max:
171 return False
172 if self._arg_min is not None and (duration/1000) < self._arg_min:
173 return False
174 return True
175
176 def iotop_output_read(self):
177 count = 0
178 limit = self._arg_limit
179 graph = Pyasciigraph()
180 values = []
181 for tid in sorted(self.state.tids.values(),
182 key=operator.attrgetter('read'), reverse=True):
183 if not self.filter_process(tid):
184 continue
185 info_fmt = "{:>10} {:<25} {:>9} file {:>9} net {:>9} unknown"
186 values.append((info_fmt.format(
187 common.convert_size(tid.read, padding_after=True),
188 "%s (%d)" % (tid.comm, tid.pid),
189 common.convert_size(tid.disk_read,
190 padding_after=True),
191 common.convert_size(tid.net_read,
192 padding_after=True),
193 common.convert_size(tid.unk_read,
194 padding_after=True)),
195 tid.read))
196 count = count + 1
197 if limit > 0 and count >= limit:
198 break
199 for line in graph.graph('Per-process I/O Read', values,
200 with_value=False):
201 print(line)
202
203 def iotop_output_write(self):
204 count = 0
205 limit = self._arg_limit
206 graph = Pyasciigraph()
207 values = []
208 for tid in sorted(self.state.tids.values(),
209 key=operator.attrgetter('write'), reverse=True):
210 if not self.filter_process(tid):
211 continue
212 info_fmt = "{:>10} {:<25} {:>9} file {:>9} net {:>9} unknown "
213 values.append((info_fmt.format(
214 common.convert_size(tid.write, padding_after=True),
215 "%s (%d)" % (tid.comm, tid.pid),
216 common.convert_size(tid.disk_write,
217 padding_after=True),
218 common.convert_size(tid.net_write,
219 padding_after=True),
220 common.convert_size(tid.unk_write,
221 padding_after=True)),
222 tid.write))
223 count = count + 1
224 if limit > 0 and count >= limit:
225 break
226 for line in graph.graph('Per-process I/O Write', values,
227 with_value=False):
228 print(line)
229
230 def iotop_output_disk_read(self):
231 count = 0
232 limit = self._arg_limit
233 graph = Pyasciigraph()
234 values = []
235 for tid in sorted(self.state.tids.values(),
236 key=operator.attrgetter('block_read'), reverse=True):
237 if not self.filter_process(tid):
238 continue
239 if tid.block_read == 0:
240 continue
241 info_fmt = "{:>10} {:<22}"
242 values.append((info_fmt.format(common.convert_size(tid.block_read,
243 padding_after=True),
244 "%s (pid=%d)" % (tid.comm,
245 tid.pid)),
246 tid.block_read))
247 count = count + 1
248 if limit > 0 and count >= limit:
249 break
250 for line in graph.graph('Block I/O Read', values, with_value=False):
251 print(line)
252
253 def iotop_output_disk_write(self):
254 count = 0
255 limit = self._arg_limit
256 graph = Pyasciigraph()
257 values = []
258 for tid in sorted(self.state.tids.values(),
259 key=operator.attrgetter('block_write'),
260 reverse=True):
261 if not self.filter_process(tid):
262 continue
263 if tid.block_write == 0:
264 continue
265 info_fmt = "{:>10} {:<22}"
266 values.append((info_fmt.format(common.convert_size(tid.block_write,
267 padding_after=True),
268 "%s (pid=%d)" % (tid.comm,
269 tid.pid)),
270 tid.block_write))
271 count = count + 1
272 if limit > 0 and count >= limit:
273 break
274 for line in graph.graph('Block I/O Write', values, with_value=False):
275 print(line)
276
277 def iotop_output_nr_sector(self):
278 graph = Pyasciigraph()
279 values = []
280 for disk in sorted(self.state.disks.values(),
281 key=operator.attrgetter('nr_sector'), reverse=True):
282 if disk.nr_sector == 0:
283 continue
284 values.append((disk.prettyname, disk.nr_sector))
285 for line in graph.graph('Disk nr_sector', values, unit=" sectors"):
286 print(line)
287
288 def iotop_output_nr_requests(self):
289 graph = Pyasciigraph()
290 values = []
291 for disk in sorted(self.state.disks.values(),
292 key=operator.attrgetter('nr_requests'),
293 reverse=True):
294 if disk.nr_sector == 0:
295 continue
296 values.append((disk.prettyname, disk.nr_requests))
297 for line in graph.graph('Disk nr_requests', values, unit=" requests"):
298 print(line)
299
300 def iotop_output_dev_latency(self):
301 graph = Pyasciigraph()
302 values = []
303 for disk in self.state.disks.values():
304 if disk.completed_requests == 0:
305 continue
306 total = (disk.request_time / disk.completed_requests) \
307 / sv.MSEC_PER_NSEC
308 total = float("%0.03f" % total)
309 values.append(("%s" % disk.prettyname, total))
310 for line in graph.graph('Disk request time/sector', values, sort=2,
311 unit=" ms"):
312 print(line)
313
314 def iotop_output_net_recv_bytes(self):
315 graph = Pyasciigraph()
316 values = []
317 for iface in sorted(self.state.ifaces.values(),
318 key=operator.attrgetter('recv_bytes'),
319 reverse=True):
320 values.append(("%s %s" % (common.convert_size(iface.recv_bytes),
321 iface.name),
322 iface.recv_bytes))
323 for line in graph.graph('Network recv_bytes', values,
324 with_value=False):
325 print(line)
326
327 def iotop_output_net_sent_bytes(self):
328 graph = Pyasciigraph()
329 values = []
330 for iface in sorted(self.state.ifaces.values(),
331 key=operator.attrgetter('send_bytes'),
332 reverse=True):
333 values.append(("%s %s" % (common.convert_size(iface.send_bytes),
334 iface.name),
335 iface.send_bytes))
336 for line in graph.graph('Network sent_bytes', values,
337 with_value=False):
338 print(line)
339
340 def iotop_output(self):
341 self.iotop_output_read()
342 self.iotop_output_write()
343 self.iotop_output_file_read_write()
344 self.iotop_output_disk_read()
345 self.iotop_output_disk_write()
346 self.iotop_output_nr_sector()
347 self.iotop_output_nr_requests()
348 self.iotop_output_dev_latency()
349 self.iotop_output_net_recv_bytes()
350 self.iotop_output_net_sent_bytes()
351# self.output_latencies()
352
353 def iolatency_freq_histogram(self, _min, _max, res, rq_list, title):
354 step = (_max - _min) / res
355 if step == 0:
356 return
357 buckets = []
358 values = []
359 graph = Pyasciigraph()
360 for i in range(res):
361 buckets.append(i * step)
362 values.append(0)
363 for i in rq_list:
364 v = i / 1000
365 b = min(int((v-_min)/step), res - 1)
366 values[b] += 1
367 g = []
368 i = 0
369 for v in values:
370 g.append(("%0.03f" % (i * step + _min), v))
371 i += 1
372 for line in graph.graph(title, g, info_before=True):
373 print(line)
374 print("")
375
376 def compute_disk_stats(self, dev):
377 _max = 0
378 _min = -1
379 total = 0
380 values = []
381 count = len(dev.rq_list)
382 if count == 0:
383 return
384 for rq in dev.rq_list:
385 if rq.duration > _max:
386 _max = rq.duration
387 if _min == -1 or rq.duration < _min:
388 _min = rq.duration
389 total += rq.duration
390 values.append(rq.duration)
391 if count > 2:
392 stdev = statistics.stdev(values) / 1000
393 else:
394 stdev = "?"
395 dev.min = _min / 1000
396 dev.max = _max / 1000
397 dev.total = total / 1000
398 dev.count = count
399 dev.rq_values = values
400 dev.stdev = stdev
401
402 # iolatency functions
403 def iolatency_output_disk(self):
404 for dev in self.state.disks.keys():
405 d = self.state.disks[dev]
406 if d.max is None:
407 self.compute_disk_stats(d)
408 if d.count is not None:
409 self.iolatency_freq_histogram(d.min, d.max,
410 self._arg_freq_resolution,
411 d.rq_values,
412 "Frequency distribution for "
413 "disk %s (usec)" %
414 (d.prettyname))
415
416 def iolatency_output(self):
417 self.iolatency_output_disk()
418
419# def output_latencies(self):
420# graph = Pyasciigraph()
421# for proc in self.latency_hist.keys():
422# values = []
423# for v in self.latency_hist[proc]:
424# values.append(("%s" % (v[0]), v[1]))
425# for line in graph.graph('%s requests latency (ms)' % proc, values,
426# unit=" ms"):
427# print(line)
428
429 def iostats_minmax(self, duration, current_min, current_max):
430 _min = current_min
431 _max = current_max
432 if current_min is None or duration < current_min:
433 _min = duration
434 if duration > current_max:
435 _max = duration
436 return (_min, _max)
437
438 def iostats_syscalls_line(self, fmt, name, count, _min, _max, total, rq):
439 if count < 2:
440 stdev = "?"
441 else:
442 stdev = "%0.03f" % (statistics.stdev(rq) / 1000)
443 if count < 1:
444 avg = "0.000"
445 else:
446 avg = "%0.03f" % (total / (count * 1000))
447 if _min is None:
448 _min = 0
449 _min = "%0.03f" % (_min / 1000)
450 _max = "%0.03f" % (_max / 1000)
451 print(fmt.format(name, count, _min, avg, _max, stdev))
452
453 def account_syscall_iorequests(self, s, iorequests):
454 for rq in iorequests:
455 # filter out if completely out of range but accept the
456 # union to show the real begin/end time
457 if self._arg_begin and self._arg_end and rq.end and \
458 rq.begin > self._arg_end:
459 continue
460 if rq.iotype != sv.IORequest.IO_SYSCALL:
461 continue
462 if not self.filter_size(rq.size):
463 continue
464 if not self.filter_latency(rq.duration):
465 continue
466 if rq.operation == sv.IORequest.OP_READ:
467 s.read_count += 1
468 s.read_total += rq.duration
469 s.read_rq.append(rq.duration)
470 s.all_read.append(rq)
471 s.read_min, s.read_max = self.iostats_minmax(
472 rq.duration, s.read_min, s.read_max)
473 elif rq.operation == sv.IORequest.OP_WRITE:
474 s.write_count += 1
475 s.write_total += rq.duration
476 s.write_rq.append(rq.duration)
477 s.all_write.append(rq)
478 s.write_min, s.write_max = self.iostats_minmax(
479 rq.duration, s.write_min, s.write_max)
480 elif rq.operation == sv.IORequest.OP_SYNC:
481 s.sync_count += 1
482 s.sync_total += rq.duration
483 s.sync_rq.append(rq.duration)
484 s.all_sync.append(rq)
485 s.sync_min, s.sync_max = self.iostats_minmax(
486 rq.duration, s.sync_min, s.sync_max)
487 elif rq.operation == sv.IORequest.OP_OPEN:
488 s.open_count += 1
489 s.open_total += rq.duration
490 s.open_rq.append(rq.duration)
491 s.all_open.append(rq)
492 s.open_min, s.open_max = self.iostats_minmax(
493 rq.duration, s.open_min, s.open_max)
494
495 def compute_syscalls_latency_stats(self, end_ns):
496 s = sv.Syscalls_stats()
497 for tid in self.state.tids.values():
498 if not self.filter_process(tid):
499 continue
500 self.account_syscall_iorequests(s, tid.iorequests)
501 for fd in tid.fds.values():
502 self.account_syscall_iorequests(s, fd.iorequests)
503 for fd in tid.closed_fds.values():
504 self.account_syscall_iorequests(s, fd.iorequests)
505 return s
506
507 def iostats_output_syscalls(self):
508 s = self.syscalls_stats
509 print("\nSyscalls latency statistics (usec):")
510 fmt = "{:<14} {:>14} {:>14} {:>14} {:>14} {:>14}"
511 print(fmt.format("Type", "Count", "Min", "Average",
512 "Max", "Stdev"))
513 print("-" * 89)
514 self.iostats_syscalls_line(fmt, "Open", s.open_count, s.open_min,
515 s.open_max, s.open_total, s.open_rq)
516 self.iostats_syscalls_line(fmt, "Read", s.read_count, s.read_min,
517 s.read_max, s.read_total, s.read_rq)
518 self.iostats_syscalls_line(fmt, "Write", s.write_count, s.write_min,
519 s.write_max, s.write_total, s.write_rq)
520 self.iostats_syscalls_line(fmt, "Sync", s.sync_count, s.sync_min,
521 s.sync_max, s.sync_total, s.sync_rq)
522
523 def iolatency_syscalls_output(self):
524 s = self.syscalls_stats
525 print("")
526 if s.open_count > 0:
527 self.iolatency_freq_histogram(s.open_min/1000, s.open_max/1000,
528 self._arg_freq_resolution, s.open_rq,
529 "Open latency distribution (usec)")
530 if s.read_count > 0:
531 self.iolatency_freq_histogram(s.read_min/1000, s.read_max/1000,
532 self._arg_freq_resolution, s.read_rq,
533 "Read latency distribution (usec)")
534 if s.write_count > 0:
535 self.iolatency_freq_histogram(s.write_min/1000, s.write_max/1000,
536 self._arg_freq_resolution,
537 s.write_rq,
538 "Write latency distribution (usec)")
539 if s.sync_count > 0:
540 self.iolatency_freq_histogram(s.sync_min/1000, s.sync_max/1000,
541 self._arg_freq_resolution, s.sync_rq,
542 "Sync latency distribution (usec)")
543
544 def iolatency_syscalls_list_output(self, title, rq_list,
545 sortkey, reverse):
546 limit = self._arg_limit
547 count = 0
548 outrange_legend = False
549 if len(rq_list) == 0:
550 return
551 print(title)
552 if self._arg_extra:
553 extra_fmt = "{:<48}"
554 extra_title = "{:<8} {:<8} {:<8} {:<8} {:<8} {:<8} ".format(
555 "Dirtied", "Alloc", "Free", "Written", "Kswap", "Cleared")
556 else:
557 extra_fmt = "{:<0}"
558 extra_title = ""
559 title_fmt = "{:<19} {:<20} {:<16} {:<23} {:<5} {:<24} {:<8} " + \
560 extra_fmt + "{:<14}"
561 fmt = "{:<40} {:<16} {:>16} {:>12} {:<24} {:<8} " + \
562 extra_fmt + "{:<14}"
563 print(title_fmt.format("Begin", "End", "Name", "Duration (usec)",
564 "Size", "Proc", "PID", extra_title, "Filename"))
565 for rq in sorted(rq_list,
566 key=operator.attrgetter(sortkey), reverse=reverse):
567 # only limit the output if in the "top" view
568 if reverse and count > limit:
569 break
570 if rq.size is None:
571 size = "N/A"
572 else:
573 size = common.convert_size(rq.size)
574 if self._arg_extra:
575 extra = "{:<8} {:<8} {:<8} {:<8} {:<8} {:<8} ".format(
576 rq.dirty, rq.page_alloc, rq.page_free, rq.page_written,
577 rq.woke_kswapd, rq.page_cleared)
578 else:
579 extra = ""
580 name = rq.name.replace("syscall_entry_", "").replace("sys_", "")
581 if rq.fd is None:
582 filename = "None"
583 fd = "None"
584 else:
585 filename = rq.fd.filename
586 fd = rq.fd.fd
587 end = common.ns_to_hour_nsec(rq.end, self._arg_multi_day,
588 self._arg_gmt)
589
590 outrange = " "
591 duration = rq.duration
592 if self._arg_begin and rq.begin < self._arg_begin:
593 outrange = "*"
594 outrange_legend = True
595 if self._arg_end and rq.end > self._arg_end:
596 outrange = "*"
597 outrange_legend = True
598
599 print(fmt.format("[" + common.ns_to_hour_nsec(
600 rq.begin, self._arg_multi_day, self._arg_gmt) + "," +
601 end + "]" + outrange,
602 name,
603 "%0.03f" % (duration/1000) + outrange,
604 size, rq.proc.comm,
605 rq.proc.pid, extra,
606 "%s (fd=%s)" % (filename, fd)))
607 count += 1
608 if outrange_legend:
609 print("*: Syscalls started and/or completed outside of the "
610 "range specified")
611
612 def iolatency_syscalls_top_output(self):
613 s = self.syscalls_stats
614 self.iolatency_syscalls_list_output(
615 "\nTop open syscall latencies (usec)", s.all_open,
616 "duration", True)
617 self.iolatency_syscalls_list_output(
618 "\nTop read syscall latencies (usec)", s.all_read,
619 "duration", True)
620 self.iolatency_syscalls_list_output(
621 "\nTop write syscall latencies (usec)", s.all_write,
622 "duration", True)
623 self.iolatency_syscalls_list_output(
624 "\nTop sync syscall latencies (usec)", s.all_sync,
625 "duration", True)
626
627 def iolatency_syscalls_log_output(self):
628 s = self.syscalls_stats
629 self.iolatency_syscalls_list_output(
630 "\nLog of all I/O system calls",
631 s.all_open + s.all_read + s.all_write + s.all_sync,
632 "begin", False)
633
634 # iostats functions
635 def iostats_output_disk(self):
636 # TODO same with network
637 if len(self.state.disks.keys()) == 0:
638 return
639 print("\nDisk latency statistics (usec):")
640 fmt = "{:<14} {:>14} {:>14} {:>14} {:>14} {:>14}"
641 print(fmt.format("Name", "Count", "Min", "Average", "Max", "Stdev"))
642 print("-" * 89)
643
644 for dev in self.state.disks.keys():
645 d = self.state.disks[dev]
646 if d.max is None:
647 d = self.state.disks[dev]
648 self.compute_disk_stats(d)
649 if d.count is not None:
650 self.iostats_syscalls_line(fmt, d.prettyname, d.count, d.min,
651 d.max, d.total, d.rq_values)
652
653 def iostats_output(self):
654 self.iostats_output_syscalls()
655 self.iostats_output_disk()
656
657 def _print_results(self, begin_ns, end_ns, final=0):
658 print('%s to %s' % (common.ns_to_asctime(begin_ns),
659 common.ns_to_asctime(end_ns)))
660 self.iotop_output()
661 self.syscalls_stats = self.compute_syscalls_latency_stats(end_ns)
662 if self._arg_stats:
663 self.iostats_output()
664 self.iolatency_syscalls_top_output()
665 if self._arg_freq:
666 self.iolatency_syscalls_output()
667 self.iolatency_output()
668 if self._arg_log:
669 self.iolatency_syscalls_log_output()
670
671 def _reset_total(self, start_ts):
672 for dev in self.state.disks.keys():
673 self.state.disks[dev].init_counts()
674
675 for iface in self.state.ifaces.keys():
676 self.state.ifaces[iface].init_counts()
677
678 for tid in self.state.tids.values():
679 for fd in tid.fds.values():
680 fd.init_counts()
681 for fd in tid.closed_fds.values():
682 fd.init_counts()
683 tid.init_counts()
684
685 def _add_arguments(self, ap):
686 ap.add_argument('--extra', type=str, default=0,
687 help='Show extra information in stats (beta)')
688 # specific argument
689 pass
690
691
692# entry point
693def run():
694 # create command
695 iocmd = IoAnalysis()
696
697 # execute command
698 iocmd.run()
This page took 0.048694 seconds and 5 git commands to generate.