Commit | Line | Data |
---|---|---|
a9c9a2a6 JD |
1 | from .command import Command |
2 | import lttnganalyses.syscalls | |
3 | from linuxautomaton import common, sv | |
4 | from ascii_graph import Pyasciigraph | |
5 | import operator | |
6 | import statistics | |
7 | ||
8 | ||
9 | class 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 | |
693 | def run(): | |
694 | # create command | |
695 | iocmd = IoAnalysis() | |
696 | ||
697 | # execute command | |
698 | iocmd.run() |