1 # The MIT License (MIT)
3 # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com>
4 # 2015 - Antoine Busque <abusque@efficios.com>
5 # 2015 - Philippe Proulx <pproulx@efficios.com>
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:
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
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
30 from . import termgraph
32 from ..common
import format_utils
33 from .command
import Command
36 _UsageTables
= collections
.namedtuple('_UsageTables', [
41 'per_proc_block_read',
42 'per_proc_block_write',
51 class IoAnalysisCommand(Command
):
52 _DESC
= """The I/O command."""
53 _ANALYSIS_CLASS
= io
.IoAnalysis
54 _MI_TITLE
= 'I/O analysis'
55 _MI_DESCRIPTION
= 'System call/disk latency statistics, system call ' + \
56 'latency distribution, system call top latencies, ' + \
57 'I/O usage top, and I/O operations log'
66 _MI_TABLE_CLASS_SYSCALL_LATENCY_STATS
= 'syscall-latency-stats'
67 _MI_TABLE_CLASS_PART_LATENCY_STATS
= 'disk-latency-stats'
68 _MI_TABLE_CLASS_FREQ
= 'freq'
69 _MI_TABLE_CLASS_TOP_SYSCALL
= 'top-syscall'
70 _MI_TABLE_CLASS_LOG
= 'log'
71 _MI_TABLE_CLASS_PER_PROCESS_TOP
= 'per-process-top'
72 _MI_TABLE_CLASS_PER_FILE_TOP
= 'per-file-top'
73 _MI_TABLE_CLASS_PER_PROCESS_TOP_BLOCK
= 'per-process-top-block'
74 _MI_TABLE_CLASS_PER_DISK_TOP_SECTOR
= 'per-disk-top-sector'
75 _MI_TABLE_CLASS_PER_DISK_TOP_REQUEST
= 'per-disk-top-request'
76 _MI_TABLE_CLASS_PER_DISK_TOP_RTPS
= 'per-disk-top-rps'
77 _MI_TABLE_CLASS_PER_NETIF_TOP
= 'per-netif-top'
80 _MI_TABLE_CLASS_SYSCALL_LATENCY_STATS
,
81 'System call latency statistics', [
82 ('obj', 'System call category', mi
.String
),
83 ('count', 'Call count', mi
.Integer
, 'calls'),
84 ('min_latency', 'Minimum call latency', mi
.Duration
),
85 ('avg_latency', 'Average call latency', mi
.Duration
),
86 ('max_latency', 'Maximum call latency', mi
.Duration
),
87 ('stdev_latency', 'System call latency standard deviation',
92 _MI_TABLE_CLASS_PART_LATENCY_STATS
,
93 'Partition latency statistics', [
94 ('obj', 'Partition', mi
.Disk
),
95 ('count', 'Access count', mi
.Integer
, 'accesses'),
96 ('min_latency', 'Minimum access latency', mi
.Duration
),
97 ('avg_latency', 'Average access latency', mi
.Duration
),
98 ('max_latency', 'Maximum access latency', mi
.Duration
),
99 ('stdev_latency', 'System access latency standard deviation',
104 _MI_TABLE_CLASS_FREQ
,
105 'I/O request latency distribution', [
106 ('latency_lower', 'Latency (lower bound)', mi
.Duration
),
107 ('latency_upper', 'Latency (upper bound)', mi
.Duration
),
108 ('count', 'Request count', mi
.Integer
, 'requests'),
112 _MI_TABLE_CLASS_TOP_SYSCALL
,
113 'Top system call latencies', [
114 ('time_range', 'Call time range', mi
.TimeRange
),
115 ('out_of_range', 'System call out of range?', mi
.Boolean
),
116 ('duration', 'Call duration', mi
.Duration
),
117 ('syscall', 'System call', mi
.Syscall
),
118 ('size', 'Read/write size', mi
.Size
),
119 ('process', 'Process', mi
.Process
),
120 ('path', 'File path', mi
.Path
),
121 ('fd', 'File descriptor', mi
.Fd
),
126 'I/O operations log', [
127 ('time_range', 'Call time range', mi
.TimeRange
),
128 ('out_of_range', 'System call out of range?', mi
.Boolean
),
129 ('duration', 'Call duration', mi
.Duration
),
130 ('syscall', 'System call', mi
.Syscall
),
131 ('size', 'Read/write size', mi
.Size
),
132 ('process', 'Process', mi
.Process
),
133 ('path', 'File path', mi
.Path
),
134 ('fd', 'File descriptor', mi
.Fd
),
138 _MI_TABLE_CLASS_PER_PROCESS_TOP
,
139 'Per-process top I/O operations', [
140 ('process', 'Process', mi
.Process
),
141 ('size', 'Total operations size', mi
.Size
),
142 ('disk_size', 'Disk operations size', mi
.Size
),
143 ('net_size', 'Network operations size', mi
.Size
),
144 ('unknown_size', 'Unknown operations size', mi
.Size
),
148 _MI_TABLE_CLASS_PER_FILE_TOP
,
149 'Per-file top I/O operations', [
150 ('path', 'File path/info', mi
.Path
),
151 ('size', 'Operations size', mi
.Size
),
152 ('fd_owners', 'File descriptor owners', mi
.String
),
156 _MI_TABLE_CLASS_PER_PROCESS_TOP_BLOCK
,
157 'Per-process top block I/O operations', [
158 ('process', 'Process', mi
.Process
),
159 ('size', 'Operations size', mi
.Size
),
163 _MI_TABLE_CLASS_PER_DISK_TOP_SECTOR
,
164 'Per-disk top sector I/O operations', [
165 ('disk', 'Disk', mi
.Disk
),
166 ('count', 'Sector count', mi
.Integer
, 'sectors'),
170 _MI_TABLE_CLASS_PER_DISK_TOP_REQUEST
,
171 'Per-disk top I/O requests', [
172 ('disk', 'Disk', mi
.Disk
),
173 ('count', 'Request count', mi
.Integer
, 'I/O requests'),
177 _MI_TABLE_CLASS_PER_DISK_TOP_RTPS
,
178 'Per-disk top I/O request time/sector', [
179 ('disk', 'Disk', mi
.Disk
),
180 ('rtps', 'Request time/sector', mi
.Duration
),
184 _MI_TABLE_CLASS_PER_NETIF_TOP
,
185 'Per-network interface top I/O operations', [
186 ('netif', 'Network interface', mi
.NetIf
),
187 ('size', 'Operations size', mi
.Size
),
191 _LATENCY_STATS_FORMAT
= '{:<14} {:>14} {:>14} {:>14} {:>14} {:>14}'
192 _SECTION_SEPARATOR_STRING
= '-' * 89
194 def _analysis_tick(self
, begin_ns
, end_ns
):
195 syscall_latency_stats_table
= None
196 disk_latency_stats_table
= None
203 syscall_latency_stats_table
, disk_latency_stats_table
= \
204 self
._get
_latency
_stats
_result
_tables
(begin_ns
, end_ns
)
207 freq_tables
= self
._get
_freq
_result
_tables
(begin_ns
, end_ns
)
210 usage_tables
= self
._get
_usage
_result
_tables
(begin_ns
, end_ns
)
213 top_tables
= self
._get
_top
_result
_tables
(begin_ns
, end_ns
)
216 log_table
= self
._get
_log
_result
_table
(begin_ns
, end_ns
)
219 self
._mi
_append
_result
_tables
([
221 syscall_latency_stats_table
,
222 disk_latency_stats_table
,
224 self
._mi
_append
_result
_tables
(top_tables
)
225 self
._mi
_append
_result
_tables
(usage_tables
)
226 self
._mi
_append
_result
_tables
(freq_tables
)
228 self
._print
_date
(begin_ns
, end_ns
)
231 self
._print
_usage
(usage_tables
)
234 self
._print
_latency
_stats
(syscall_latency_stats_table
,
235 disk_latency_stats_table
)
238 self
._print
_top
(top_tables
)
241 self
._print
_freq
(freq_tables
)
244 self
._print
_log
(log_table
)
246 def _create_summary_result_tables(self
):
247 # TODO: create a summary table here
248 self
._mi
_clear
_result
_tables
()
251 def _filter_size(self
, size
):
254 if self
._args
.maxsize
is not None and size
> self
._args
.maxsize
:
256 if self
._args
.minsize
is not None and size
< self
._args
.minsize
:
260 def _filter_latency(self
, duration
):
261 if self
._args
.max is not None and duration
> self
._args
.max:
263 if self
._args
.min is not None and duration
< self
._args
.min:
267 def _filter_time_range(self
, begin
, end
):
268 # Note: we only want to return False only when a request has
269 # ended and is completely outside the timerange (i.e. begun
270 # after the end of the time range).
271 return not (self
._args
.begin
and self
._args
.end
and end
and
272 begin
> self
._args
.end
)
274 def _filter_io_request(self
, io_rq
):
275 return self
._filter
_size
(io_rq
.size
) and \
276 self
._filter
_latency
(io_rq
.duration
) and \
277 self
._filter
_time
_range
(io_rq
.begin_ts
, io_rq
.end_ts
)
279 def _is_io_rq_out_of_range(self
, io_rq
):
280 return self
._args
.begin
and io_rq
.begin_ts
< self
._args
.begin
or \
281 self
._args
.end
and io_rq
.end_ts
> self
._args
.end
283 def _append_per_proc_read_usage_row(self
, proc_stats
, result_table
):
284 result_table
.append_row(
285 process
=mi
.Process(proc_stats
.comm
, pid
=proc_stats
.pid
,
287 size
=mi
.Size(proc_stats
.total_read
),
288 disk_size
=mi
.Size(proc_stats
.disk_io
.read
),
289 net_size
=mi
.Size(proc_stats
.net_io
.read
),
290 unknown_size
=mi
.Size(proc_stats
.unk_io
.read
),
295 def _append_per_proc_write_usage_row(self
, proc_stats
, result_table
):
296 result_table
.append_row(
297 process
=mi
.Process(proc_stats
.comm
, pid
=proc_stats
.pid
,
299 size
=mi
.Size(proc_stats
.total_write
),
300 disk_size
=mi
.Size(proc_stats
.disk_io
.write
),
301 net_size
=mi
.Size(proc_stats
.net_io
.write
),
302 unknown_size
=mi
.Size(proc_stats
.unk_io
.write
),
307 def _append_per_proc_block_read_usage_row(self
, proc_stats
, result_table
):
308 if proc_stats
.block_io
.read
== 0:
312 proc_name
= proc_stats
.comm
316 result_table
.append_row(
317 process
=mi
.Process(proc_name
, pid
=proc_stats
.pid
,
319 size
=mi
.Size(proc_stats
.block_io
.read
),
324 def _append_per_proc_block_write_usage_row(self
, proc_stats
, result_table
):
325 if proc_stats
.block_io
.write
== 0:
329 proc_name
= proc_stats
.comm
333 result_table
.append_row(
334 process
=mi
.Process(proc_name
, pid
=proc_stats
.pid
,
336 size
=mi
.Size(proc_stats
.block_io
.write
),
341 def _append_disk_sector_usage_row(self
, disk_stats
, result_table
):
342 if disk_stats
.total_rq_sectors
== 0:
345 result_table
.append_row(
346 disk
=mi
.Disk(disk_stats
.disk_name
),
347 count
=mi
.Integer(disk_stats
.total_rq_sectors
),
352 def _append_disk_request_usage_row(self
, disk_stats
, result_table
):
353 if disk_stats
.rq_count
== 0:
356 result_table
.append_row(
357 disk
=mi
.Disk(disk_stats
.disk_name
),
358 count
=mi
.Integer(disk_stats
.rq_count
),
363 def _append_disk_rtps_usage_row(self
, disk_stats
, result_table
):
364 if disk_stats
.rq_count
== 0:
367 avg_latency
= (disk_stats
.total_rq_duration
/ disk_stats
.rq_count
)
368 result_table
.append_row(
369 disk
=mi
.Disk(disk_stats
.disk_name
),
370 rtps
=mi
.Duration(avg_latency
),
375 def _append_netif_recv_usage_row(self
, netif_stats
, result_table
):
376 result_table
.append_row(
377 netif
=mi
.NetIf(netif_stats
.name
),
378 size
=mi
.Size(netif_stats
.recv_bytes
)
383 def _append_netif_send_usage_row(self
, netif_stats
, result_table
):
384 result_table
.append_row(
385 netif
=mi
.NetIf(netif_stats
.name
),
386 size
=mi
.Size(netif_stats
.sent_bytes
)
391 def _get_file_stats_fd_owners_str(self
, file_stats
):
394 for pid
, fd
in file_stats
.fd_by_pid
.items():
395 comm
= self
._analysis
.tids
[pid
].comm
396 fd_by_pid_str
+= 'fd %d in %s (%s) ' % (fd
, comm
, pid
)
400 def _append_file_read_usage_row(self
, file_stats
, result_table
):
401 if file_stats
.io
.read
== 0:
404 fd_owners
= self
._get
_file
_stats
_fd
_owners
_str
(file_stats
)
405 result_table
.append_row(
406 path
=mi
.Path(file_stats
.filename
),
407 size
=mi
.Size(file_stats
.io
.read
),
408 fd_owners
=mi
.String(fd_owners
),
413 def _append_file_write_usage_row(self
, file_stats
, result_table
):
414 if file_stats
.io
.write
== 0:
417 fd_owners
= self
._get
_file
_stats
_fd
_owners
_str
(file_stats
)
418 result_table
.append_row(
419 path
=mi
.Path(file_stats
.filename
),
420 size
=mi
.Size(file_stats
.io
.write
),
421 fd_owners
=mi
.String(fd_owners
),
426 def _fill_usage_result_table(self
, input_list
, append_row_cb
,
429 limit
= self
._args
.limit
431 for elem
in input_list
:
432 if append_row_cb(elem
, result_table
):
435 if limit
is not None and count
>= limit
:
438 def _fill_per_process_read_usage_result_table(self
, result_table
):
439 input_list
= sorted(self
._analysis
.tids
.values(),
440 key
=operator
.attrgetter('total_read'),
442 self
._fill
_usage
_result
_table
(input_list
,
443 self
._append
_per
_proc
_read
_usage
_row
,
446 def _fill_per_process_write_usage_result_table(self
, result_table
):
447 input_list
= sorted(self
._analysis
.tids
.values(),
448 key
=operator
.attrgetter('total_write'),
450 self
._fill
_usage
_result
_table
(input_list
,
451 self
._append
_per
_proc
_write
_usage
_row
,
454 def _fill_per_process_block_read_usage_result_table(self
, result_table
):
455 input_list
= sorted(self
._analysis
.tids
.values(),
456 key
=operator
.attrgetter('block_io.read'),
458 self
._fill
_usage
_result
_table
(
459 input_list
, self
._append
_per
_proc
_block
_read
_usage
_row
,
462 def _fill_per_process_block_write_usage_result_table(self
, result_table
):
463 input_list
= sorted(self
._analysis
.tids
.values(),
464 key
=operator
.attrgetter('block_io.write'),
466 self
._fill
_usage
_result
_table
(
467 input_list
, self
._append
_per
_proc
_block
_write
_usage
_row
,
470 def _fill_disk_sector_usage_result_table(self
, result_table
):
471 input_list
= sorted(self
._analysis
.disks
.values(),
472 key
=operator
.attrgetter('total_rq_sectors'),
474 self
._fill
_usage
_result
_table
(input_list
,
475 self
._append
_disk
_sector
_usage
_row
,
478 def _fill_disk_request_usage_result_table(self
, result_table
):
479 input_list
= sorted(self
._analysis
.disks
.values(),
480 key
=operator
.attrgetter('rq_count'),
482 self
._fill
_usage
_result
_table
(input_list
,
483 self
._append
_disk
_request
_usage
_row
,
486 def _fill_disk_rtps_usage_result_table(self
, result_table
):
487 input_list
= self
._analysis
.disks
.values()
488 self
._fill
_usage
_result
_table
(input_list
,
489 self
._append
_disk
_rtps
_usage
_row
,
492 def _fill_netif_recv_usage_result_table(self
, result_table
):
493 input_list
= sorted(self
._analysis
.ifaces
.values(),
494 key
=operator
.attrgetter('recv_bytes'),
496 self
._fill
_usage
_result
_table
(input_list
,
497 self
._append
_netif
_recv
_usage
_row
,
500 def _fill_netif_send_usage_result_table(self
, result_table
):
501 input_list
= sorted(self
._analysis
.ifaces
.values(),
502 key
=operator
.attrgetter('sent_bytes'),
504 self
._fill
_usage
_result
_table
(input_list
,
505 self
._append
_netif
_send
_usage
_row
,
508 def _fill_file_read_usage_result_table(self
, files
, result_table
):
509 input_list
= sorted(files
.values(),
510 key
=lambda file_stats
: file_stats
.io
.read
,
512 self
._fill
_usage
_result
_table
(input_list
,
513 self
._append
_file
_read
_usage
_row
,
516 def _fill_file_write_usage_result_table(self
, files
, result_table
):
517 input_list
= sorted(files
.values(),
518 key
=lambda file_stats
: file_stats
.io
.write
,
520 self
._fill
_usage
_result
_table
(input_list
,
521 self
._append
_file
_write
_usage
_row
,
524 def _fill_file_usage_result_tables(self
, read_table
, write_table
):
525 files
= self
._analysis
.get_files_stats()
526 self
._fill
_file
_read
_usage
_result
_table
(files
, read_table
)
527 self
._fill
_file
_write
_usage
_result
_table
(files
, write_table
)
529 def _get_usage_result_tables(self
, begin
, end
):
530 # create result tables
531 per_proc_read_table
= self
._mi
_create
_result
_table
(
532 self
._MI
_TABLE
_CLASS
_PER
_PROCESS
_TOP
, begin
, end
, 'read')
533 per_proc_write_table
= self
._mi
_create
_result
_table
(
534 self
._MI
_TABLE
_CLASS
_PER
_PROCESS
_TOP
, begin
, end
, 'written')
535 per_file_read_table
= self
._mi
_create
_result
_table
(
536 self
._MI
_TABLE
_CLASS
_PER
_FILE
_TOP
, begin
, end
, 'read')
537 per_file_write_table
= self
._mi
_create
_result
_table
(
538 self
._MI
_TABLE
_CLASS
_PER
_FILE
_TOP
, begin
, end
, 'written')
539 per_proc_block_read_table
= self
._mi
_create
_result
_table
(
540 self
._MI
_TABLE
_CLASS
_PER
_PROCESS
_TOP
_BLOCK
, begin
, end
, 'read')
541 per_proc_block_write_table
= self
._mi
_create
_result
_table
(
542 self
._MI
_TABLE
_CLASS
_PER
_PROCESS
_TOP
_BLOCK
, begin
, end
, 'written')
543 per_disk_sector_table
= self
._mi
_create
_result
_table
(
544 self
._MI
_TABLE
_CLASS
_PER
_DISK
_TOP
_SECTOR
, begin
, end
)
545 per_disk_request_table
= self
._mi
_create
_result
_table
(
546 self
._MI
_TABLE
_CLASS
_PER
_DISK
_TOP
_REQUEST
, begin
, end
)
547 per_disk_rtps_table
= self
._mi
_create
_result
_table
(
548 self
._MI
_TABLE
_CLASS
_PER
_DISK
_TOP
_RTPS
, begin
, end
)
549 per_netif_recv_table
= self
._mi
_create
_result
_table
(
550 self
._MI
_TABLE
_CLASS
_PER
_NETIF
_TOP
, begin
, end
, 'received')
551 per_netif_send_table
= self
._mi
_create
_result
_table
(
552 self
._MI
_TABLE
_CLASS
_PER
_NETIF
_TOP
, begin
, end
, 'sent')
555 self
._fill
_per
_process
_read
_usage
_result
_table
(per_proc_read_table
)
556 self
._fill
_per
_process
_write
_usage
_result
_table
(per_proc_write_table
)
557 self
._fill
_file
_usage
_result
_tables
(per_file_read_table
,
558 per_file_write_table
)
559 self
._fill
_per
_process
_block
_read
_usage
_result
_table
(
560 per_proc_block_read_table
)
561 self
._fill
_per
_process
_block
_write
_usage
_result
_table
(
562 per_proc_block_write_table
)
563 self
._fill
_disk
_sector
_usage
_result
_table
(per_disk_sector_table
)
564 self
._fill
_disk
_request
_usage
_result
_table
(per_disk_request_table
)
565 self
._fill
_disk
_rtps
_usage
_result
_table
(per_disk_rtps_table
)
566 self
._fill
_netif
_recv
_usage
_result
_table
(per_netif_recv_table
)
567 self
._fill
_netif
_send
_usage
_result
_table
(per_netif_send_table
)
570 per_proc_read
=per_proc_read_table
,
571 per_proc_write
=per_proc_write_table
,
572 per_file_read
=per_file_read_table
,
573 per_file_write
=per_file_write_table
,
574 per_proc_block_read
=per_proc_block_read_table
,
575 per_proc_block_write
=per_proc_block_write_table
,
576 per_disk_sector
=per_disk_sector_table
,
577 per_disk_request
=per_disk_request_table
,
578 per_disk_rtps
=per_disk_rtps_table
,
579 per_netif_recv
=per_netif_recv_table
,
580 per_netif_send
=per_netif_send_table
,
583 def _print_per_proc_io(self
, result_table
, title
):
584 header_format
= '{:<25} {:<10} {:<10} {:<10}'
585 label_header
= header_format
.format(
586 'Process', 'Disk', 'Net', 'Unknown'
590 label_format
= '{:<25} {:>10} {:>10} {:>10}'
591 if row
.process
.pid
is None:
592 pid_str
= 'unknown (tid=%d)' % (row
.process
.tid
)
594 pid_str
= str(row
.process
.pid
)
596 label
= label_format
.format(
597 '%s (%s)' % (row
.process
.name
, pid_str
),
598 format_utils
.format_size(row
.disk_size
.value
),
599 format_utils
.format_size(row
.net_size
.value
),
600 format_utils
.format_size(row
.unknown_size
.value
)
605 graph
= termgraph
.BarGraph(
606 title
='Per-process I/O ' + title
,
607 label_header
=label_header
,
608 get_value
=lambda row
: row
.size
.value
,
609 get_value_str
=format_utils
.format_size
,
611 data
=result_table
.rows
616 def _print_per_proc_block_io(self
, result_table
, title
):
618 proc_name
= row
.process
.name
621 proc_name
= 'unknown'
623 if row
.process
.pid
is None:
624 pid_str
= 'unknown (tid={})'.format(row
.process
.tid
)
626 pid_str
= str(row
.process
.pid
)
628 return '{} (pid={})'.format(proc_name
, pid_str
)
630 graph
= termgraph
.BarGraph(
631 title
='Block I/O ' + title
,
632 label_header
='Process',
633 get_value
=lambda row
: row
.size
.value
,
634 get_value_str
=format_utils
.format_size
,
636 data
=result_table
.rows
641 def _print_per_disk_sector(self
, result_table
):
642 graph
= termgraph
.BarGraph(
643 title
='Disk Requests Sector Count',
646 get_value
=lambda row
: row
.count
.value
,
647 get_label
=lambda row
: row
.disk
.name
,
648 data
=result_table
.rows
653 def _print_per_disk_request(self
, result_table
):
654 graph
= termgraph
.BarGraph(
655 title
='Disk Request Count',
658 get_value
=lambda row
: row
.count
.value
,
659 get_label
=lambda row
: row
.disk
.name
,
660 data
=result_table
.rows
665 def _print_per_disk_rtps(self
, result_table
):
666 graph
= termgraph
.BarGraph(
667 title
='Disk Request Average Latency',
670 get_value
=lambda row
: row
.rtps
.value
/ 1000000,
671 get_label
=lambda row
: row
.disk
.name
,
672 data
=result_table
.rows
677 def _print_per_netif_io(self
, result_table
, title
):
678 graph
= termgraph
.BarGraph(
679 title
='Network ' + title
+ ' Bytes',
680 label_header
='Interface',
681 get_value
=lambda row
: row
.size
.value
,
682 get_value_str
=format_utils
.format_size
,
683 get_label
=lambda row
: row
.netif
.name
,
684 data
=result_table
.rows
689 def _print_per_file_io(self
, result_table
, title
):
690 # FIXME add option to show FD owners
691 # FIXME why are read and write values the same?
692 graph
= termgraph
.BarGraph(
693 title
='Per-file I/O ' + title
,
695 get_value
=lambda row
: row
.size
.value
,
696 get_value_str
=format_utils
.format_size
,
697 get_label
=lambda row
: row
.path
.path
,
698 data
=result_table
.rows
703 def _print_usage(self
, usage_tables
):
704 self
._print
_per
_proc
_io
(usage_tables
.per_proc_read
, 'Read')
705 self
._print
_per
_proc
_io
(usage_tables
.per_proc_write
, 'Write')
706 self
._print
_per
_file
_io
(usage_tables
.per_file_read
, 'Read')
707 self
._print
_per
_file
_io
(usage_tables
.per_file_write
, 'Write')
708 self
._print
_per
_proc
_block
_io
(usage_tables
.per_proc_block_read
, 'Read')
709 self
._print
_per
_proc
_block
_io
(
710 usage_tables
.per_proc_block_write
, 'Write'
712 self
._print
_per
_disk
_sector
(usage_tables
.per_disk_sector
)
713 self
._print
_per
_disk
_request
(usage_tables
.per_disk_request
)
714 self
._print
_per
_disk
_rtps
(usage_tables
.per_disk_rtps
)
715 self
._print
_per
_netif
_io
(usage_tables
.per_netif_recv
, 'Received')
716 self
._print
_per
_netif
_io
(usage_tables
.per_netif_send
, 'Sent')
718 def _fill_freq_result_table(self
, duration_list
, result_table
):
719 if not duration_list
:
722 # The number of bins for the histogram
723 resolution
= self
._args
.freq_resolution
725 min_duration
= min(duration_list
)
726 max_duration
= max(duration_list
)
731 step
= (max_duration
- min_duration
) / resolution
739 for i
in range(resolution
):
740 buckets
.append(i
* step
)
743 for duration
in duration_list
:
745 index
= min(int((duration
- min_duration
) / step
), resolution
- 1)
748 for index
, value
in enumerate(values
):
749 result_table
.append_row(
750 latency_lower
=mi
.Duration
.from_us(index
* step
+ min_duration
),
751 latency_upper
=mi
.Duration
.from_us((index
+ 1) * step
+
753 count
=mi
.Integer(value
),
756 def _get_disk_freq_result_tables(self
, begin
, end
):
759 for disk
in self
._analysis
.disks
.values():
760 rq_durations
= [rq
.duration
for rq
in disk
.rq_list
if
761 self
._filter
_io
_request
(rq
)]
762 subtitle
= 'disk: {}'.format(disk
.disk_name
)
764 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
765 begin
, end
, subtitle
)
766 self
._fill
_freq
_result
_table
(rq_durations
, result_table
)
767 result_tables
.append(result_table
)
771 def _get_syscall_freq_result_tables(self
, begin
, end
):
773 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
776 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
779 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
782 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
784 self
._fill
_freq
_result
_table
([io_rq
.duration
for io_rq
in
785 self
._analysis
.open_io_requests
if
786 self
._filter
_io
_request
(io_rq
)],
788 self
._fill
_freq
_result
_table
([io_rq
.duration
for io_rq
in
789 self
._analysis
.read_io_requests
if
790 self
._filter
_io
_request
(io_rq
)],
792 self
._fill
_freq
_result
_table
([io_rq
.duration
for io_rq
in
793 self
._analysis
.write_io_requests
if
794 self
._filter
_io
_request
(io_rq
)],
796 self
._fill
_freq
_result
_table
([io_rq
.duration
for io_rq
in
797 self
._analysis
.sync_io_requests
if
798 self
._filter
_io
_request
(io_rq
)],
801 return [open_table
, read_table
, write_table
, sync_table
]
803 def _get_freq_result_tables(self
, begin
, end
):
804 syscall_tables
= self
._get
_syscall
_freq
_result
_tables
(begin
, end
)
805 disk_tables
= self
._get
_disk
_freq
_result
_tables
(begin
, end
)
807 return syscall_tables
+ disk_tables
809 def _print_one_freq(self
, result_table
):
810 graph
= termgraph
.FreqGraph(
811 data
=result_table
.rows
,
812 get_value
=lambda row
: row
.count
.value
,
813 get_lower_bound
=lambda row
: row
.latency_lower
.to_us(),
814 title
='{} {}'.format(result_table
.title
, result_table
.subtitle
),
820 def _print_freq(self
, freq_tables
):
821 for freq_table
in freq_tables
:
822 self
._print
_one
_freq
(freq_table
)
824 def _append_log_row(self
, io_rq
, result_table
):
825 if io_rq
.size
is None:
828 size
= mi
.Size(io_rq
.size
)
831 proc_stats
= self
._analysis
.tids
[tid
]
832 proc_name
= proc_stats
.comm
834 # TODO: handle fd_in/fd_out for RW type operations
840 parent_proc
= proc_stats
842 if parent_proc
.pid
is not None:
843 parent_proc
= self
._analysis
.tids
[parent_proc
.pid
]
845 fd_stats
= parent_proc
.get_fd(io_rq
.fd
, io_rq
.end_ts
)
847 if fd_stats
is not None:
848 path
= mi
.Path(fd_stats
.filename
)
852 result_table
.append_row(
853 time_range
=mi
.TimeRange(io_rq
.begin_ts
, io_rq
.end_ts
),
854 out_of_range
=mi
.Boolean(self
._is
_io
_rq
_out
_of
_range
(io_rq
)),
855 duration
=mi
.Duration(io_rq
.duration
),
856 syscall
=mi
.Syscall(io_rq
.syscall_name
),
858 process
=mi
.Process(proc_name
, tid
=tid
),
863 def _fill_log_result_table(self
, rq_list
, sort_key
, is_top
, result_table
):
869 for io_rq
in sorted(rq_list
, key
=operator
.attrgetter(sort_key
),
871 if is_top
and count
> self
._args
.limit
:
874 self
._append
_log
_row
(io_rq
, result_table
)
877 def _fill_log_result_table_from_io_requests(self
, io_requests
, sort_key
,
878 is_top
, result_table
):
879 io_requests
= [io_rq
for io_rq
in io_requests
if
880 self
._filter
_io
_request
(io_rq
)]
881 self
._fill
_log
_result
_table
(io_requests
, sort_key
, is_top
,
884 def _get_top_result_tables(self
, begin
, end
):
886 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_TOP
_SYSCALL
,
889 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_TOP
_SYSCALL
,
892 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_TOP
_SYSCALL
,
895 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_TOP
_SYSCALL
,
897 self
._fill
_log
_result
_table
_from
_io
_requests
(
898 self
._analysis
.open_io_requests
, 'duration', True, open_table
)
899 self
._fill
_log
_result
_table
_from
_io
_requests
(
900 self
._analysis
.read_io_requests
, 'duration', True, read_table
)
901 self
._fill
_log
_result
_table
_from
_io
_requests
(
902 self
._analysis
.write_io_requests
, 'duration', True, write_table
)
903 self
._fill
_log
_result
_table
_from
_io
_requests
(
904 self
._analysis
.sync_io_requests
, 'duration', True, sync_table
)
906 return [open_table
, read_table
, write_table
, sync_table
]
908 def _print_log_row(self
, row
):
909 fmt
= '{:<40} {:<16} {:>16} {:>11} {:<24} {:<8} {:<14}'
910 time_range_str
= format_utils
.format_time_range(
911 row
.time_range
.begin
,
913 self
._args
.multi_day
,
916 duration_str
= '%0.03f' % row
.duration
.to_us()
918 if type(row
.size
) is mi
.Empty
:
921 size
= format_utils
.format_size(row
.size
.value
)
923 tid
= row
.process
.tid
924 proc_name
= row
.process
.name
926 if type(row
.fd
) is mi
.Empty
:
929 if type(row
.path
) is mi
.Unknown
:
934 file_str
= '%s (fd=%s)' % (path
, row
.fd
.fd
)
936 if row
.out_of_range
.value
:
937 time_range_str
+= '*'
940 time_range_str
+= ' '
943 print(fmt
.format(time_range_str
, row
.syscall
.name
, duration_str
,
944 size
, proc_name
, tid
, file_str
))
946 def _print_log(self
, result_table
):
947 if not result_table
.rows
:
950 has_out_of_range_rq
= False
954 print(fmt
.format(result_table
.title
, result_table
.subtitle
))
955 header_fmt
= '{:<20} {:<20} {:<16} {:<23} {:<5} {:<24} {:<8} {:<14}'
956 print(header_fmt
.format(
957 'Begin', 'End', 'Name', 'Duration (usec)', 'Size', 'Proc', 'PID',
960 for row
in result_table
.rows
:
961 self
._print
_log
_row
(row
)
963 if not has_out_of_range_rq
and row
.out_of_range
.value
:
964 has_out_of_range_rq
= True
966 if has_out_of_range_rq
:
967 print('*: Syscalls started and/or completed outside of the '
970 def _print_top(self
, top_tables
):
971 for table
in top_tables
:
972 self
._print
_log
(table
)
974 def _get_log_result_table(self
, begin
, end
):
975 log_table
= self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_LOG
,
977 self
._fill
_log
_result
_table
_from
_io
_requests
(
978 self
._analysis
.io_requests
, 'begin_ts', False, log_table
)
982 def _append_latency_stats_row(self
, obj
, rq_durations
, result_table
):
983 rq_count
= len(rq_durations
)
984 total_duration
= sum(rq_durations
)
986 if len(rq_durations
) > 0:
987 min_duration
= min(rq_durations
)
988 max_duration
= max(rq_durations
)
996 stdev
= mi
.Duration(statistics
.stdev(rq_durations
))
999 avg
= total_duration
/ rq_count
1003 result_table
.append_row(
1005 count
=mi
.Integer(rq_count
),
1006 min_latency
=mi
.Duration(min_duration
),
1007 avg_latency
=mi
.Duration(avg
),
1008 max_latency
=mi
.Duration(max_duration
),
1009 stdev_latency
=stdev
,
1012 def _append_latency_stats_row_from_requests(self
, obj
, io_requests
,
1014 rq_durations
= [io_rq
.duration
for io_rq
in io_requests
if
1015 self
._filter
_io
_request
(io_rq
)]
1016 self
._append
_latency
_stats
_row
(obj
, rq_durations
, result_table
)
1018 def _get_syscall_latency_stats_result_table(self
, begin
, end
):
1019 result_table
= self
._mi
_create
_result
_table
(
1020 self
._MI
_TABLE
_CLASS
_SYSCALL
_LATENCY
_STATS
, begin
, end
)
1021 append_fn
= self
._append
_latency
_stats
_row
_from
_requests
1022 append_fn(mi
.String('Open'), self
._analysis
.open_io_requests
,
1024 append_fn(mi
.String('Read'), self
._analysis
.read_io_requests
,
1026 append_fn(mi
.String('Write'), self
._analysis
.write_io_requests
,
1028 append_fn(mi
.String('Sync'), self
._analysis
.sync_io_requests
,
1033 def _get_disk_latency_stats_result_table(self
, begin
, end
):
1034 if not self
._analysis
.disks
:
1037 result_table
= self
._mi
_create
_result
_table
(
1038 self
._MI
_TABLE
_CLASS
_PART
_LATENCY
_STATS
, begin
, end
)
1040 for disk
in self
._analysis
.disks
.values():
1042 rq_durations
= [rq
.duration
for rq
in disk
.rq_list
if
1043 self
._filter
_io
_request
(rq
)]
1044 disk
= mi
.Disk(disk
.disk_name
)
1045 self
._append
_latency
_stats
_row
(disk
, rq_durations
,
1050 def _get_latency_stats_result_tables(self
, begin
, end
):
1051 syscall_tbl
= self
._get
_syscall
_latency
_stats
_result
_table
(begin
, end
)
1052 disk_tbl
= self
._get
_disk
_latency
_stats
_result
_table
(begin
, end
)
1054 return syscall_tbl
, disk_tbl
1056 def _print_latency_stats_row(self
, row
):
1057 if type(row
.stdev_latency
) is mi
.Unknown
:
1060 stdev
= '%0.03f' % row
.stdev_latency
.to_us()
1062 avg
= '%0.03f' % row
.avg_latency
.to_us()
1063 min_duration
= '%0.03f' % row
.min_latency
.to_us()
1064 max_duration
= '%0.03f' % row
.max_latency
.to_us()
1066 print(IoAnalysisCommand
._LATENCY
_STATS
_FORMAT
.format(
1067 str(row
.obj
), row
.count
.value
, min_duration
,
1068 avg
, max_duration
, stdev
))
1070 def _print_syscall_latency_stats(self
, stats_table
):
1071 print('\nSyscalls latency statistics (usec):')
1072 print(IoAnalysisCommand
._LATENCY
_STATS
_FORMAT
.format(
1073 'Type', 'Count', 'Min', 'Average', 'Max', 'Stdev'))
1074 print(IoAnalysisCommand
._SECTION
_SEPARATOR
_STRING
)
1076 for row
in stats_table
.rows
:
1077 self
._print
_latency
_stats
_row
(row
)
1079 def _print_disk_latency_stats(self
, stats_table
):
1080 if not stats_table
.rows
:
1083 print('\nDisk latency statistics (usec):')
1084 print(IoAnalysisCommand
._LATENCY
_STATS
_FORMAT
.format(
1085 'Name', 'Count', 'Min', 'Average', 'Max', 'Stdev'))
1086 print(IoAnalysisCommand
._SECTION
_SEPARATOR
_STRING
)
1088 for row
in stats_table
.rows
:
1089 self
._print
_latency
_stats
_row
(row
)
1091 def _print_latency_stats(self
, syscall_latency_stats_table
,
1092 disk_latency_stats_table
):
1093 self
._print
_syscall
_latency
_stats
(syscall_latency_stats_table
)
1094 self
._print
_disk
_latency
_stats
(disk_latency_stats_table
)
1096 def _add_arguments(self
, ap
):
1097 Command
._add
_proc
_filter
_args
(ap
)
1098 Command
._add
_min
_max
_args
(ap
)
1099 Command
._add
_log
_args
(
1100 ap
, help='Output the I/O requests in chronological order')
1101 Command
._add
_top
_args
(
1102 ap
, help='Output the top I/O latencies by category')
1103 Command
._add
_stats
_args
(ap
, help='Output the I/O latency statistics')
1104 Command
._add
_freq
_args
(
1105 ap
, help='Output the I/O latency frequency distribution')
1106 ap
.add_argument('--usage', action
='store_true',
1107 help='Output the I/O usage')
1108 ap
.add_argument('--minsize', type=float,
1109 help='Filter out, I/O operations working with '
1110 'less that minsize bytes')
1111 ap
.add_argument('--maxsize', type=float,
1112 help='Filter out, I/O operations working with '
1113 'more that maxsize bytes')
1117 iocmd
= IoAnalysisCommand(mi_mode
=mi_mode
)
1121 def _runstats(mi_mode
):
1122 sys
.argv
.insert(1, '--stats')
1126 def _runlog(mi_mode
):
1127 sys
.argv
.insert(1, '--log')
1131 def _runfreq(mi_mode
):
1132 sys
.argv
.insert(1, '--freq')
1136 def _runlatencytop(mi_mode
):
1137 sys
.argv
.insert(1, '--top')
1141 def _runusage(mi_mode
):
1142 sys
.argv
.insert(1, '--usage')
1147 _runstats(mi_mode
=False)
1151 _runlog(mi_mode
=False)
1155 _runfreq(mi_mode
=False)
1158 def runlatencytop():
1159 _runlatencytop(mi_mode
=False)
1163 _runusage(mi_mode
=False)
1167 _runstats(mi_mode
=True)
1171 _runlog(mi_mode
=True)
1175 _runfreq(mi_mode
=True)
1178 def runlatencytop_mi():
1179 _runlatencytop(mi_mode
=True)
1183 _runusage(mi_mode
=True)