xref: /linux/tools/perf/scripts/python/task-analyzer.py (revision 702648721db590b3425c31ade294000e18808345)
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 milliseconds"
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    parser.add_argument(
160        "--csv",
161        default="",
162        help="Write trace to file selected by user. Options, like --ns or --extended"
163            "-times are used.",
164    )
165    parser.add_argument(
166        "--csv-summary",
167        default="",
168        help="Write summary to file selected by user. Options, like --ns or"
169            " --summary-extended are used.",
170    )
171    args = parser.parse_args()
172    args.tid_renames = dict()
173
174    _argument_filter_sanity_check()
175    _argument_prepare_check()
176
177
178def time_uniter(unit):
179    picker = {
180        "s": 1,
181        "ms": 1e3,
182        "us": 1e6,
183        "ns": 1e9,
184    }
185    return picker[unit]
186
187
188def _init_db():
189    global db
190    db = dict()
191    db["running"] = dict()
192    db["cpu"] = dict()
193    db["tid"] = dict()
194    db["global"] = []
195    if args.summary or args.summary_extended or args.summary_only:
196        db["task_info"] = dict()
197        db["runtime_info"] = dict()
198        # min values for summary depending on the header
199        db["task_info"]["pid"] = len("PID")
200        db["task_info"]["tid"] = len("TID")
201        db["task_info"]["comm"] = len("Comm")
202        db["runtime_info"]["runs"] = len("Runs")
203        db["runtime_info"]["acc"] = len("Accumulated")
204        db["runtime_info"]["max"] = len("Max")
205        db["runtime_info"]["max_at"] = len("Max At")
206        db["runtime_info"]["min"] = len("Min")
207        db["runtime_info"]["mean"] = len("Mean")
208        db["runtime_info"]["median"] = len("Median")
209        if args.summary_extended:
210            db["inter_times"] = dict()
211            db["inter_times"]["out_in"] = len("Out-In")
212            db["inter_times"]["inter_at"] = len("At")
213            db["inter_times"]["out_out"] = len("Out-Out")
214            db["inter_times"]["in_in"] = len("In-In")
215            db["inter_times"]["in_out"] = len("In-Out")
216
217
218def _median(numbers):
219    """phython3 hat statistics module - we have nothing"""
220    n = len(numbers)
221    index = n // 2
222    if n % 2:
223        return sorted(numbers)[index]
224    return sum(sorted(numbers)[index - 1 : index + 1]) / 2
225
226
227def _mean(numbers):
228    return sum(numbers) / len(numbers)
229
230
231class Timespans(object):
232    """
233    The elapsed time between two occurrences of the same task is being tracked with the
234    help of this class. There are 4 of those Timespans Out-Out, In-Out, Out-In and
235    In-In.
236    The first half of the name signals the first time point of the
237    first task. The second half of the name represents the second
238    timepoint of the second task.
239    """
240
241    def __init__(self):
242        self._last_start = None
243        self._last_finish = None
244        self.out_out = -1
245        self.in_out = -1
246        self.out_in = -1
247        self.in_in = -1
248        if args.summary_extended:
249            self._time_in = -1
250            self.max_out_in = -1
251            self.max_at = -1
252            self.max_in_out = -1
253            self.max_in_in = -1
254            self.max_out_out = -1
255
256    def feed(self, task):
257        """
258        Called for every recorded trace event to find process pair and calculate the
259        task timespans. Chronological ordering, feed does not do reordering
260        """
261        if not self._last_finish:
262            self._last_start = task.time_in(time_unit)
263            self._last_finish = task.time_out(time_unit)
264            return
265        self._time_in = task.time_in()
266        time_in = task.time_in(time_unit)
267        time_out = task.time_out(time_unit)
268        self.in_in = time_in - self._last_start
269        self.out_in = time_in - self._last_finish
270        self.in_out = time_out - self._last_start
271        self.out_out = time_out - self._last_finish
272        if args.summary_extended:
273            self._update_max_entries()
274        self._last_finish = task.time_out(time_unit)
275        self._last_start = task.time_in(time_unit)
276
277    def _update_max_entries(self):
278        if self.in_in > self.max_in_in:
279            self.max_in_in = self.in_in
280        if self.out_out > self.max_out_out:
281            self.max_out_out = self.out_out
282        if self.in_out > self.max_in_out:
283            self.max_in_out = self.in_out
284        if self.out_in > self.max_out_in:
285            self.max_out_in = self.out_in
286            self.max_at = self._time_in
287
288
289
290class Summary(object):
291    """
292    Primary instance for calculating the summary output. Processes the whole trace to
293    find and memorize relevant data such as mean, max et cetera. This instance handles
294    dynamic alignment aspects for summary output.
295    """
296
297    def __init__(self):
298        self._body = []
299
300    class AlignmentHelper:
301        """
302        Used to calculated the alignment for the output of the summary.
303        """
304        def __init__(self, pid, tid, comm, runs, acc, mean,
305                    median, min, max, max_at):
306            self.pid = pid
307            self.tid = tid
308            self.comm = comm
309            self.runs = runs
310            self.acc = acc
311            self.mean = mean
312            self.median = median
313            self.min = min
314            self.max = max
315            self.max_at = max_at
316            if args.summary_extended:
317                self.out_in = None
318                self.inter_at = None
319                self.out_out = None
320                self.in_in = None
321                self.in_out = None
322
323    def _print_header(self):
324        '''
325        Output is trimmed in _format_stats thus additional adjustment in the header
326        is needed, depending on the choice of timeunit. The adjustment corresponds
327        to the amount of column titles being adjusted in _column_titles.
328        '''
329        decimal_precision = 6 if not args.ns else 9
330        fmt = " {{:^{}}}".format(sum(db["task_info"].values()))
331        fmt += " {{:^{}}}".format(
332            sum(db["runtime_info"].values()) - 2 * decimal_precision
333            )
334        _header = ("Task Information", "Runtime Information")
335
336        if args.summary_extended:
337            fmt += " {{:^{}}}".format(
338                sum(db["inter_times"].values()) - 4 * decimal_precision
339                )
340            _header += ("Max Inter Task Times",)
341        fd_sum.write(fmt.format(*_header) +  "\n")
342
343    def _column_titles(self):
344        """
345        Cells are being processed and displayed in different way so an alignment adjust
346        is implemented depeding on the choice of the timeunit. The positions of the max
347        values are being displayed in grey. Thus in their format two additional {},
348        are placed for color set and reset.
349        """
350        separator, fix_csv_align = _prepare_fmt_sep()
351        decimal_precision, time_precision = _prepare_fmt_precision()
352        fmt = "{{:>{}}}".format(db["task_info"]["pid"] * fix_csv_align)
353        fmt += "{}{{:>{}}}".format(separator, db["task_info"]["tid"] * fix_csv_align)
354        fmt += "{}{{:>{}}}".format(separator, db["task_info"]["comm"] * fix_csv_align)
355        fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["runs"] * fix_csv_align)
356        fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["acc"] * fix_csv_align)
357        fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["mean"] * fix_csv_align)
358        fmt += "{}{{:>{}}}".format(
359            separator, db["runtime_info"]["median"] * fix_csv_align
360        )
361        fmt += "{}{{:>{}}}".format(
362            separator, (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align
363        )
364        fmt += "{}{{:>{}}}".format(
365            separator, (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align
366        )
367        fmt += "{}{{}}{{:>{}}}{{}}".format(
368            separator, (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align
369        )
370
371        column_titles = ("PID", "TID", "Comm")
372        column_titles += ("Runs", "Accumulated", "Mean", "Median", "Min", "Max")
373        column_titles += (_COLORS["grey"], "Max At", _COLORS["reset"])
374
375        if args.summary_extended:
376            fmt += "{}{{:>{}}}".format(
377                separator,
378                (db["inter_times"]["out_in"] - decimal_precision) * fix_csv_align
379            )
380            fmt += "{}{{}}{{:>{}}}{{}}".format(
381                separator,
382                (db["inter_times"]["inter_at"] - time_precision) * fix_csv_align
383            )
384            fmt += "{}{{:>{}}}".format(
385                separator,
386                (db["inter_times"]["out_out"] - decimal_precision) * fix_csv_align
387            )
388            fmt += "{}{{:>{}}}".format(
389                separator,
390                (db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align
391            )
392            fmt += "{}{{:>{}}}".format(
393                separator,
394                (db["inter_times"]["in_out"] - decimal_precision) * fix_csv_align
395            )
396
397            column_titles += ("Out-In", _COLORS["grey"], "Max At", _COLORS["reset"],
398                        "Out-Out", "In-In", "In-Out")
399
400        fd_sum.write(fmt.format(*column_titles) + "\n")
401
402
403    def _task_stats(self):
404        """calculates the stats of every task and constructs the printable summary"""
405        for tid in sorted(db["tid"]):
406            color_one_sample = _COLORS["grey"]
407            color_reset = _COLORS["reset"]
408            no_executed = 0
409            runtimes = []
410            time_in = []
411            timespans = Timespans()
412            for task in db["tid"][tid]:
413                pid = task.pid
414                comm = task.comm
415                no_executed += 1
416                runtimes.append(task.runtime(time_unit))
417                time_in.append(task.time_in())
418                timespans.feed(task)
419            if len(runtimes) > 1:
420                color_one_sample = ""
421                color_reset = ""
422            time_max = max(runtimes)
423            time_min = min(runtimes)
424            max_at = time_in[runtimes.index(max(runtimes))]
425
426            # The size of the decimal after sum,mean and median varies, thus we cut
427            # the decimal number, by rounding it. It has no impact on the output,
428            # because we have a precision of the decimal points at the output.
429            time_sum = round(sum(runtimes), 3)
430            time_mean = round(_mean(runtimes), 3)
431            time_median = round(_median(runtimes), 3)
432
433            align_helper = self.AlignmentHelper(pid, tid, comm, no_executed, time_sum,
434                                    time_mean, time_median, time_min, time_max, max_at)
435            self._body.append([pid, tid, comm, no_executed, time_sum, color_one_sample,
436                                time_mean, time_median, time_min, time_max,
437                                _COLORS["grey"], max_at, _COLORS["reset"], color_reset])
438            if args.summary_extended:
439                self._body[-1].extend([timespans.max_out_in,
440                                _COLORS["grey"], timespans.max_at,
441                                _COLORS["reset"], timespans.max_out_out,
442                                timespans.max_in_in,
443                                timespans.max_in_out])
444                align_helper.out_in = timespans.max_out_in
445                align_helper.inter_at = timespans.max_at
446                align_helper.out_out = timespans.max_out_out
447                align_helper.in_in = timespans.max_in_in
448                align_helper.in_out = timespans.max_in_out
449            self._calc_alignments_summary(align_helper)
450
451    def _format_stats(self):
452        separator, fix_csv_align = _prepare_fmt_sep()
453        decimal_precision, time_precision = _prepare_fmt_precision()
454        len_pid = db["task_info"]["pid"] * fix_csv_align
455        len_tid = db["task_info"]["tid"] * fix_csv_align
456        len_comm = db["task_info"]["comm"] * fix_csv_align
457        len_runs = db["runtime_info"]["runs"] * fix_csv_align
458        len_acc = db["runtime_info"]["acc"] * fix_csv_align
459        len_mean = db["runtime_info"]["mean"] * fix_csv_align
460        len_median = db["runtime_info"]["median"] * fix_csv_align
461        len_min = (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align
462        len_max = (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align
463        len_max_at = (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align
464        if args.summary_extended:
465            len_out_in = (
466                db["inter_times"]["out_in"] - decimal_precision
467            ) * fix_csv_align
468            len_inter_at = (
469                db["inter_times"]["inter_at"] - time_precision
470            ) * fix_csv_align
471            len_out_out = (
472                db["inter_times"]["out_out"] - decimal_precision
473            ) * fix_csv_align
474            len_in_in = (db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align
475            len_in_out = (
476                db["inter_times"]["in_out"] - decimal_precision
477            ) * fix_csv_align
478
479        fmt = "{{:{}d}}".format(len_pid)
480        fmt += "{}{{:{}d}}".format(separator, len_tid)
481        fmt += "{}{{:>{}}}".format(separator, len_comm)
482        fmt += "{}{{:{}d}}".format(separator, len_runs)
483        fmt += "{}{{:{}.{}f}}".format(separator, len_acc, time_precision)
484        fmt += "{}{{}}{{:{}.{}f}}".format(separator, len_mean, time_precision)
485        fmt += "{}{{:{}.{}f}}".format(separator, len_median, time_precision)
486        fmt += "{}{{:{}.{}f}}".format(separator, len_min, time_precision)
487        fmt += "{}{{:{}.{}f}}".format(separator, len_max, time_precision)
488        fmt += "{}{{}}{{:{}.{}f}}{{}}{{}}".format(
489            separator, len_max_at, decimal_precision
490        )
491        if args.summary_extended:
492            fmt += "{}{{:{}.{}f}}".format(separator, len_out_in, time_precision)
493            fmt += "{}{{}}{{:{}.{}f}}{{}}".format(
494                separator, len_inter_at, decimal_precision
495            )
496            fmt += "{}{{:{}.{}f}}".format(separator, len_out_out, time_precision)
497            fmt += "{}{{:{}.{}f}}".format(separator, len_in_in, time_precision)
498            fmt += "{}{{:{}.{}f}}".format(separator, len_in_out, time_precision)
499        return fmt
500
501
502    def _calc_alignments_summary(self, align_helper):
503        # Length is being cut in 3 groups so that further addition is easier to handle.
504        # The length of every argument from the alignment helper is being checked if it
505        # is longer than the longest until now. In that case the length is being saved.
506        for key in db["task_info"]:
507            if len(str(getattr(align_helper, key))) > db["task_info"][key]:
508                db["task_info"][key] = len(str(getattr(align_helper, key)))
509        for key in db["runtime_info"]:
510            if len(str(getattr(align_helper, key))) > db["runtime_info"][key]:
511                db["runtime_info"][key] = len(str(getattr(align_helper, key)))
512        if args.summary_extended:
513            for key in db["inter_times"]:
514                if len(str(getattr(align_helper, key))) > db["inter_times"][key]:
515                    db["inter_times"][key] = len(str(getattr(align_helper, key)))
516
517
518    def print(self):
519        self._task_stats()
520        fmt = self._format_stats()
521
522        if not args.csv_summary:
523            print("\nSummary")
524            self._print_header()
525        self._column_titles()
526        for i in range(len(self._body)):
527            fd_sum.write(fmt.format(*tuple(self._body[i])) + "\n")
528
529
530
531class Task(object):
532    """ The class is used to handle the information of a given task."""
533
534    def __init__(self, id, tid, cpu, comm):
535        self.id = id
536        self.tid = tid
537        self.cpu = cpu
538        self.comm = comm
539        self.pid = None
540        self._time_in = None
541        self._time_out = None
542
543    def schedule_in_at(self, time):
544        """set the time where the task was scheduled in"""
545        self._time_in = time
546
547    def schedule_out_at(self, time):
548        """set the time where the task was scheduled out"""
549        self._time_out = time
550
551    def time_out(self, unit="s"):
552        """return time where a given task was scheduled out"""
553        factor = time_uniter(unit)
554        return self._time_out * decimal.Decimal(factor)
555
556    def time_in(self, unit="s"):
557        """return time where a given task was scheduled in"""
558        factor = time_uniter(unit)
559        return self._time_in * decimal.Decimal(factor)
560
561    def runtime(self, unit="us"):
562        factor = time_uniter(unit)
563        return (self._time_out - self._time_in) * decimal.Decimal(factor)
564
565    def update_pid(self, pid):
566        self.pid = pid
567
568
569def _task_id(pid, cpu):
570    """returns a "unique-enough" identifier, please do not change"""
571    return "{}-{}".format(pid, cpu)
572
573
574def _filter_non_printable(unfiltered):
575    """comm names may contain loony chars like '\x00000'"""
576    filtered = ""
577    for char in unfiltered:
578        if char not in string.printable:
579            continue
580        filtered += char
581    return filtered
582
583
584def _fmt_header():
585    separator, fix_csv_align = _prepare_fmt_sep()
586    fmt = "{{:>{}}}".format(LEN_SWITCHED_IN*fix_csv_align)
587    fmt += "{}{{:>{}}}".format(separator, LEN_SWITCHED_OUT*fix_csv_align)
588    fmt += "{}{{:>{}}}".format(separator, LEN_CPU*fix_csv_align)
589    fmt += "{}{{:>{}}}".format(separator, LEN_PID*fix_csv_align)
590    fmt += "{}{{:>{}}}".format(separator, LEN_TID*fix_csv_align)
591    fmt += "{}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align)
592    fmt += "{}{{:>{}}}".format(separator, LEN_RUNTIME*fix_csv_align)
593    fmt += "{}{{:>{}}}".format(separator, LEN_OUT_IN*fix_csv_align)
594    if args.extended_times:
595        fmt += "{}{{:>{}}}".format(separator, LEN_OUT_OUT*fix_csv_align)
596        fmt += "{}{{:>{}}}".format(separator, LEN_IN_IN*fix_csv_align)
597        fmt += "{}{{:>{}}}".format(separator, LEN_IN_OUT*fix_csv_align)
598    return fmt
599
600
601def _fmt_body():
602    separator, fix_csv_align = _prepare_fmt_sep()
603    decimal_precision, time_precision = _prepare_fmt_precision()
604    fmt = "{{}}{{:{}.{}f}}".format(LEN_SWITCHED_IN*fix_csv_align, decimal_precision)
605    fmt += "{}{{:{}.{}f}}".format(
606        separator, LEN_SWITCHED_OUT*fix_csv_align, decimal_precision
607    )
608    fmt += "{}{{:{}d}}".format(separator, LEN_CPU*fix_csv_align)
609    fmt += "{}{{:{}d}}".format(separator, LEN_PID*fix_csv_align)
610    fmt += "{}{{}}{{:{}d}}{{}}".format(separator, LEN_TID*fix_csv_align)
611    fmt += "{}{{}}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align)
612    fmt += "{}{{:{}.{}f}}".format(separator, LEN_RUNTIME*fix_csv_align, time_precision)
613    if args.extended_times:
614        fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_IN*fix_csv_align, time_precision)
615        fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_OUT*fix_csv_align, time_precision)
616        fmt += "{}{{:{}.{}f}}".format(separator, LEN_IN_IN*fix_csv_align, time_precision)
617        fmt += "{}{{:{}.{}f}}{{}}".format(
618            separator, LEN_IN_OUT*fix_csv_align, time_precision
619        )
620    else:
621        fmt += "{}{{:{}.{}f}}{{}}".format(
622            separator, LEN_OUT_IN*fix_csv_align, time_precision
623        )
624    return fmt
625
626
627def _print_header():
628    fmt = _fmt_header()
629    header = ("Switched-In", "Switched-Out", "CPU", "PID", "TID", "Comm", "Runtime",
630            "Time Out-In")
631    if args.extended_times:
632        header += ("Time Out-Out", "Time In-In", "Time In-Out")
633    fd_task.write(fmt.format(*header) + "\n")
634
635
636
637def _print_task_finish(task):
638    """calculating every entry of a row and printing it immediately"""
639    c_row_set = ""
640    c_row_reset = ""
641    out_in = -1
642    out_out = -1
643    in_in = -1
644    in_out = -1
645    fmt = _fmt_body()
646    # depending on user provided highlight option we change the color
647    # for particular tasks
648    if str(task.tid) in args.highlight_tasks_map:
649        c_row_set = _COLORS[args.highlight_tasks_map[str(task.tid)]]
650        c_row_reset = _COLORS["reset"]
651    if task.comm in args.highlight_tasks_map:
652        c_row_set = _COLORS[args.highlight_tasks_map[task.comm]]
653        c_row_reset = _COLORS["reset"]
654    # grey-out entries if PID == TID, they
655    # are identical, no threaded model so the
656    # thread id (tid) do not matter
657    c_tid_set = ""
658    c_tid_reset = ""
659    if task.pid == task.tid:
660        c_tid_set = _COLORS["grey"]
661        c_tid_reset = _COLORS["reset"]
662    if task.tid in db["tid"]:
663        # get last task of tid
664        last_tid_task = db["tid"][task.tid][-1]
665        # feed the timespan calculate, last in tid db
666        # and second the current one
667        timespan_gap_tid = Timespans()
668        timespan_gap_tid.feed(last_tid_task)
669        timespan_gap_tid.feed(task)
670        out_in = timespan_gap_tid.out_in
671        out_out = timespan_gap_tid.out_out
672        in_in = timespan_gap_tid.in_in
673        in_out = timespan_gap_tid.in_out
674
675
676    if args.extended_times:
677        line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu,
678                        task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
679                        task.runtime(time_unit), out_in, out_out, in_in, in_out,
680                        c_row_reset) + "\n"
681    else:
682        line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu,
683                        task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
684                        task.runtime(time_unit), out_in, c_row_reset) + "\n"
685    try:
686        fd_task.write(line_out)
687    except(IOError):
688        # don't mangle the output if user SIGINT this script
689        sys.exit()
690
691def _record_cleanup(_list):
692    """
693    no need to store more then one element if --summarize
694    is not enabled
695    """
696    if not args.summary and len(_list) > 1:
697        _list = _list[len(_list) - 1 :]
698
699
700def _record_by_tid(task):
701    tid = task.tid
702    if tid not in db["tid"]:
703        db["tid"][tid] = []
704    db["tid"][tid].append(task)
705    _record_cleanup(db["tid"][tid])
706
707
708def _record_by_cpu(task):
709    cpu = task.cpu
710    if cpu not in db["cpu"]:
711        db["cpu"][cpu] = []
712    db["cpu"][cpu].append(task)
713    _record_cleanup(db["cpu"][cpu])
714
715
716def _record_global(task):
717    """record all executed task, ordered by finish chronological"""
718    db["global"].append(task)
719    _record_cleanup(db["global"])
720
721
722def _handle_task_finish(tid, cpu, time, perf_sample_dict):
723    if tid == 0:
724        return
725    _id = _task_id(tid, cpu)
726    if _id not in db["running"]:
727        # may happen, if we missed the switch to
728        # event. Seen in combination with --exclude-perf
729        # where the start is filtered out, but not the
730        # switched in. Probably a bug in exclude-perf
731        # option.
732        return
733    task = db["running"][_id]
734    task.schedule_out_at(time)
735
736    # record tid, during schedule in the tid
737    # is not available, update now
738    pid = int(perf_sample_dict["sample"]["pid"])
739
740    task.update_pid(pid)
741    del db["running"][_id]
742
743    # print only tasks which are not being filtered and no print of trace
744    # for summary only, but record every task.
745    if not _limit_filtered(tid, pid, task.comm) and not args.summary_only:
746        _print_task_finish(task)
747    _record_by_tid(task)
748    _record_by_cpu(task)
749    _record_global(task)
750
751
752def _handle_task_start(tid, cpu, comm, time):
753    if tid == 0:
754        return
755    if tid in args.tid_renames:
756        comm = args.tid_renames[tid]
757    _id = _task_id(tid, cpu)
758    if _id in db["running"]:
759        # handle corner cases where already running tasks
760        # are switched-to again - saw this via --exclude-perf
761        # recorded traces. We simple ignore this "second start"
762        # event.
763        return
764    assert _id not in db["running"]
765    task = Task(_id, tid, cpu, comm)
766    task.schedule_in_at(time)
767    db["running"][_id] = task
768
769
770def _time_to_internal(time_ns):
771    """
772    To prevent float rounding errors we use Decimal internally
773    """
774    return decimal.Decimal(time_ns) / decimal.Decimal(1e9)
775
776
777def _limit_filtered(tid, pid, comm):
778    if args.filter_tasks:
779        if str(tid) in args.filter_tasks or comm in args.filter_tasks:
780            return True
781        else:
782            return False
783    if args.limit_to_tasks:
784        if str(tid) in args.limit_to_tasks or comm in args.limit_to_tasks:
785            return False
786        else:
787            return True
788
789
790def _argument_filter_sanity_check():
791    if args.limit_to_tasks and args.filter_tasks:
792        sys.exit("Error: Filter and Limit at the same time active.")
793    if args.extended_times and args.summary_only:
794        sys.exit("Error: Summary only and extended times active.")
795    if args.time_limit and ":" not in args.time_limit:
796        sys.exit(
797            "Error: No bound set for time limit. Please set bound by ':' e.g :123."
798        )
799    if args.time_limit and (args.summary or args.summary_only or args.summary_extended):
800        sys.exit("Error: Cannot set time limit and print summary")
801    if args.csv_summary:
802        args.summary = True
803        if args.csv == args.csv_summary:
804            sys.exit("Error: Chosen files for csv and csv summary are the same")
805    if args.csv and (args.summary_extended or args.summary) and not args.csv_summary:
806        sys.exit("Error: No file chosen to write summary to. Choose with --csv-summary "
807        "<file>")
808    if args.csv and args.summary_only:
809        sys.exit("Error: --csv chosen and --summary-only. Standard task would not be"
810            "written to csv file.")
811
812def _argument_prepare_check():
813    global time_unit, fd_task, fd_sum
814    if args.filter_tasks:
815        args.filter_tasks = args.filter_tasks.split(",")
816    if args.limit_to_tasks:
817        args.limit_to_tasks = args.limit_to_tasks.split(",")
818    if args.time_limit:
819        args.time_limit = args.time_limit.split(":")
820    for rename_tuple in args.rename_comms_by_tids.split(","):
821        tid_name = rename_tuple.split(":")
822        if len(tid_name) != 2:
823            continue
824        args.tid_renames[int(tid_name[0])] = tid_name[1]
825    args.highlight_tasks_map = dict()
826    for highlight_tasks_tuple in args.highlight_tasks.split(","):
827        tasks_color_map = highlight_tasks_tuple.split(":")
828        # default highlight color to red if no color set by user
829        if len(tasks_color_map) == 1:
830            tasks_color_map.append("red")
831        if args.highlight_tasks and tasks_color_map[1].lower() not in _COLORS:
832            sys.exit(
833                "Error: Color not defined, please choose from grey,red,green,yellow,blue,"
834                "violet"
835            )
836        if len(tasks_color_map) != 2:
837            continue
838        args.highlight_tasks_map[tasks_color_map[0]] = tasks_color_map[1]
839    time_unit = "us"
840    if args.ns:
841        time_unit = "ns"
842    elif args.ms:
843        time_unit = "ms"
844
845
846    fd_task = sys.stdout
847    if args.csv:
848        args.stdio_color = "never"
849        fd_task = open(args.csv, "w")
850        print("generating csv at",args.csv,)
851
852    fd_sum = sys.stdout
853    if args.csv_summary:
854        args.stdio_color = "never"
855        fd_sum = open(args.csv_summary, "w")
856        print("generating csv summary at",args.csv_summary)
857        if not args.csv:
858            args.summary_only = True
859
860
861def _is_within_timelimit(time):
862    """
863    Check if a time limit was given by parameter, if so ignore the rest. If not,
864    process the recorded trace in its entirety.
865    """
866    if not args.time_limit:
867        return True
868    lower_time_limit = args.time_limit[0]
869    upper_time_limit = args.time_limit[1]
870    # check for upper limit
871    if upper_time_limit == "":
872        if time >= decimal.Decimal(lower_time_limit):
873            return True
874    # check for lower limit
875    if lower_time_limit == "":
876        if time <= decimal.Decimal(upper_time_limit):
877            return True
878        # quit if time exceeds upper limit. Good for big datasets
879        else:
880            quit()
881    if lower_time_limit != "" and upper_time_limit != "":
882        if (time >= decimal.Decimal(lower_time_limit) and
883            time <= decimal.Decimal(upper_time_limit)):
884            return True
885        # quit if time exceeds upper limit. Good for big datasets
886        elif time > decimal.Decimal(upper_time_limit):
887            quit()
888
889def _prepare_fmt_precision():
890    decimal_precision = 6
891    time_precision = 3
892    if args.ns:
893     decimal_precision = 9
894     time_precision = 0
895    return decimal_precision, time_precision
896
897def _prepare_fmt_sep():
898    separator = " "
899    fix_csv_align = 1
900    if args.csv or args.csv_summary:
901        separator = ";"
902        fix_csv_align = 0
903    return separator, fix_csv_align
904
905def trace_unhandled(event_name, context, event_fields_dict, perf_sample_dict):
906    pass
907
908
909def trace_begin():
910    _parse_args()
911    _check_color()
912    _init_db()
913    if not args.summary_only:
914        _print_header()
915
916def trace_end():
917    if args.summary or args.summary_extended or args.summary_only:
918        Summary().print()
919
920def sched__sched_switch(event_name, context, common_cpu, common_secs, common_nsecs,
921                        common_pid, common_comm, common_callchain, prev_comm,
922                        prev_pid, prev_prio, prev_state, next_comm, next_pid,
923                        next_prio, perf_sample_dict):
924    # ignore common_secs & common_nsecs cause we need
925    # high res timestamp anyway, using the raw value is
926    # faster
927    time = _time_to_internal(perf_sample_dict["sample"]["time"])
928    if not _is_within_timelimit(time):
929        # user specific --time-limit a:b set
930        return
931
932    next_comm = _filter_non_printable(next_comm)
933    _handle_task_finish(prev_pid, common_cpu, time, perf_sample_dict)
934    _handle_task_start(next_pid, common_cpu, next_comm, time)
935