1 # The MIT License (MIT)
3 # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com>
4 # 2015 - Antoine Busque <abusque@efficios.com>
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 from . import termgraph
31 from ..core
import sched
32 from .command
import Command
33 from ..linuxautomaton
import common
36 _SchedStats
= collections
.namedtuple('_SchedStats', [
45 class SchedAnalysisCommand(Command
):
46 _DESC
= """The sched command."""
47 _ANALYSIS_CLASS
= sched
.SchedAnalysis
48 _MI_TITLE
= 'Scheduling latencies analysis'
50 'Scheduling latencies frequency distribution, statistics, top, and log'
51 _MI_TAGS
= [mi
.Tags
.SCHED
, mi
.Tags
.STATS
, mi
.Tags
.FREQ
, mi
.Tags
.TOP
,
53 _MI_TABLE_CLASS_LOG
= 'log'
54 _MI_TABLE_CLASS_TOP
= 'top'
55 _MI_TABLE_CLASS_TOTAL_STATS
= 'total_stats'
56 _MI_TABLE_CLASS_PER_TID_STATS
= 'per_tid_stats'
57 _MI_TABLE_CLASS_PER_PRIO_STATS
= 'per_prio_stats'
58 _MI_TABLE_CLASS_FREQ
= 'freq'
59 # _MI_TABLE_CLASS_SUMMARY = 'summary'
64 ('wakeup_ts', 'Wakeup timestamp', mi
.Timestamp
),
65 ('switch_ts', 'Switch timestamp', mi
.Timestamp
),
66 ('latency', 'Scheduling latency', mi
.Duration
),
67 ('prio', 'Priority', mi
.Integer
),
68 ('target_cpu', 'Target CPU', mi
.Integer
),
69 ('wakee_proc', 'Wakee process', mi
.Process
),
70 ('waker_proc', 'Waker process', mi
.Process
),
76 ('wakeup_ts', 'Wakeup timestamp', mi
.Timestamp
),
77 ('switch_ts', 'Switch timestamp', mi
.Timestamp
),
78 ('latency', 'Scheduling latency', mi
.Duration
),
79 ('prio', 'Priority', mi
.Integer
),
80 ('target_cpu', 'Target CPU', mi
.Integer
),
81 ('wakee_proc', 'Wakee process', mi
.Process
),
82 ('waker_proc', 'Waker process', mi
.Process
),
86 _MI_TABLE_CLASS_TOTAL_STATS
,
87 'Scheduling latency stats (total)', [
88 ('count', 'Scheduling count', mi
.Integer
, 'schedulings'),
89 ('min_latency', 'Minimum latency', mi
.Duration
),
90 ('avg_latency', 'Average latency', mi
.Duration
),
91 ('max_latency', 'Maximum latency', mi
.Duration
),
92 ('stdev_latency', 'Scheduling latency standard deviation',
97 _MI_TABLE_CLASS_PER_TID_STATS
,
98 'Scheduling latency stats (per-TID)', [
99 ('process', 'Wakee process', mi
.Process
),
100 ('count', 'Scheduling count', mi
.Integer
, 'schedulings'),
101 ('min_latency', 'Minimum latency', mi
.Duration
),
102 ('avg_latency', 'Average latency', mi
.Duration
),
103 ('max_latency', 'Maximum latency', mi
.Duration
),
104 ('stdev_latency', 'Scheduling latency standard deviation',
106 ('prio_list', 'Chronological priorities', mi
.String
),
110 _MI_TABLE_CLASS_PER_PRIO_STATS
,
111 'Scheduling latency stats (per-prio)', [
112 ('prio', 'Priority', mi
.Integer
),
113 ('count', 'Scheduling count', mi
.Integer
, 'schedulings'),
114 ('min_latency', 'Minimum latency', mi
.Duration
),
115 ('avg_latency', 'Average latency', mi
.Duration
),
116 ('max_latency', 'Maximum latency', mi
.Duration
),
117 ('stdev_latency', 'Scheduling latency standard deviation',
122 _MI_TABLE_CLASS_FREQ
,
123 'Scheduling latency frequency distribution', [
124 ('duration_lower', 'Duration (lower bound)', mi
.Duration
),
125 ('duration_upper', 'Duration (upper bound)', mi
.Duration
),
126 ('count', 'Scheduling count', mi
.Integer
, 'schedulings'),
131 def _analysis_tick(self
, begin_ns
, end_ns
):
134 total_stats_table
= None
135 per_tid_stats_table
= None
136 per_prio_stats_table
= None
137 total_freq_tables
= None
138 per_tid_freq_tables
= None
139 per_prio_freq_tables
= None
142 log_table
= self
._get
_log
_result
_table
(begin_ns
, end_ns
)
145 top_table
= self
._get
_top
_result
_table
(begin_ns
, end_ns
)
149 total_stats_table
= self
._get
_total
_stats
_result
_table
(
152 if self
._args
.per_tid
:
153 per_tid_stats_table
= self
._get
_per
_tid
_stats
_result
_table
(
156 if self
._args
.per_prio
:
157 per_prio_stats_table
= self
._get
_per
_prio
_stats
_result
_table
(
162 total_freq_tables
= self
._get
_total
_freq
_result
_tables
(
165 if self
._args
.per_tid
:
166 per_tid_freq_tables
= self
._get
_per
_tid
_freq
_result
_tables
(
169 if self
._args
.per_prio
:
170 per_prio_freq_tables
= self
._get
_per
_prio
_freq
_result
_tables
(
175 self
._mi
_append
_result
_table
(log_table
)
178 self
._mi
_append
_result
_table
(top_table
)
180 if total_stats_table
and total_stats_table
.rows
:
181 self
._mi
_append
_result
_table
(total_stats_table
)
183 if per_tid_stats_table
and per_tid_stats_table
.rows
:
184 self
._mi
_append
_result
_table
(per_tid_stats_table
)
186 if per_prio_stats_table
and per_prio_stats_table
.rows
:
187 self
._mi
_append
_result
_table
(per_prio_stats_table
)
189 if self
._args
.freq_series
:
190 if total_freq_tables
:
191 self
._mi
_append
_result
_tables
(total_freq_tables
)
193 if per_tid_freq_tables
:
194 per_tid_freq_tables
= [
195 self
._get
_per
_tid
_freq
_series
_table
(
199 self
._mi
_append
_result
_tables
(per_tid_freq_tables
)
201 if per_prio_freq_tables
:
202 per_prio_freq_tables
= [
203 self
._get
_per
_prio
_freq
_series
_table
(
204 per_prio_freq_tables
)
207 self
._mi
_append
_result
_tables
(per_prio_freq_tables
)
209 self
._print
_date
(begin_ns
, end_ns
)
212 if total_stats_table
:
213 self
._print
_total
_stats
(total_stats_table
)
214 if per_tid_stats_table
:
215 self
._print
_per
_tid
_stats
(per_tid_stats_table
)
216 if per_prio_stats_table
:
217 self
._print
_per
_prio
_stats
(per_prio_stats_table
)
220 if total_freq_tables
:
221 self
._print
_freq
(total_freq_tables
)
222 if per_tid_freq_tables
:
223 self
._print
_freq
(per_tid_freq_tables
)
224 if per_prio_freq_tables
:
225 self
._print
_freq
(per_prio_freq_tables
)
228 self
._print
_sched
_events
(log_table
)
231 self
._print
_sched
_events
(top_table
)
233 def _get_total_sched_lists_stats(self
):
234 total_list
= self
._analysis
.sched_list
235 stdev
= self
._compute
_sched
_latency
_stdev
(total_list
)
236 total_stats
= _SchedStats(
237 count
=self
._analysis
.count
,
238 min=self
._analysis
.min_latency
,
239 max=self
._analysis
.max_latency
,
241 total
=self
._analysis
.total_latency
244 return [total_list
], total_stats
246 def _get_tid_sched_lists_stats(self
):
250 for sched_event
in self
._analysis
.sched_list
:
251 tid
= sched_event
.wakee_proc
.tid
252 if tid
not in tid_sched_lists
:
253 tid_sched_lists
[tid
] = []
255 tid_sched_lists
[tid
].append(sched_event
)
257 for tid
in tid_sched_lists
:
258 sched_list
= tid_sched_lists
[tid
]
263 stdev
= self
._compute
_sched
_latency
_stdev
(sched_list
)
264 latencies
= [sched
.latency
for sched
in sched_list
]
265 count
= len(latencies
)
266 min_latency
= min(latencies
)
267 max_latency
= max(latencies
)
268 total_latency
= sum(latencies
)
270 tid_stats
[tid
] = _SchedStats(
278 return tid_sched_lists
, tid_stats
280 def _get_prio_sched_lists_stats(self
):
281 prio_sched_lists
= {}
284 for sched_event
in self
._analysis
.sched_list
:
285 if sched_event
.prio
not in prio_sched_lists
:
286 prio_sched_lists
[sched_event
.prio
] = []
288 prio_sched_lists
[sched_event
.prio
].append(sched_event
)
290 for prio
in prio_sched_lists
:
291 sched_list
= prio_sched_lists
[prio
]
296 stdev
= self
._compute
_sched
_latency
_stdev
(sched_list
)
297 latencies
= [sched
.latency
for sched
in sched_list
]
298 count
= len(latencies
)
299 min_latency
= min(latencies
)
300 max_latency
= max(latencies
)
301 total_latency
= sum(latencies
)
303 prio_stats
[prio
] = _SchedStats(
311 return prio_sched_lists
, prio_stats
313 def _get_log_result_table(self
, begin_ns
, end_ns
):
314 result_table
= self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_LOG
,
317 for sched_event
in self
._analysis
.sched_list
:
318 wakee_proc
= mi
.Process(sched_event
.wakee_proc
.comm
,
319 sched_event
.wakee_proc
.pid
,
320 sched_event
.wakee_proc
.tid
)
322 if sched_event
.waker_proc
:
323 waker_proc
= mi
.Process(sched_event
.waker_proc
.comm
,
324 sched_event
.waker_proc
.pid
,
325 sched_event
.waker_proc
.tid
)
327 waker_proc
= mi
.Empty()
329 result_table
.append_row(
330 wakeup_ts
=mi
.Timestamp(sched_event
.wakeup_ts
),
331 switch_ts
=mi
.Timestamp(sched_event
.switch_ts
),
332 latency
=mi
.Duration(sched_event
.latency
),
333 prio
=mi
.Integer(sched_event
.prio
),
334 target_cpu
=mi
.Integer(sched_event
.target_cpu
),
335 wakee_proc
=wakee_proc
,
336 waker_proc
=waker_proc
,
341 def _get_top_result_table(self
, begin_ns
, end_ns
):
342 result_table
= self
._mi
_create
_result
_table
(
343 self
._MI
_TABLE
_CLASS
_TOP
, begin_ns
, end_ns
)
345 top_events
= sorted(self
._analysis
.sched_list
,
346 key
=operator
.attrgetter('latency'),
348 top_events
= top_events
[:self
._args
.limit
]
350 for sched_event
in top_events
:
351 wakee_proc
= mi
.Process(sched_event
.wakee_proc
.comm
,
352 sched_event
.wakee_proc
.pid
,
353 sched_event
.wakee_proc
.tid
)
355 if sched_event
.waker_proc
:
356 waker_proc
= mi
.Process(sched_event
.waker_proc
.comm
,
357 sched_event
.waker_proc
.pid
,
358 sched_event
.waker_proc
.tid
)
360 waker_proc
= mi
.Empty()
362 result_table
.append_row(
363 wakeup_ts
=mi
.Timestamp(sched_event
.wakeup_ts
),
364 switch_ts
=mi
.Timestamp(sched_event
.switch_ts
),
365 latency
=mi
.Duration(sched_event
.latency
),
366 prio
=mi
.Integer(sched_event
.prio
),
367 target_cpu
=mi
.Integer(sched_event
.target_cpu
),
368 wakee_proc
=wakee_proc
,
369 waker_proc
=waker_proc
,
374 def _get_total_stats_result_table(self
, begin_ns
, end_ns
):
376 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_TOTAL
_STATS
,
379 stdev
= self
._compute
_sched
_latency
_stdev
(self
._analysis
.sched_list
)
380 if math
.isnan(stdev
):
383 stdev
= mi
.Duration(stdev
)
385 stats_table
.append_row(
386 count
=mi
.Integer(self
._analysis
.count
),
387 min_latency
=mi
.Duration(self
._analysis
.min_latency
),
388 avg_latency
=mi
.Duration(self
._analysis
.total_latency
/
389 self
._analysis
.count
),
390 max_latency
=mi
.Duration(self
._analysis
.max_latency
),
396 def _cleanup_prio_list(self
, prio_list
):
404 for p
in sorted(prios
.keys()):
406 count_str
= " (%s times)" % prios
[p
]
410 prio_str
= "[%s%s" % (p
, count_str
)
412 prio_str
= "%s, %s%s" % (prio_str
, p
, count_str
)
413 prio_str
= prio_str
+ "]"
416 def _get_per_tid_stats_result_table(self
, begin_ns
, end_ns
):
418 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_PER
_TID
_STATS
,
421 tid_stats_list
= sorted(list(self
._analysis
.tids
.values()),
422 key
=lambda proc
: proc
.comm
.lower())
424 for tid_stats
in tid_stats_list
:
425 if not tid_stats
.sched_list
:
428 stdev
= self
._compute
_sched
_latency
_stdev
(tid_stats
.sched_list
)
429 if math
.isnan(stdev
):
432 stdev
= mi
.Duration(stdev
)
434 prio_list
= self
._cleanup
_prio
_list
(tid_stats
.prio_list
)
436 stats_table
.append_row(
437 process
=mi
.Process(tid
=tid_stats
.tid
, name
=tid_stats
.comm
),
438 count
=mi
.Integer(tid_stats
.count
),
439 min_latency
=mi
.Duration(tid_stats
.min_latency
),
440 avg_latency
=mi
.Duration(tid_stats
.total_latency
/
442 max_latency
=mi
.Duration(tid_stats
.max_latency
),
444 prio_list
=mi
.String(prio_list
),
449 def _get_per_prio_stats_result_table(self
, begin_ns
, end_ns
):
451 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_PER
_PRIO
_STATS
,
454 _
, prio_stats
= self
._get
_prio
_sched
_lists
_stats
()
456 for prio
in sorted(prio_stats
):
457 stats
= prio_stats
[prio
]
460 if math
.isnan(stdev
):
463 stdev
= mi
.Duration(stdev
)
466 min_latency
= stats
.min
467 max_latency
= stats
.max
468 total_latency
= stats
.total
470 stats_table
.append_row(
471 prio
=mi
.Integer(prio
),
472 count
=mi
.Integer(count
),
473 min_latency
=mi
.Duration(min_latency
),
474 avg_latency
=mi
.Duration(total_latency
/ count
),
475 max_latency
=mi
.Duration(max_latency
),
481 def _get_per_tid_freq_series_table(self
, freq_tables
):
486 ('duration_lower', 'Duration (lower bound)', mi
.Duration
),
487 ('duration_upper', 'Duration (upper bound)', mi
.Duration
),
490 for index
, freq_table
in enumerate(freq_tables
):
491 column_infos
.append((
492 'tid{}'.format(index
),
498 title
= 'Scheduling latencies frequency distributions'
499 table_class
= mi
.TableClass(None, title
, column_infos
)
500 begin
= freq_tables
[0].timerange
.begin
501 end
= freq_tables
[0].timerange
.end
502 result_table
= mi
.ResultTable(table_class
, begin
, end
)
504 for row_index
, freq0_row
in enumerate(freq_tables
[0].rows
):
506 freq0_row
.duration_lower
,
507 freq0_row
.duration_upper
,
510 for freq_table
in freq_tables
:
511 freq_row
= freq_table
.rows
[row_index
]
512 row_tuple
.append(freq_row
.count
)
514 result_table
.append_row_tuple(tuple(row_tuple
))
518 def _get_per_prio_freq_series_table(self
, freq_tables
):
523 ('duration_lower', 'Duration (lower bound)', mi
.Duration
),
524 ('duration_upper', 'Duration (upper bound)', mi
.Duration
),
527 for index
, freq_table
in enumerate(freq_tables
):
528 column_infos
.append((
529 'prio{}'.format(index
),
535 title
= 'Scheduling latencies frequency distributions'
536 table_class
= mi
.TableClass(None, title
, column_infos
)
537 begin
= freq_tables
[0].timerange
.begin
538 end
= freq_tables
[0].timerange
.end
539 result_table
= mi
.ResultTable(table_class
, begin
, end
)
541 for row_index
, freq0_row
in enumerate(freq_tables
[0].rows
):
543 freq0_row
.duration_lower
,
544 freq0_row
.duration_upper
,
547 for freq_table
in freq_tables
:
548 freq_row
= freq_table
.rows
[row_index
]
549 row_tuple
.append(freq_row
.count
)
551 result_table
.append_row_tuple(tuple(row_tuple
))
555 def _fill_freq_result_table(self
, sched_list
, stats
, min_duration
,
556 max_duration
, step
, freq_table
):
557 # The number of bins for the histogram
558 resolution
= self
._args
.freq_resolution
560 if not self
._args
.freq_uniform
:
561 if self
._args
.min is not None:
562 min_duration
= self
._args
.min
564 min_duration
= stats
.min
566 if self
._args
.max is not None:
567 max_duration
= self
._args
.max
569 max_duration
= stats
.max
575 step
= (max_duration
- min_duration
) / resolution
583 for i
in range(resolution
):
584 buckets
.append(i
* step
)
587 for sched_event
in sched_list
:
588 duration
= sched_event
.latency
/ 1000
589 index
= int((duration
- min_duration
) / step
)
591 if index
>= resolution
:
592 # special case for max value: put in last bucket (includes
594 if duration
== max_duration
:
595 counts
[index
- 1] += 1
601 for index
, count
in enumerate(counts
):
602 lower_bound
= index
* step
+ min_duration
603 upper_bound
= (index
+ 1) * step
+ min_duration
604 freq_table
.append_row(
605 duration_lower
=mi
.Duration
.from_us(lower_bound
),
606 duration_upper
=mi
.Duration
.from_us(upper_bound
),
607 count
=mi
.Integer(count
),
610 def _get_total_freq_result_tables(self
, begin_ns
, end_ns
):
612 sched_lists
, sched_stats
= self
._get
_total
_sched
_lists
_stats
()
617 if self
._args
.freq_uniform
:
620 for sched_list
in sched_lists
:
621 latencies
+= [sched
.latency
for sched
in sched_list
]
623 min_duration
, max_duration
, step
= \
624 self
._get
_uniform
_freq
_values
(latencies
)
626 for sched_list
in sched_lists
:
628 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
630 self
._fill
_freq
_result
_table
(sched_list
, sched_stats
, min_duration
,
631 max_duration
, step
, freq_table
)
632 freq_tables
.append(freq_table
)
636 def _get_per_tid_freq_result_tables(self
, begin_ns
, end_ns
):
638 tid_sched_lists
, tid_stats
= self
._get
_tid
_sched
_lists
_stats
()
643 if self
._args
.freq_uniform
:
646 for sched_list
in tid_sched_lists
.values():
647 latencies
+= [sched
.latency
for sched
in sched_list
]
649 min_duration
, max_duration
, step
= \
650 self
._get
_uniform
_freq
_values
(latencies
)
652 for tid
in sorted(tid_sched_lists
):
653 sched_list
= tid_sched_lists
[tid
]
654 stats
= tid_stats
[tid
]
655 subtitle
= 'TID: {}'.format(tid
)
657 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
658 begin_ns
, end_ns
, subtitle
)
659 self
._fill
_freq
_result
_table
(sched_list
, stats
, min_duration
,
660 max_duration
, step
, freq_table
)
661 freq_tables
.append(freq_table
)
665 def _get_per_prio_freq_result_tables(self
, begin_ns
, end_ns
):
667 prio_sched_lists
, prio_stats
= self
._get
_prio
_sched
_lists
_stats
()
672 if self
._args
.freq_uniform
:
675 for sched_list
in prio_sched_lists
.values():
676 latencies
+= [sched
.latency
for sched
in sched_list
]
678 min_duration
, max_duration
, step
= \
679 self
._get
_uniform
_freq
_values
(latencies
)
681 for prio
in sorted(prio_sched_lists
):
682 sched_list
= prio_sched_lists
[prio
]
683 stats
= prio_stats
[prio
]
684 subtitle
= 'Priority: {}'.format(prio
)
686 self
._mi
_create
_result
_table
(self
._MI
_TABLE
_CLASS
_FREQ
,
687 begin_ns
, end_ns
, subtitle
)
688 self
._fill
_freq
_result
_table
(sched_list
, stats
, min_duration
,
689 max_duration
, step
, freq_table
)
690 freq_tables
.append(freq_table
)
694 def _compute_sched_latency_stdev(self
, sched_events
):
695 if len(sched_events
) < 2:
699 for sched_event
in sched_events
:
700 sched_latencies
.append(sched_event
.latency
)
702 return statistics
.stdev(sched_latencies
)
704 def _ns_to_hour_nsec(self
, ts
):
705 return common
.ns_to_hour_nsec(ts
, self
._args
.multi_day
, self
._args
.gmt
)
707 def _print_sched_events(self
, result_table
):
708 fmt
= '[{:<18}, {:<18}] {:>15} {:>10} {:>3} {:<25} {:<25}'
709 title_fmt
= '{:<20} {:<19} {:>15} {:>10} {:>3} {:<25} {:<25}'
711 print(result_table
.title
)
712 print(title_fmt
.format('Wakeup', 'Switch', 'Latency (us)', 'Priority',
713 'CPU', 'Wakee', 'Waker'))
714 for row
in result_table
.rows
:
715 wakeup_ts
= row
.wakeup_ts
.value
716 switch_ts
= row
.switch_ts
.value
717 latency
= row
.latency
.value
718 prio
= row
.prio
.value
719 target_cpu
= row
.target_cpu
.value
720 wakee_proc
= row
.wakee_proc
721 waker_proc
= row
.waker_proc
723 wakee_str
= '%s (%d)' % (wakee_proc
.name
, wakee_proc
.tid
)
724 if isinstance(waker_proc
, mi
.Empty
):
725 waker_str
= 'Unknown (N/A)'
727 waker_str
= '%s (%d)' % (waker_proc
.name
, waker_proc
.tid
)
729 print(fmt
.format(self
._ns
_to
_hour
_nsec
(wakeup_ts
),
730 self
._ns
_to
_hour
_nsec
(switch_ts
),
731 '%0.03f' % (latency
/ 1000), prio
,
732 target_cpu
, wakee_str
, waker_str
))
734 def _print_total_stats(self
, stats_table
):
735 row_format
= '{:<12} {:<12} {:<12} {:<12} {:<12}'
736 header
= row_format
.format(
737 'Count', 'Min', 'Avg', 'Max', 'Stdev'
742 print(stats_table
.title
+ ' (us)')
744 for row
in stats_table
.rows
:
745 if type(row
.stdev_latency
) is mi
.Unknown
:
748 stdev_str
= '%0.03f' % row
.stdev_latency
.to_us()
750 row_str
= row_format
.format(
751 '%d' % row
.count
.value
,
752 '%0.03f' % row
.min_latency
.to_us(),
753 '%0.03f' % row
.avg_latency
.to_us(),
754 '%0.03f' % row
.max_latency
.to_us(),
760 def _print_per_tid_stats(self
, stats_table
):
761 row_format
= '{:<25} {:>8} {:>12} {:>12} {:>12} {:>12} {}'
762 header
= row_format
.format(
763 'Process', 'Count', 'Min', 'Avg', 'Max', 'Stdev', 'Priorities'
768 print(stats_table
.title
+ ' (us)')
770 for row
in stats_table
.rows
:
771 if type(row
.stdev_latency
) is mi
.Unknown
:
774 stdev_str
= '%0.03f' % row
.stdev_latency
.to_us()
777 proc_str
= '%s (%d)' % (proc
.name
, proc
.tid
)
779 row_str
= row_format
.format(
781 '%d' % row
.count
.value
,
782 '%0.03f' % row
.min_latency
.to_us(),
783 '%0.03f' % row
.avg_latency
.to_us(),
784 '%0.03f' % row
.max_latency
.to_us(),
786 '%s' % row
.prio_list
.value
,
791 def _print_per_prio_stats(self
, stats_table
):
792 row_format
= '{:>4} {:>8} {:>12} {:>12} {:>12} {:>12}'
793 header
= row_format
.format(
794 'Prio', 'Count', 'Min', 'Avg', 'Max', 'Stdev'
799 print(stats_table
.title
+ ' (us)')
801 for row
in stats_table
.rows
:
802 if type(row
.stdev_latency
) is mi
.Unknown
:
805 stdev_str
= '%0.03f' % row
.stdev_latency
.to_us()
807 row_str
= row_format
.format(
808 '%d' % row
.prio
.value
,
809 '%d' % row
.count
.value
,
810 '%0.03f' % row
.min_latency
.to_us(),
811 '%0.03f' % row
.avg_latency
.to_us(),
812 '%0.03f' % row
.max_latency
.to_us(),
818 def _print_frequency_distribution(self
, freq_table
):
819 title_fmt
= 'Scheduling latency frequency distribution - {}'
821 graph
= termgraph
.FreqGraph(
822 data
=freq_table
.rows
,
823 get_value
=lambda row
: row
.count
.value
,
824 get_lower_bound
=lambda row
: row
.duration_lower
.to_us(),
825 title
=title_fmt
.format(freq_table
.subtitle
),
831 def _print_freq(self
, freq_tables
):
832 for freq_table
in freq_tables
:
833 self
._print
_frequency
_distribution
(freq_table
)
835 def _validate_transform_args(self
, args
):
836 # If neither --total nor --per-prio are specified, default
838 if not (args
.total
or args
.per_prio
):
841 def _add_arguments(self
, ap
):
842 Command
._add
_min
_max
_args
(ap
)
843 Command
._add
_proc
_filter
_args
(ap
)
844 Command
._add
_freq
_args
(
845 ap
, help='Output the frequency distribution of sched switch '
847 Command
._add
_top
_args
(ap
, help='Output the top sched switch latencies')
848 Command
._add
_log
_args
(
849 ap
, help='Output the sched switches in chronological order')
850 Command
._add
_stats
_args
(ap
, help='Output sched switch statistics')
851 ap
.add_argument('--total', action
='store_true',
852 help='Group all results (applies to stats and freq)')
853 ap
.add_argument('--per-tid', action
='store_true',
854 help='Group results per-TID (applies to stats and '
856 ap
.add_argument('--per-prio', action
='store_true',
857 help='Group results per-prio (applies to stats and '
862 schedcmd
= SchedAnalysisCommand(mi_mode
=mi_mode
)
866 def _runstats(mi_mode
):
867 sys
.argv
.insert(1, '--stats')
871 def _runlog(mi_mode
):
872 sys
.argv
.insert(1, '--log')
876 def _runtop(mi_mode
):
877 sys
.argv
.insert(1, '--top')
881 def _runfreq(mi_mode
):
882 sys
.argv
.insert(1, '--freq')
887 _runstats(mi_mode
=False)
891 _runlog(mi_mode
=False)
895 _runtop(mi_mode
=False)
899 _runfreq(mi_mode
=False)
903 _runstats(mi_mode
=True)
907 _runlog(mi_mode
=True)
911 _runtop(mi_mode
=True)
915 _runfreq(mi_mode
=True)