History

perf script: Introduce task analyzer python script

Introduce a new 'perf script' to analyze task scheduling behavior. During the task analysis, some data is always needed - which goes beyond the simple time of switching on and off a task (process/thread). This concerns for example the runtime of a process or the frequency with which the process was called. This script serves to simplify this recurring analyze process. It immediately provides the user with helpful task characteristic information about the tasks runtimes. Usage: Recorded can be in two ways: $ perf script record tasks-analyzer -- sleep 10 $ perf record -e sched:sched_switch -a -- sleep 10 The script can parse all perf.data files, most important: sched:sched_switch events are mandatory, other events will be ignored. Most simple report use case is to just call the script without arguments: $ perf script report tasks-analyzer Switched-In Switched-Out CPU PID TID Comm Runtime Time Out-In 15576.658891407 15576.659156086 4 2412 2428 gdbus 265 1949 15576.659111320 15576.659455410 0 2412 2412 gnome-shell 344 2267 15576.659491326 15576.659506173 2 74 74 kworker/2:1 15 13145 15576.659506173 15576.659825748 2 2858 2858 gnome-terminal- 320 63263 15576.659871270 15576.659902872 6 20932 20932 kworker/u16:0 32 2314582 15576.659909951 15576.659945501 3 27264 27264 sh 36 -1 15576.659853285 15576.659971052 7 27265 27265 perf 118 5050741 [...] What is not shown here are the ASCII color sequences. For example, if the task consists of only one thread, the TID is grayed out. Runtime is the time the task was running on the CPU, Time Out-In is the time between the process being scheduled *out* and scheduled back *in*. So the last time span between two executions. If -1 is printed, then the task simply ran the first time in the measurements - a Out-In delta could not be calculated. In addition to the chronological representation, there is a summary on task level. This output can be additionally switched on via the --summary option and provides information such as max, min & average runtime per process. The maximum runtime is often important for debugging. The call looks like this: $ perf script report tasks-analyzer --summary Summary Task Information Runtime Information PID TID Comm Runs Accumulated Mean Median Min Max Max At 14 14 ksoftirqd/0 13 334 26 15 9 127 15571.621211956 15 15 rcu_preempt 133 1778 13 13 2 33 15572.581176024 16 16 migration/0 3 49 16 13 12 24 15571.608915425 20 20 migration/1 3 34 11 13 8 13 15571.639101555 25 25 migration/2 3 32 11 12 9 12 15575.639239896 [...] Besides these two options, there are a number of other options that change the output and behavior. This can be queried via --help. Options worth mentioning include: - filter-tasks - filter out unneeded tasks, --filter-task 1337,/sbin/init - highlight-tasks - more pleasant focusing, --highlight-tasks 1:red,mutt:yellow - extended-times - show combinations of elapsed times between schedule in/schedule out - summary-extended - summary with additional information, like maximum delta time statistics - rename-comms-by-tids - handy for inexpressive processnames like python, --rename 1337:my-python-app - ms - show timestamps in milliseconds, nanoseconds is also possible (--ns) - time-limit - limit the analyzer to a time range, --time-limit 15576.0:15576.1 Script is tested and prime time ready for python2 & python3: - make PYTHON=python3 prefix=/usr/local install - make PYTHON=python2 prefix=/usr/local install

Hagen Paul Pfeifer committed 3 years ago

Commit e76aff01 parent 55c1de93 files · +843 −0Browse files

Showing the changes to tools/perf/scripts/python (3 of 3 files). Show all

Unified · Split

3 files
  1. tools/perf/scripts/python/bin/task-analyzer-record +2 −0
  2. tools/perf/scripts/python/bin/task-analyzer-report +3 −0
  3. tools/perf/scripts/python/task-analyzer.py +838 −0
tools/perf/scripts/python/bin/task-analyzer-record+2−0
@@ -0,0 +1,2 @@
1#!/bin/bash
2perf record -e sched:sched_switch -e sched:sched_migrate_task "$@"
tools/perf/scripts/python/bin/task-analyzer-report+3−0
@@ -0,0 +1,3 @@
1#!/bin/bash
2# description: analyze timings of tasks
3perf script -s "$PERF_EXEC_PATH"/scripts/python/task-analyzer.py -- "$@"
tools/perf/scripts/python/task-analyzer.py+838−0
@@ -0,0 +1,838 @@
1# task-analyzer.py - comprehensive perf tasks analysis
2# SPDX-License-Identifier: GPL-2.0
3# Copyright (c) 2022, Hagen Paul Pfeifer <hagen@jauu.net>
4# Licensed under the terms of the GNU GPL License version 2
5#
6# Usage:
7#
8# perf record -e sched:sched_switch -a -- sleep 10
9# perf script report task-analyzer
10#
11
12from __future__ import print_function
13import sys
14import os
15import string
16import argparse
17import decimal
18
19
20sys.path.append(
21 os.environ["PERF_EXEC_PATH"] + "/scripts/python/Perf-Trace-Util/lib/Perf/Trace"
22)
23from perf_trace_context import *
24from Core import *
25
26# Definition of possible ASCII color codes
27_COLORS = {
28 "grey": "\033[90m",
29 "red": "\033[91m",
30 "green": "\033[92m",
31 "yellow": "\033[93m",
32 "blue": "\033[94m",
33 "violet": "\033[95m",
34 "reset": "\033[0m",
35}
36
37# Columns will have a static size to align everything properly
38# Support of 116 days of active update with nano precision
39LEN_SWITCHED_IN = len("9999999.999999999") # 17
40LEN_SWITCHED_OUT = len("9999999.999999999") # 17
41LEN_CPU = len("000")
42LEN_PID = len("maxvalue") # 8
43LEN_TID = len("maxvalue") # 8
44LEN_COMM = len("max-comms-length") # 16
45LEN_RUNTIME = len("999999.999") # 10
46# Support of 3.45 hours of timespans
47LEN_OUT_IN = len("99999999999.999") # 15
48LEN_OUT_OUT = len("99999999999.999") # 15
49LEN_IN_IN = len("99999999999.999") # 15
50LEN_IN_OUT = len("99999999999.999") # 15
51
52
53# py2/py3 compatibility layer, see PEP469
54try:
55 dict.iteritems
56except AttributeError:
57 # py3
58 def itervalues(d):
59 return iter(d.values())
60
61 def iteritems(d):
62 return iter(d.items())
63
64else:
65 # py2
66 def itervalues(d):
67 return d.itervalues()
68
69 def iteritems(d):
70 return d.iteritems()
71
72
73def _check_color():
74 global _COLORS
75 """user enforced no-color or if stdout is no tty we disable colors"""
76 if sys.stdout.isatty() and args.stdio_color != "never":
77 return
78 _COLORS = {
79 "grey": "",
80 "red": "",
81 "green": "",
82 "yellow": "",
83 "blue": "",
84 "violet": "",
85 "reset": "",
86 }
87
88
89def _parse_args():
90 global args
91 parser = argparse.ArgumentParser(description="Analyze tasks behavior")
92 parser.add_argument(
93 "--time-limit",
94 default=[],
95 help=
96 "print tasks only in time[s] window e.g"
97 " --time-limit 123.111:789.222(print all between 123.111 and 789.222)"
98 " --time-limit 123: (print all from 123)"
99 " --time-limit :456 (print all until incl. 456)",
100 )
101 parser.add_argument(
102 "--summary", action="store_true", help="print addtional runtime information"
103 )
104 parser.add_argument(
105 "--summary-only", action="store_true", help="print only summary without traces"
106 )
107 parser.add_argument(
108 "--summary-extended",
109 action="store_true",
110 help="print the summary with additional information of max inter task times"
111 " relative to the prev task",
112 )
113 parser.add_argument(
114 "--ns", action="store_true", help="show timestamps in nanoseconds"
115 )
116 parser.add_argument(
117 "--ms", action="store_true", help="show timestamps in miliseconds"
118 )
119 parser.add_argument(
120 "--extended-times",
121 action="store_true",
122 help="Show the elapsed times between schedule in/schedule out"
123 " of this task and the schedule in/schedule out of previous occurrence"
124 " of the same task",
125 )
126 parser.add_argument(
127 "--filter-tasks",
128 default=[],
129 help="filter out unneeded tasks by tid, pid or processname."
130 " E.g --filter-task 1337,/sbin/init ",
131 )
132 parser.add_argument(
133 "--limit-to-tasks",
134 default=[],
135 help="limit output to selected task by tid, pid, processname."
136 " E.g --limit-to-tasks 1337,/sbin/init",
137 )
138 parser.add_argument(
139 "--highlight-tasks",
140 default="",
141 help="colorize special tasks by their pid/tid/comm."
142 " E.g. --highlight-tasks 1:red,mutt:yellow"
143 " Colors available: red,grey,yellow,blue,violet,green",
144 )
145 parser.add_argument(
146 "--rename-comms-by-tids",
147 default="",
148 help="rename task names by using tid (<tid>:<newname>,<tid>:<newname>)"
149 " This option is handy for inexpressive processnames like python interpreted"
150 " process. E.g --rename 1337:my-python-app",
151 )
152 parser.add_argument(
153 "--stdio-color",
154 default="auto",
155 choices=["always", "never", "auto"],
156 help="always, never or auto, allowing configuring color output"
157 " via the command line",
158 )
159 args = parser.parse_args()
160 args.tid_renames = dict()
161
162 _argument_filter_sanity_check()
163 _argument_prepare_check()
164
165
166def time_uniter(unit):
167 picker = {
168 "s": 1,
169 "ms": 1e3,
170 "us": 1e6,
171 "ns": 1e9,
172 }
173 return picker[unit]
174
175
176def _init_db():
177 global db
178 db = dict()
179 db["running"] = dict()
180 db["cpu"] = dict()
181 db["tid"] = dict()
182 db["global"] = []
183 if args.summary or args.summary_extended or args.summary_only:
184 db["task_info"] = dict()
185 db["runtime_info"] = dict()
186 # min values for summary depending on the header
187 db["task_info"]["pid"] = len("PID")
188 db["task_info"]["tid"] = len("TID")
189 db["task_info"]["comm"] = len("Comm")
190 db["runtime_info"]["runs"] = len("Runs")
191 db["runtime_info"]["acc"] = len("Accumulated")
192 db["runtime_info"]["max"] = len("Max")
193 db["runtime_info"]["max_at"] = len("Max At")
194 db["runtime_info"]["min"] = len("Min")
195 db["runtime_info"]["mean"] = len("Mean")
196 db["runtime_info"]["median"] = len("Median")
197 if args.summary_extended:
198 db["inter_times"] = dict()
199 db["inter_times"]["out_in"] = len("Out-In")
200 db["inter_times"]["inter_at"] = len("At")
201 db["inter_times"]["out_out"] = len("Out-Out")
202 db["inter_times"]["in_in"] = len("In-In")
203 db["inter_times"]["in_out"] = len("In-Out")
204
205
206def _median(numbers):
207 """phython3 hat statistics module - we have nothing"""
208 n = len(numbers)
209 index = n // 2
210 if n % 2:
211 return sorted(numbers)[index]
212 return sum(sorted(numbers)[index - 1 : index + 1]) / 2
213
214
215def _mean(numbers):
216 return sum(numbers) / len(numbers)
217
218
219class Timespans(object):
220 """
221 The elapsed time between two occurrences of the same task is being tracked with the
222 help of this class. There are 4 of those Timespans Out-Out, In-Out, Out-In and
223 In-In.
224 The first half of the name signals the first time point of the
225 first task. The second half of the name represents the second
226 timepoint of the second task.
227 """
228
229 def __init__(self):
230 self._last_start = None
231 self._last_finish = None
232 self.out_out = -1
233 self.in_out = -1
234 self.out_in = -1
235 self.in_in = -1
236 if args.summary_extended:
237 self._time_in = -1
238 self.max_out_in = -1
239 self.max_at = -1
240 self.max_in_out = -1
241 self.max_in_in = -1
242 self.max_out_out = -1
243
244 def feed(self, task):
245 """
246 Called for every recorded trace event to find process pair and calculate the
247 task timespans. Chronological ordering, feed does not do reordering
248 """
249 if not self._last_finish:
250 self._last_start = task.time_in(time_unit)
251 self._last_finish = task.time_out(time_unit)
252 return
253 self._time_in = task.time_in()
254 time_in = task.time_in(time_unit)
255 time_out = task.time_out(time_unit)
256 self.in_in = time_in - self._last_start
257 self.out_in = time_in - self._last_finish
258 self.in_out = time_out - self._last_start
259 self.out_out = time_out - self._last_finish
260 if args.summary_extended:
261 self._update_max_entries()
262 self._last_finish = task.time_out(time_unit)
263 self._last_start = task.time_in(time_unit)
264
265 def _update_max_entries(self):
266 if self.in_in > self.max_in_in:
267 self.max_in_in = self.in_in
268 if self.out_out > self.max_out_out:
269 self.max_out_out = self.out_out
270 if self.in_out > self.max_in_out:
271 self.max_in_out = self.in_out
272 if self.out_in > self.max_out_in:
273 self.max_out_in = self.out_in
274 self.max_at = self._time_in
275
276
277
278
279class Summary(object):
280 """
281 Primary instance for calculating the summary output. Processes the whole trace to
282 find and memorize relevant data such as mean, max et cetera. This instance handles
283 dynamic alignment aspects for summary output.
284 """
285
286 def __init__(self):
287 self._body = []
288
289 class AlignmentHelper:
290 """
291 Used to calculated the alignment for the output of the summary.
292 """
293 def __init__(self, pid, tid, comm, runs, acc, mean,
294 median, min, max, max_at):
295 self.pid = pid
296 self.tid = tid
297 self.comm = comm
298 self.runs = runs
299 self.acc = acc
300 self.mean = mean
301 self.median = median
302 self.min = min
303 self.max = max
304 self.max_at = max_at
305 if args.summary_extended:
306 self.out_in = None
307 self.inter_at = None
308 self.out_out = None
309 self.in_in = None
310 self.in_out = None
311
312 def _print_header(self):
313 '''
314 Output is trimmed in _format_stats thus additional adjustment in the header
315 is needed, depending on the choice of timeunit. The adjustment corresponds
316 to the amount of column titles being adjusted in _column_titles.
317 '''
318 decimal_precision = 6 if not args.ns else 9
319 fmt = " {{:^{}}}".format(sum(db["task_info"].values()))
320 fmt += " {{:^{}}}".format(
321 sum(db["runtime_info"].values()) - 2 * decimal_precision
322 )
323 _header = ("Task Information", "Runtime Information")
324
325 if args.summary_extended:
326 fmt += " {{:^{}}}".format(
327 sum(db["inter_times"].values()) - 4 * decimal_precision
328 )
329 _header += ("Max Inter Task Times",)
330 print(fmt.format(*_header))
331
332 def _column_titles(self):
333 """
334 Cells are being processed and displayed in different way so an alignment adjust
335 is implemented depeding on the choice of the timeunit. The positions of the max
336 values are being displayed in grey. Thus in their format two additional {},
337 are placed for color set and reset.
338 """
339 decimal_precision, time_precision = _prepare_fmt_precision()
340 fmt = " {{:>{}}}".format(db["task_info"]["pid"])
341 fmt += " {{:>{}}}".format(db["task_info"]["tid"])
342 fmt += " {{:>{}}}".format(db["task_info"]["comm"])
343 fmt += " {{:>{}}}".format(db["runtime_info"]["runs"])
344 fmt += " {{:>{}}}".format(db["runtime_info"]["acc"])
345 fmt += " {{:>{}}}".format(db["runtime_info"]["mean"])
346 fmt += " {{:>{}}}".format(db["runtime_info"]["median"])
347 fmt += " {{:>{}}}".format(db["runtime_info"]["min"] - decimal_precision)
348 fmt += " {{:>{}}}".format(db["runtime_info"]["max"] - decimal_precision)
349 fmt += " {{}}{{:>{}}}{{}}".format(db["runtime_info"]["max_at"] - time_precision)
350
351 column_titles = ("PID", "TID", "Comm")
352 column_titles += ("Runs", "Accumulated", "Mean", "Median", "Min", "Max")
353 column_titles += (_COLORS["grey"], "At", _COLORS["reset"])
354
355 if args.summary_extended:
356 fmt += " {{:>{}}}".format(db["inter_times"]["out_in"] - decimal_precision)
357 fmt += " {{}}{{:>{}}}{{}}".format(
358 db["inter_times"]["inter_at"] - time_precision
359 )
360 fmt += " {{:>{}}}".format(db["inter_times"]["out_out"] - decimal_precision)
361 fmt += " {{:>{}}}".format(db["inter_times"]["in_in"] - decimal_precision)
362 fmt += " {{:>{}}}".format(db["inter_times"]["in_out"] - decimal_precision)
363
364 column_titles += ("Out-In", _COLORS["grey"], "Max At", _COLORS["reset"],
365 "Out-Out", "In-In", "In-Out")
366 print(fmt.format(*column_titles))
367
368 def _task_stats(self):
369 """calculates the stats of every task and constructs the printable summary"""
370 for tid in sorted(db["tid"]):
371 color_one_sample = _COLORS["grey"]
372 color_reset = _COLORS["reset"]
373 no_executed = 0
374 runtimes = []
375 time_in = []
376 timespans = Timespans()
377 for task in db["tid"][tid]:
378 pid = task.pid
379 comm = task.comm
380 no_executed += 1
381 runtimes.append(task.runtime(time_unit))
382 time_in.append(task.time_in())
383 timespans.feed(task)
384 if len(runtimes) > 1:
385 color_one_sample = ""
386 color_reset = ""
387 time_max = max(runtimes)
388 time_min = min(runtimes)
389 max_at = time_in[runtimes.index(max(runtimes))]
390
391 # The size of the decimal after sum,mean and median varies, thus we cut
392 # the decimal number, by rounding it. It has no impact on the output,
393 # because we have a precision of the decimal points at the output.
394 time_sum = round(sum(runtimes), 3)
395 time_mean = round(_mean(runtimes), 3)
396 time_median = round(_median(runtimes), 3)
397
398 align_helper = self.AlignmentHelper(pid, tid, comm, no_executed, time_sum,
399 time_mean, time_median, time_min, time_max, max_at)
400 self._body.append([pid, tid, comm, no_executed, time_sum, color_one_sample,
401 time_mean, time_median, time_min, time_max,
402 _COLORS["grey"], max_at, _COLORS["reset"], color_reset])
403 if args.summary_extended:
404 self._body[-1].extend([timespans.max_out_in,
405 _COLORS["grey"], timespans.max_at,
406 _COLORS["reset"], timespans.max_out_out,
407 timespans.max_in_in,
408 timespans.max_in_out])
409 align_helper.out_in = timespans.max_out_in
410 align_helper.inter_at = timespans.max_at
411 align_helper.out_out = timespans.max_out_out
412 align_helper.in_in = timespans.max_in_in
413 align_helper.in_out = timespans.max_in_out
414 self._calc_alignments_summary(align_helper)
415
416 def _format_stats(self):
417 decimal_precision, time_precision = _prepare_fmt_precision()
418 fmt = " {{:>{}d}}".format(db["task_info"]["pid"])
419 fmt += " {{:>{}d}}".format(db["task_info"]["tid"])
420 fmt += " {{:>{}}}".format(db["task_info"]["comm"])
421 fmt += " {{:>{}d}}".format(db["runtime_info"]["runs"])
422 fmt += " {{:>{}.{}f}}".format(db["runtime_info"]["acc"], time_precision)
423 fmt += " {{}}{{:>{}.{}f}}".format(db["runtime_info"]["mean"], time_precision)
424 fmt += " {{:>{}.{}f}}".format(db["runtime_info"]["median"], time_precision)
425 fmt += " {{:>{}.{}f}}".format(
426 db["runtime_info"]["min"] - decimal_precision, time_precision
427 )
428 fmt += " {{:>{}.{}f}}".format(
429 db["runtime_info"]["max"] - decimal_precision, time_precision
430 )
431 fmt += " {{}}{{:>{}.{}f}}{{}}{{}}".format(
432 db["runtime_info"]["max_at"] - time_precision, decimal_precision
433 )
434 if args.summary_extended:
435 fmt += " {{:>{}.{}f}}".format(
436 db["inter_times"]["out_in"] - decimal_precision, time_precision
437 )
438 fmt += " {{}}{{:>{}.{}f}}{{}}".format(
439 db["inter_times"]["inter_at"] - time_precision, decimal_precision
440 )
441 fmt += " {{:>{}.{}f}}".format(
442 db["inter_times"]["out_out"] - decimal_precision, time_precision
443 )
444 fmt += " {{:>{}.{}f}}".format(
445 db["inter_times"]["in_in"] - decimal_precision, time_precision
446 )
447 fmt += " {{:>{}.{}f}}".format(
448 db["inter_times"]["in_out"] - decimal_precision, time_precision
449 )
450 return fmt
451
452
453 def _calc_alignments_summary(self, align_helper):
454 # Length is being cut in 3 groups so that further addition is easier to handle.
455 # The length of every argument from the alignment helper is being checked if it
456 # is longer than the longest until now. In that case the length is being saved.
457 for key in db["task_info"]:
458 if len(str(getattr(align_helper, key))) > db["task_info"][key]:
459 db["task_info"][key] = len(str(getattr(align_helper, key)))
460 for key in db["runtime_info"]:
461 if len(str(getattr(align_helper, key))) > db["runtime_info"][key]:
462 db["runtime_info"][key] = len(str(getattr(align_helper, key)))
463 if args.summary_extended:
464 for key in db["inter_times"]:
465 if len(str(getattr(align_helper, key))) > db["inter_times"][key]:
466 db["inter_times"][key] = len(str(getattr(align_helper, key)))
467
468
469 def print(self):
470 print("\nSummary")
471 self._task_stats()
472 self._print_header()
473 self._column_titles()
474 fmt = self._format_stats()
475 for i in range(len(self._body)):
476 print(fmt.format(*tuple(self._body[i])))
477
478
479
480class Task(object):
481 """ The class is used to handle the information of a given task."""
482
483 def __init__(self, id, tid, cpu, comm):
484 self.id = id
485 self.tid = tid
486 self.cpu = cpu
487 self.comm = comm
488 self.pid = None
489 self._time_in = None
490 self._time_out = None
491
492 def schedule_in_at(self, time):
493 """set the time where the task was scheduled in"""
494 self._time_in = time
495
496 def schedule_out_at(self, time):
497 """set the time where the task was scheduled out"""
498 self._time_out = time
499
500 def time_out(self, unit="s"):
501 """return time where a given task was scheduled out"""
502 factor = time_uniter(unit)
503 return self._time_out * decimal.Decimal(factor)
504
505 def time_in(self, unit="s"):
506 """return time where a given task was scheduled in"""
507 factor = time_uniter(unit)
508 return self._time_in * decimal.Decimal(factor)
509
510 def runtime(self, unit="us"):
511 factor = time_uniter(unit)
512 return (self._time_out - self._time_in) * decimal.Decimal(factor)
513
514 def update_pid(self, pid):
515 self.pid = pid
516
517
518def _task_id(pid, cpu):
519 """returns a "unique-enough" identifier, please do not change"""
520 return "{}-{}".format(pid, cpu)
521
522
523def _filter_non_printable(unfiltered):
524 """comm names may contain loony chars like '\x00000'"""
525 filtered = ""
526 for char in unfiltered:
527 if char not in string.printable:
528 continue
529 filtered += char
530 return filtered
531
532
533def _fmt_header():
534 fmt = "{{:>{}}}".format(LEN_SWITCHED_IN)
535 fmt += " {{:>{}}}".format(LEN_SWITCHED_OUT)
536 fmt += " {{:>{}}}".format(LEN_CPU)
537 fmt += " {{:>{}}}".format(LEN_PID)
538 fmt += " {{:>{}}}".format(LEN_TID)
539 fmt += " {{:>{}}}".format(LEN_COMM)
540 fmt += " {{:>{}}}".format(LEN_RUNTIME)
541 fmt += " {{:>{}}}".format(LEN_OUT_IN)
542 if args.extended_times:
543 fmt += " {{:>{}}}".format(LEN_OUT_OUT)
544 fmt += " {{:>{}}}".format(LEN_IN_IN)
545 fmt += " {{:>{}}}".format(LEN_IN_OUT)
546 return fmt
547
548
549def _fmt_body():
550 decimal_precision, time_precision = _prepare_fmt_precision()
551 fmt = "{{}}{{:{}.{}f}}".format(LEN_SWITCHED_IN, decimal_precision)
552 fmt += " {{:{}.{}f}}".format(LEN_SWITCHED_OUT, decimal_precision)
553 fmt += " {{:{}d}}".format(LEN_CPU)
554 fmt += " {{:{}d}}".format(LEN_PID)
555 fmt += " {{}}{{:{}d}}{{}}".format(LEN_TID)
556 fmt += " {{}}{{:>{}}}".format(LEN_COMM)
557 fmt += " {{:{}.{}f}}".format(LEN_RUNTIME, time_precision)
558 if args.extended_times:
559 fmt += " {{:{}.{}f}}".format(LEN_OUT_IN, time_precision)
560 fmt += " {{:{}.{}f}}".format(LEN_OUT_OUT, time_precision)
561 fmt += " {{:{}.{}f}}".format(LEN_IN_IN, time_precision)
562 fmt += " {{:{}.{}f}}{{}}".format(LEN_IN_OUT, time_precision)
563 else:
564 fmt += " {{:{}.{}f}}{{}}".format(LEN_OUT_IN, time_precision)
565 return fmt
566
567
568def _print_header():
569 fmt = _fmt_header()
570 header = ("Switched-In", "Switched-Out", "CPU", "PID", "TID", "Comm", "Runtime",
571 "Time Out-In")
572 if args.extended_times:
573 header += ("Time Out-Out", "Time In-In", "Time In-Out")
574 print(fmt.format(*header))
575
576
577def _print_task_finish(task):
578 """calculating every entry of a row and printing it immediately"""
579 c_row_set = ""
580 c_row_reset = ""
581 out_in = -1
582 out_out = -1
583 in_in = -1
584 in_out = -1
585 fmt = _fmt_body()
586
587 # depending on user provided highlight option we change the color
588 # for particular tasks
589 if str(task.tid) in args.highlight_tasks_map:
590 c_row_set = _COLORS[args.highlight_tasks_map[str(task.tid)]]
591 c_row_reset = _COLORS["reset"]
592 if task.comm in args.highlight_tasks_map:
593 c_row_set = _COLORS[args.highlight_tasks_map[task.comm]]
594 c_row_reset = _COLORS["reset"]
595 # grey-out entries if PID == TID, they
596 # are identical, no threaded model so the
597 # thread id (tid) do not matter
598 c_tid_set = ""
599 c_tid_reset = ""
600 if task.pid == task.tid:
601 c_tid_set = _COLORS["grey"]
602 c_tid_reset = _COLORS["reset"]
603 if task.tid in db["tid"]:
604 # get last task of tid
605 last_tid_task = db["tid"][task.tid][-1]
606 # feed the timespan calculate, last in tid db
607 # and second the current one
608 timespan_gap_tid = Timespans()
609 timespan_gap_tid.feed(last_tid_task)
610 timespan_gap_tid.feed(task)
611 out_in = timespan_gap_tid.out_in
612 out_out = timespan_gap_tid.out_out
613 in_in = timespan_gap_tid.in_in
614 in_out = timespan_gap_tid.in_out
615 if args.extended_times:
616 print(fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu, task.pid,
617 c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
618 task.runtime(time_unit), out_in, out_out, in_in, in_out,
619 c_row_reset))
620 else:
621 print(fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu, task.pid,
622 c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
623 task.runtime(time_unit), out_in, c_row_reset))
624
625
626def _record_cleanup(_list):
627 """
628 no need to store more then one element if --summarize
629 is not enabled
630 """
631 if not args.summary and len(_list) > 1:
632 _list = _list[len(_list) - 1 :]
633
634
635def _record_by_tid(task):
636 tid = task.tid
637 if tid not in db["tid"]:
638 db["tid"][tid] = []
639 db["tid"][tid].append(task)
640 _record_cleanup(db["tid"][tid])
641
642
643def _record_by_cpu(task):
644 cpu = task.cpu
645 if cpu not in db["cpu"]:
646 db["cpu"][cpu] = []
647 db["cpu"][cpu].append(task)
648 _record_cleanup(db["cpu"][cpu])
649
650
651def _record_global(task):
652 """record all executed task, ordered by finish chronological"""
653 db["global"].append(task)
654 _record_cleanup(db["global"])
655
656
657def _handle_task_finish(tid, cpu, time, perf_sample_dict):
658 if tid == 0:
659 return
660 _id = _task_id(tid, cpu)
661 if _id not in db["running"]:
662 # may happen, if we missed the switch to
663 # event. Seen in combination with --exclude-perf
664 # where the start is filtered out, but not the
665 # switched in. Probably a bug in exclude-perf
666 # option.
667 return
668 task = db["running"][_id]
669 task.schedule_out_at(time)
670
671 # record tid, during schedule in the tid
672 # is not available, update now
673 pid = int(perf_sample_dict["sample"]["pid"])
674
675 task.update_pid(pid)
676 del db["running"][_id]
677
678 # print only tasks which are not being filtered and no print of trace
679 # for summary only, but record every task.
680 if not _limit_filtered(tid, pid, task.comm) and not args.summary_only:
681 _print_task_finish(task)
682 _record_by_tid(task)
683 _record_by_cpu(task)
684 _record_global(task)
685
686
687def _handle_task_start(tid, cpu, comm, time):
688 if tid == 0:
689 return
690 if tid in args.tid_renames:
691 comm = args.tid_renames[tid]
692 _id = _task_id(tid, cpu)
693 if _id in db["running"]:
694 # handle corner cases where already running tasks
695 # are switched-to again - saw this via --exclude-perf
696 # recorded traces. We simple ignore this "second start"
697 # event.
698 return
699 assert _id not in db["running"]
700 task = Task(_id, tid, cpu, comm)
701 task.schedule_in_at(time)
702 db["running"][_id] = task
703
704
705def _time_to_internal(time_ns):
706 """
707 To prevent float rounding errors we use Decimal internally
708 """
709 return decimal.Decimal(time_ns) / decimal.Decimal(1e9)
710
711
712def _limit_filtered(tid, pid, comm):
713 if args.filter_tasks:
714 if str(tid) in args.filter_tasks or comm in args.filter_tasks:
715 return True
716 else:
717 return False
718 if args.limit_to_tasks:
719 if str(tid) in args.limit_to_tasks or comm in args.limit_to_tasks:
720 return False
721 else:
722 return True
723
724
725def _argument_filter_sanity_check():
726 if args.limit_to_tasks and args.filter_tasks:
727 sys.exit("Error: Filter and Limit at the same time active.")
728 if args.extended_times and args.summary_only:
729 sys.exit("Error: Summary only and extended times active.")
730 if args.time_limit and ":" not in args.time_limit:
731 sys.exit(
732 "Error: No bound set for time limit. Please set bound by ':' e.g :123."
733 )
734 if args.time_limit and (args.summary or args.summary_only or args.summary_extended):
735 sys.exit("Error: Cannot set time limit and print summary")
736
737
738def _argument_prepare_check():
739 global time_unit
740 if args.filter_tasks:
741 args.filter_tasks = args.filter_tasks.split(",")
742 if args.limit_to_tasks:
743 args.limit_to_tasks = args.limit_to_tasks.split(",")
744 if args.time_limit:
745 args.time_limit = args.time_limit.split(":")
746 for rename_tuple in args.rename_comms_by_tids.split(","):
747 tid_name = rename_tuple.split(":")
748 if len(tid_name) != 2:
749 continue
750 args.tid_renames[int(tid_name[0])] = tid_name[1]
751 args.highlight_tasks_map = dict()
752 for highlight_tasks_tuple in args.highlight_tasks.split(","):
753 tasks_color_map = highlight_tasks_tuple.split(":")
754 # default highlight color to red if no color set by user
755 if len(tasks_color_map) == 1:
756 tasks_color_map.append("red")
757 if args.highlight_tasks and tasks_color_map[1].lower() not in _COLORS:
758 sys.exit(
759 "Error: Color not defined, please choose from grey,red,green,yellow,blue,"
760 "violet"
761 )
762 if len(tasks_color_map) != 2:
763 continue
764 args.highlight_tasks_map[tasks_color_map[0]] = tasks_color_map[1]
765 time_unit = "us"
766 if args.ns:
767 time_unit = "ns"
768 elif args.ms:
769 time_unit = "ms"
770
771
772def _is_within_timelimit(time):
773 """
774 Check if a time limit was given by parameter, if so ignore the rest. If not,
775 process the recorded trace in its entirety.
776 """
777 if not args.time_limit:
778 return True
779 lower_time_limit = args.time_limit[0]
780 upper_time_limit = args.time_limit[1]
781 # check for upper limit
782 if upper_time_limit == "":
783 if time >= decimal.Decimal(lower_time_limit):
784 return True
785 # check for lower limit
786 if lower_time_limit == "":
787 if time <= decimal.Decimal(upper_time_limit):
788 return True
789 # quit if time exceeds upper limit. Good for big datasets
790 else:
791 quit()
792 if lower_time_limit != "" and upper_time_limit != "":
793 if (time >= decimal.Decimal(lower_time_limit) and
794 time <= decimal.Decimal(upper_time_limit)):
795 return True
796 # quit if time exceeds upper limit. Good for big datasets
797 elif time > decimal.Decimal(upper_time_limit):
798 quit()
799
800def _prepare_fmt_precision():
801 decimal_precision = 6
802 time_precision = 3
803 if args.ns:
804 decimal_precision = 9
805 time_precision = 0
806 return decimal_precision, time_precision
807
808
809def trace_unhandled(event_name, context, event_fields_dict, perf_sample_dict):
810 pass
811
812
813def trace_begin():
814 _parse_args()
815 _check_color()
816 _init_db()
817 if not args.summary_only:
818 _print_header()
819
820def trace_end():
821 if args.summary or args.summary_extended or args.summary_only:
822 Summary().print()
823
824def sched__sched_switch(event_name, context, common_cpu, common_secs, common_nsecs,
825 common_pid, common_comm, common_callchain, prev_comm,
826 prev_pid, prev_prio, prev_state, next_comm, next_pid,
827 next_prio, perf_sample_dict):
828 # ignore common_secs & common_nsecs cause we need
829 # high res timestamp anyway, using the raw value is
830 # faster
831 time = _time_to_internal(perf_sample_dict["sample"]["time"])
832 if not _is_within_timelimit(time):
833 # user specific --time-limit a:b set
834 return
835
836 next_comm = _filter_non_printable(next_comm)
837 _handle_task_finish(prev_pid, common_cpu, time, perf_sample_dict)
838 _handle_task_start(next_pid, common_cpu, next_comm, time)