| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934 |
- # task-analyzer.py - comprehensive perf tasks analysis
- # SPDX-License-Identifier: GPL-2.0
- # Copyright (c) 2022, Hagen Paul Pfeifer <hagen@jauu.net>
- # Licensed under the terms of the GNU GPL License version 2
- #
- # Usage:
- #
- # perf record -e sched:sched_switch -a -- sleep 10
- # perf script report task-analyzer
- #
- from __future__ import print_function
- import sys
- import os
- import string
- import argparse
- import decimal
- sys.path.append(
- os.environ["PERF_EXEC_PATH"] + "/scripts/python/Perf-Trace-Util/lib/Perf/Trace"
- )
- from perf_trace_context import *
- from Core import *
- # Definition of possible ASCII color codes
- _COLORS = {
- "grey": "\033[90m",
- "red": "\033[91m",
- "green": "\033[92m",
- "yellow": "\033[93m",
- "blue": "\033[94m",
- "violet": "\033[95m",
- "reset": "\033[0m",
- }
- # Columns will have a static size to align everything properly
- # Support of 116 days of active update with nano precision
- LEN_SWITCHED_IN = len("9999999.999999999") # 17
- LEN_SWITCHED_OUT = len("9999999.999999999") # 17
- LEN_CPU = len("000")
- LEN_PID = len("maxvalue") # 8
- LEN_TID = len("maxvalue") # 8
- LEN_COMM = len("max-comms-length") # 16
- LEN_RUNTIME = len("999999.999") # 10
- # Support of 3.45 hours of timespans
- LEN_OUT_IN = len("99999999999.999") # 15
- LEN_OUT_OUT = len("99999999999.999") # 15
- LEN_IN_IN = len("99999999999.999") # 15
- LEN_IN_OUT = len("99999999999.999") # 15
- # py2/py3 compatibility layer, see PEP469
- try:
- dict.iteritems
- except AttributeError:
- # py3
- def itervalues(d):
- return iter(d.values())
- def iteritems(d):
- return iter(d.items())
- else:
- # py2
- def itervalues(d):
- return d.itervalues()
- def iteritems(d):
- return d.iteritems()
- def _check_color():
- global _COLORS
- """user enforced no-color or if stdout is no tty we disable colors"""
- if sys.stdout.isatty() and args.stdio_color != "never":
- return
- _COLORS = {
- "grey": "",
- "red": "",
- "green": "",
- "yellow": "",
- "blue": "",
- "violet": "",
- "reset": "",
- }
- def _parse_args():
- global args
- parser = argparse.ArgumentParser(description="Analyze tasks behavior")
- parser.add_argument(
- "--time-limit",
- default=[],
- help=
- "print tasks only in time[s] window e.g"
- " --time-limit 123.111:789.222(print all between 123.111 and 789.222)"
- " --time-limit 123: (print all from 123)"
- " --time-limit :456 (print all until incl. 456)",
- )
- parser.add_argument(
- "--summary", action="store_true", help="print addtional runtime information"
- )
- parser.add_argument(
- "--summary-only", action="store_true", help="print only summary without traces"
- )
- parser.add_argument(
- "--summary-extended",
- action="store_true",
- help="print the summary with additional information of max inter task times"
- " relative to the prev task",
- )
- parser.add_argument(
- "--ns", action="store_true", help="show timestamps in nanoseconds"
- )
- parser.add_argument(
- "--ms", action="store_true", help="show timestamps in milliseconds"
- )
- parser.add_argument(
- "--extended-times",
- action="store_true",
- help="Show the elapsed times between schedule in/schedule out"
- " of this task and the schedule in/schedule out of previous occurrence"
- " of the same task",
- )
- parser.add_argument(
- "--filter-tasks",
- default=[],
- help="filter out unneeded tasks by tid, pid or processname."
- " E.g --filter-task 1337,/sbin/init ",
- )
- parser.add_argument(
- "--limit-to-tasks",
- default=[],
- help="limit output to selected task by tid, pid, processname."
- " E.g --limit-to-tasks 1337,/sbin/init",
- )
- parser.add_argument(
- "--highlight-tasks",
- default="",
- help="colorize special tasks by their pid/tid/comm."
- " E.g. --highlight-tasks 1:red,mutt:yellow"
- " Colors available: red,grey,yellow,blue,violet,green",
- )
- parser.add_argument(
- "--rename-comms-by-tids",
- default="",
- help="rename task names by using tid (<tid>:<newname>,<tid>:<newname>)"
- " This option is handy for inexpressive processnames like python interpreted"
- " process. E.g --rename 1337:my-python-app",
- )
- parser.add_argument(
- "--stdio-color",
- default="auto",
- choices=["always", "never", "auto"],
- help="always, never or auto, allowing configuring color output"
- " via the command line",
- )
- parser.add_argument(
- "--csv",
- default="",
- help="Write trace to file selected by user. Options, like --ns or --extended"
- "-times are used.",
- )
- parser.add_argument(
- "--csv-summary",
- default="",
- help="Write summary to file selected by user. Options, like --ns or"
- " --summary-extended are used.",
- )
- args = parser.parse_args()
- args.tid_renames = dict()
- _argument_filter_sanity_check()
- _argument_prepare_check()
- def time_uniter(unit):
- picker = {
- "s": 1,
- "ms": 1e3,
- "us": 1e6,
- "ns": 1e9,
- }
- return picker[unit]
- def _init_db():
- global db
- db = dict()
- db["running"] = dict()
- db["cpu"] = dict()
- db["tid"] = dict()
- db["global"] = []
- if args.summary or args.summary_extended or args.summary_only:
- db["task_info"] = dict()
- db["runtime_info"] = dict()
- # min values for summary depending on the header
- db["task_info"]["pid"] = len("PID")
- db["task_info"]["tid"] = len("TID")
- db["task_info"]["comm"] = len("Comm")
- db["runtime_info"]["runs"] = len("Runs")
- db["runtime_info"]["acc"] = len("Accumulated")
- db["runtime_info"]["max"] = len("Max")
- db["runtime_info"]["max_at"] = len("Max At")
- db["runtime_info"]["min"] = len("Min")
- db["runtime_info"]["mean"] = len("Mean")
- db["runtime_info"]["median"] = len("Median")
- if args.summary_extended:
- db["inter_times"] = dict()
- db["inter_times"]["out_in"] = len("Out-In")
- db["inter_times"]["inter_at"] = len("At")
- db["inter_times"]["out_out"] = len("Out-Out")
- db["inter_times"]["in_in"] = len("In-In")
- db["inter_times"]["in_out"] = len("In-Out")
- def _median(numbers):
- """phython3 hat statistics module - we have nothing"""
- n = len(numbers)
- index = n // 2
- if n % 2:
- return sorted(numbers)[index]
- return sum(sorted(numbers)[index - 1 : index + 1]) / 2
- def _mean(numbers):
- return sum(numbers) / len(numbers)
- class Timespans(object):
- """
- The elapsed time between two occurrences of the same task is being tracked with the
- help of this class. There are 4 of those Timespans Out-Out, In-Out, Out-In and
- In-In.
- The first half of the name signals the first time point of the
- first task. The second half of the name represents the second
- timepoint of the second task.
- """
- def __init__(self):
- self._last_start = None
- self._last_finish = None
- self.out_out = -1
- self.in_out = -1
- self.out_in = -1
- self.in_in = -1
- if args.summary_extended:
- self._time_in = -1
- self.max_out_in = -1
- self.max_at = -1
- self.max_in_out = -1
- self.max_in_in = -1
- self.max_out_out = -1
- def feed(self, task):
- """
- Called for every recorded trace event to find process pair and calculate the
- task timespans. Chronological ordering, feed does not do reordering
- """
- if not self._last_finish:
- self._last_start = task.time_in(time_unit)
- self._last_finish = task.time_out(time_unit)
- return
- self._time_in = task.time_in()
- time_in = task.time_in(time_unit)
- time_out = task.time_out(time_unit)
- self.in_in = time_in - self._last_start
- self.out_in = time_in - self._last_finish
- self.in_out = time_out - self._last_start
- self.out_out = time_out - self._last_finish
- if args.summary_extended:
- self._update_max_entries()
- self._last_finish = task.time_out(time_unit)
- self._last_start = task.time_in(time_unit)
- def _update_max_entries(self):
- if self.in_in > self.max_in_in:
- self.max_in_in = self.in_in
- if self.out_out > self.max_out_out:
- self.max_out_out = self.out_out
- if self.in_out > self.max_in_out:
- self.max_in_out = self.in_out
- if self.out_in > self.max_out_in:
- self.max_out_in = self.out_in
- self.max_at = self._time_in
- class Summary(object):
- """
- Primary instance for calculating the summary output. Processes the whole trace to
- find and memorize relevant data such as mean, max et cetera. This instance handles
- dynamic alignment aspects for summary output.
- """
- def __init__(self):
- self._body = []
- class AlignmentHelper:
- """
- Used to calculated the alignment for the output of the summary.
- """
- def __init__(self, pid, tid, comm, runs, acc, mean,
- median, min, max, max_at):
- self.pid = pid
- self.tid = tid
- self.comm = comm
- self.runs = runs
- self.acc = acc
- self.mean = mean
- self.median = median
- self.min = min
- self.max = max
- self.max_at = max_at
- if args.summary_extended:
- self.out_in = None
- self.inter_at = None
- self.out_out = None
- self.in_in = None
- self.in_out = None
- def _print_header(self):
- '''
- Output is trimmed in _format_stats thus additional adjustment in the header
- is needed, depending on the choice of timeunit. The adjustment corresponds
- to the amount of column titles being adjusted in _column_titles.
- '''
- decimal_precision = 6 if not args.ns else 9
- fmt = " {{:^{}}}".format(sum(db["task_info"].values()))
- fmt += " {{:^{}}}".format(
- sum(db["runtime_info"].values()) - 2 * decimal_precision
- )
- _header = ("Task Information", "Runtime Information")
- if args.summary_extended:
- fmt += " {{:^{}}}".format(
- sum(db["inter_times"].values()) - 4 * decimal_precision
- )
- _header += ("Max Inter Task Times",)
- fd_sum.write(fmt.format(*_header) + "\n")
- def _column_titles(self):
- """
- Cells are being processed and displayed in different way so an alignment adjust
- is implemented depeding on the choice of the timeunit. The positions of the max
- values are being displayed in grey. Thus in their format two additional {},
- are placed for color set and reset.
- """
- separator, fix_csv_align = _prepare_fmt_sep()
- decimal_precision, time_precision = _prepare_fmt_precision()
- fmt = "{{:>{}}}".format(db["task_info"]["pid"] * fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, db["task_info"]["tid"] * fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, db["task_info"]["comm"] * fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["runs"] * fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["acc"] * fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["mean"] * fix_csv_align)
- fmt += "{}{{:>{}}}".format(
- separator, db["runtime_info"]["median"] * fix_csv_align
- )
- fmt += "{}{{:>{}}}".format(
- separator, (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align
- )
- fmt += "{}{{:>{}}}".format(
- separator, (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align
- )
- fmt += "{}{{}}{{:>{}}}{{}}".format(
- separator, (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align
- )
- column_titles = ("PID", "TID", "Comm")
- column_titles += ("Runs", "Accumulated", "Mean", "Median", "Min", "Max")
- column_titles += (_COLORS["grey"], "Max At", _COLORS["reset"])
- if args.summary_extended:
- fmt += "{}{{:>{}}}".format(
- separator,
- (db["inter_times"]["out_in"] - decimal_precision) * fix_csv_align
- )
- fmt += "{}{{}}{{:>{}}}{{}}".format(
- separator,
- (db["inter_times"]["inter_at"] - time_precision) * fix_csv_align
- )
- fmt += "{}{{:>{}}}".format(
- separator,
- (db["inter_times"]["out_out"] - decimal_precision) * fix_csv_align
- )
- fmt += "{}{{:>{}}}".format(
- separator,
- (db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align
- )
- fmt += "{}{{:>{}}}".format(
- separator,
- (db["inter_times"]["in_out"] - decimal_precision) * fix_csv_align
- )
- column_titles += ("Out-In", _COLORS["grey"], "Max At", _COLORS["reset"],
- "Out-Out", "In-In", "In-Out")
- fd_sum.write(fmt.format(*column_titles) + "\n")
- def _task_stats(self):
- """calculates the stats of every task and constructs the printable summary"""
- for tid in sorted(db["tid"]):
- color_one_sample = _COLORS["grey"]
- color_reset = _COLORS["reset"]
- no_executed = 0
- runtimes = []
- time_in = []
- timespans = Timespans()
- for task in db["tid"][tid]:
- pid = task.pid
- comm = task.comm
- no_executed += 1
- runtimes.append(task.runtime(time_unit))
- time_in.append(task.time_in())
- timespans.feed(task)
- if len(runtimes) > 1:
- color_one_sample = ""
- color_reset = ""
- time_max = max(runtimes)
- time_min = min(runtimes)
- max_at = time_in[runtimes.index(max(runtimes))]
- # The size of the decimal after sum,mean and median varies, thus we cut
- # the decimal number, by rounding it. It has no impact on the output,
- # because we have a precision of the decimal points at the output.
- time_sum = round(sum(runtimes), 3)
- time_mean = round(_mean(runtimes), 3)
- time_median = round(_median(runtimes), 3)
- align_helper = self.AlignmentHelper(pid, tid, comm, no_executed, time_sum,
- time_mean, time_median, time_min, time_max, max_at)
- self._body.append([pid, tid, comm, no_executed, time_sum, color_one_sample,
- time_mean, time_median, time_min, time_max,
- _COLORS["grey"], max_at, _COLORS["reset"], color_reset])
- if args.summary_extended:
- self._body[-1].extend([timespans.max_out_in,
- _COLORS["grey"], timespans.max_at,
- _COLORS["reset"], timespans.max_out_out,
- timespans.max_in_in,
- timespans.max_in_out])
- align_helper.out_in = timespans.max_out_in
- align_helper.inter_at = timespans.max_at
- align_helper.out_out = timespans.max_out_out
- align_helper.in_in = timespans.max_in_in
- align_helper.in_out = timespans.max_in_out
- self._calc_alignments_summary(align_helper)
- def _format_stats(self):
- separator, fix_csv_align = _prepare_fmt_sep()
- decimal_precision, time_precision = _prepare_fmt_precision()
- len_pid = db["task_info"]["pid"] * fix_csv_align
- len_tid = db["task_info"]["tid"] * fix_csv_align
- len_comm = db["task_info"]["comm"] * fix_csv_align
- len_runs = db["runtime_info"]["runs"] * fix_csv_align
- len_acc = db["runtime_info"]["acc"] * fix_csv_align
- len_mean = db["runtime_info"]["mean"] * fix_csv_align
- len_median = db["runtime_info"]["median"] * fix_csv_align
- len_min = (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align
- len_max = (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align
- len_max_at = (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align
- if args.summary_extended:
- len_out_in = (
- db["inter_times"]["out_in"] - decimal_precision
- ) * fix_csv_align
- len_inter_at = (
- db["inter_times"]["inter_at"] - time_precision
- ) * fix_csv_align
- len_out_out = (
- db["inter_times"]["out_out"] - decimal_precision
- ) * fix_csv_align
- len_in_in = (db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align
- len_in_out = (
- db["inter_times"]["in_out"] - decimal_precision
- ) * fix_csv_align
- fmt = "{{:{}d}}".format(len_pid)
- fmt += "{}{{:{}d}}".format(separator, len_tid)
- fmt += "{}{{:>{}}}".format(separator, len_comm)
- fmt += "{}{{:{}d}}".format(separator, len_runs)
- fmt += "{}{{:{}.{}f}}".format(separator, len_acc, time_precision)
- fmt += "{}{{}}{{:{}.{}f}}".format(separator, len_mean, time_precision)
- fmt += "{}{{:{}.{}f}}".format(separator, len_median, time_precision)
- fmt += "{}{{:{}.{}f}}".format(separator, len_min, time_precision)
- fmt += "{}{{:{}.{}f}}".format(separator, len_max, time_precision)
- fmt += "{}{{}}{{:{}.{}f}}{{}}{{}}".format(
- separator, len_max_at, decimal_precision
- )
- if args.summary_extended:
- fmt += "{}{{:{}.{}f}}".format(separator, len_out_in, time_precision)
- fmt += "{}{{}}{{:{}.{}f}}{{}}".format(
- separator, len_inter_at, decimal_precision
- )
- fmt += "{}{{:{}.{}f}}".format(separator, len_out_out, time_precision)
- fmt += "{}{{:{}.{}f}}".format(separator, len_in_in, time_precision)
- fmt += "{}{{:{}.{}f}}".format(separator, len_in_out, time_precision)
- return fmt
- def _calc_alignments_summary(self, align_helper):
- # Length is being cut in 3 groups so that further addition is easier to handle.
- # The length of every argument from the alignment helper is being checked if it
- # is longer than the longest until now. In that case the length is being saved.
- for key in db["task_info"]:
- if len(str(getattr(align_helper, key))) > db["task_info"][key]:
- db["task_info"][key] = len(str(getattr(align_helper, key)))
- for key in db["runtime_info"]:
- if len(str(getattr(align_helper, key))) > db["runtime_info"][key]:
- db["runtime_info"][key] = len(str(getattr(align_helper, key)))
- if args.summary_extended:
- for key in db["inter_times"]:
- if len(str(getattr(align_helper, key))) > db["inter_times"][key]:
- db["inter_times"][key] = len(str(getattr(align_helper, key)))
- def print(self):
- self._task_stats()
- fmt = self._format_stats()
- if not args.csv_summary:
- print("\nSummary")
- self._print_header()
- self._column_titles()
- for i in range(len(self._body)):
- fd_sum.write(fmt.format(*tuple(self._body[i])) + "\n")
- class Task(object):
- """ The class is used to handle the information of a given task."""
- def __init__(self, id, tid, cpu, comm):
- self.id = id
- self.tid = tid
- self.cpu = cpu
- self.comm = comm
- self.pid = None
- self._time_in = None
- self._time_out = None
- def schedule_in_at(self, time):
- """set the time where the task was scheduled in"""
- self._time_in = time
- def schedule_out_at(self, time):
- """set the time where the task was scheduled out"""
- self._time_out = time
- def time_out(self, unit="s"):
- """return time where a given task was scheduled out"""
- factor = time_uniter(unit)
- return self._time_out * decimal.Decimal(factor)
- def time_in(self, unit="s"):
- """return time where a given task was scheduled in"""
- factor = time_uniter(unit)
- return self._time_in * decimal.Decimal(factor)
- def runtime(self, unit="us"):
- factor = time_uniter(unit)
- return (self._time_out - self._time_in) * decimal.Decimal(factor)
- def update_pid(self, pid):
- self.pid = pid
- def _task_id(pid, cpu):
- """returns a "unique-enough" identifier, please do not change"""
- return "{}-{}".format(pid, cpu)
- def _filter_non_printable(unfiltered):
- """comm names may contain loony chars like '\x00000'"""
- filtered = ""
- for char in unfiltered:
- if char not in string.printable:
- continue
- filtered += char
- return filtered
- def _fmt_header():
- separator, fix_csv_align = _prepare_fmt_sep()
- fmt = "{{:>{}}}".format(LEN_SWITCHED_IN*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_SWITCHED_OUT*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_CPU*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_PID*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_TID*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_RUNTIME*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_OUT_IN*fix_csv_align)
- if args.extended_times:
- fmt += "{}{{:>{}}}".format(separator, LEN_OUT_OUT*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_IN_IN*fix_csv_align)
- fmt += "{}{{:>{}}}".format(separator, LEN_IN_OUT*fix_csv_align)
- return fmt
- def _fmt_body():
- separator, fix_csv_align = _prepare_fmt_sep()
- decimal_precision, time_precision = _prepare_fmt_precision()
- fmt = "{{}}{{:{}.{}f}}".format(LEN_SWITCHED_IN*fix_csv_align, decimal_precision)
- fmt += "{}{{:{}.{}f}}".format(
- separator, LEN_SWITCHED_OUT*fix_csv_align, decimal_precision
- )
- fmt += "{}{{:{}d}}".format(separator, LEN_CPU*fix_csv_align)
- fmt += "{}{{:{}d}}".format(separator, LEN_PID*fix_csv_align)
- fmt += "{}{{}}{{:{}d}}{{}}".format(separator, LEN_TID*fix_csv_align)
- fmt += "{}{{}}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align)
- fmt += "{}{{:{}.{}f}}".format(separator, LEN_RUNTIME*fix_csv_align, time_precision)
- if args.extended_times:
- fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_IN*fix_csv_align, time_precision)
- fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_OUT*fix_csv_align, time_precision)
- fmt += "{}{{:{}.{}f}}".format(separator, LEN_IN_IN*fix_csv_align, time_precision)
- fmt += "{}{{:{}.{}f}}{{}}".format(
- separator, LEN_IN_OUT*fix_csv_align, time_precision
- )
- else:
- fmt += "{}{{:{}.{}f}}{{}}".format(
- separator, LEN_OUT_IN*fix_csv_align, time_precision
- )
- return fmt
- def _print_header():
- fmt = _fmt_header()
- header = ("Switched-In", "Switched-Out", "CPU", "PID", "TID", "Comm", "Runtime",
- "Time Out-In")
- if args.extended_times:
- header += ("Time Out-Out", "Time In-In", "Time In-Out")
- fd_task.write(fmt.format(*header) + "\n")
- def _print_task_finish(task):
- """calculating every entry of a row and printing it immediately"""
- c_row_set = ""
- c_row_reset = ""
- out_in = -1
- out_out = -1
- in_in = -1
- in_out = -1
- fmt = _fmt_body()
- # depending on user provided highlight option we change the color
- # for particular tasks
- if str(task.tid) in args.highlight_tasks_map:
- c_row_set = _COLORS[args.highlight_tasks_map[str(task.tid)]]
- c_row_reset = _COLORS["reset"]
- if task.comm in args.highlight_tasks_map:
- c_row_set = _COLORS[args.highlight_tasks_map[task.comm]]
- c_row_reset = _COLORS["reset"]
- # grey-out entries if PID == TID, they
- # are identical, no threaded model so the
- # thread id (tid) do not matter
- c_tid_set = ""
- c_tid_reset = ""
- if task.pid == task.tid:
- c_tid_set = _COLORS["grey"]
- c_tid_reset = _COLORS["reset"]
- if task.tid in db["tid"]:
- # get last task of tid
- last_tid_task = db["tid"][task.tid][-1]
- # feed the timespan calculate, last in tid db
- # and second the current one
- timespan_gap_tid = Timespans()
- timespan_gap_tid.feed(last_tid_task)
- timespan_gap_tid.feed(task)
- out_in = timespan_gap_tid.out_in
- out_out = timespan_gap_tid.out_out
- in_in = timespan_gap_tid.in_in
- in_out = timespan_gap_tid.in_out
- if args.extended_times:
- line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu,
- task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
- task.runtime(time_unit), out_in, out_out, in_in, in_out,
- c_row_reset) + "\n"
- else:
- line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu,
- task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
- task.runtime(time_unit), out_in, c_row_reset) + "\n"
- try:
- fd_task.write(line_out)
- except(IOError):
- # don't mangle the output if user SIGINT this script
- sys.exit()
- def _record_cleanup(_list):
- """
- no need to store more then one element if --summarize
- is not enabled
- """
- if not args.summary and len(_list) > 1:
- _list = _list[len(_list) - 1 :]
- def _record_by_tid(task):
- tid = task.tid
- if tid not in db["tid"]:
- db["tid"][tid] = []
- db["tid"][tid].append(task)
- _record_cleanup(db["tid"][tid])
- def _record_by_cpu(task):
- cpu = task.cpu
- if cpu not in db["cpu"]:
- db["cpu"][cpu] = []
- db["cpu"][cpu].append(task)
- _record_cleanup(db["cpu"][cpu])
- def _record_global(task):
- """record all executed task, ordered by finish chronological"""
- db["global"].append(task)
- _record_cleanup(db["global"])
- def _handle_task_finish(tid, cpu, time, perf_sample_dict):
- if tid == 0:
- return
- _id = _task_id(tid, cpu)
- if _id not in db["running"]:
- # may happen, if we missed the switch to
- # event. Seen in combination with --exclude-perf
- # where the start is filtered out, but not the
- # switched in. Probably a bug in exclude-perf
- # option.
- return
- task = db["running"][_id]
- task.schedule_out_at(time)
- # record tid, during schedule in the tid
- # is not available, update now
- pid = int(perf_sample_dict["sample"]["pid"])
- task.update_pid(pid)
- del db["running"][_id]
- # print only tasks which are not being filtered and no print of trace
- # for summary only, but record every task.
- if not _limit_filtered(tid, pid, task.comm) and not args.summary_only:
- _print_task_finish(task)
- _record_by_tid(task)
- _record_by_cpu(task)
- _record_global(task)
- def _handle_task_start(tid, cpu, comm, time):
- if tid == 0:
- return
- if tid in args.tid_renames:
- comm = args.tid_renames[tid]
- _id = _task_id(tid, cpu)
- if _id in db["running"]:
- # handle corner cases where already running tasks
- # are switched-to again - saw this via --exclude-perf
- # recorded traces. We simple ignore this "second start"
- # event.
- return
- assert _id not in db["running"]
- task = Task(_id, tid, cpu, comm)
- task.schedule_in_at(time)
- db["running"][_id] = task
- def _time_to_internal(time_ns):
- """
- To prevent float rounding errors we use Decimal internally
- """
- return decimal.Decimal(time_ns) / decimal.Decimal(1e9)
- def _limit_filtered(tid, pid, comm):
- if args.filter_tasks:
- if str(tid) in args.filter_tasks or comm in args.filter_tasks:
- return True
- else:
- return False
- if args.limit_to_tasks:
- if str(tid) in args.limit_to_tasks or comm in args.limit_to_tasks:
- return False
- else:
- return True
- def _argument_filter_sanity_check():
- if args.limit_to_tasks and args.filter_tasks:
- sys.exit("Error: Filter and Limit at the same time active.")
- if args.extended_times and args.summary_only:
- sys.exit("Error: Summary only and extended times active.")
- if args.time_limit and ":" not in args.time_limit:
- sys.exit(
- "Error: No bound set for time limit. Please set bound by ':' e.g :123."
- )
- if args.time_limit and (args.summary or args.summary_only or args.summary_extended):
- sys.exit("Error: Cannot set time limit and print summary")
- if args.csv_summary:
- args.summary = True
- if args.csv == args.csv_summary:
- sys.exit("Error: Chosen files for csv and csv summary are the same")
- if args.csv and (args.summary_extended or args.summary) and not args.csv_summary:
- sys.exit("Error: No file chosen to write summary to. Choose with --csv-summary "
- "<file>")
- if args.csv and args.summary_only:
- sys.exit("Error: --csv chosen and --summary-only. Standard task would not be"
- "written to csv file.")
- def _argument_prepare_check():
- global time_unit, fd_task, fd_sum
- if args.filter_tasks:
- args.filter_tasks = args.filter_tasks.split(",")
- if args.limit_to_tasks:
- args.limit_to_tasks = args.limit_to_tasks.split(",")
- if args.time_limit:
- args.time_limit = args.time_limit.split(":")
- for rename_tuple in args.rename_comms_by_tids.split(","):
- tid_name = rename_tuple.split(":")
- if len(tid_name) != 2:
- continue
- args.tid_renames[int(tid_name[0])] = tid_name[1]
- args.highlight_tasks_map = dict()
- for highlight_tasks_tuple in args.highlight_tasks.split(","):
- tasks_color_map = highlight_tasks_tuple.split(":")
- # default highlight color to red if no color set by user
- if len(tasks_color_map) == 1:
- tasks_color_map.append("red")
- if args.highlight_tasks and tasks_color_map[1].lower() not in _COLORS:
- sys.exit(
- "Error: Color not defined, please choose from grey,red,green,yellow,blue,"
- "violet"
- )
- if len(tasks_color_map) != 2:
- continue
- args.highlight_tasks_map[tasks_color_map[0]] = tasks_color_map[1]
- time_unit = "us"
- if args.ns:
- time_unit = "ns"
- elif args.ms:
- time_unit = "ms"
- fd_task = sys.stdout
- if args.csv:
- args.stdio_color = "never"
- fd_task = open(args.csv, "w")
- print("generating csv at",args.csv,)
- fd_sum = sys.stdout
- if args.csv_summary:
- args.stdio_color = "never"
- fd_sum = open(args.csv_summary, "w")
- print("generating csv summary at",args.csv_summary)
- if not args.csv:
- args.summary_only = True
- def _is_within_timelimit(time):
- """
- Check if a time limit was given by parameter, if so ignore the rest. If not,
- process the recorded trace in its entirety.
- """
- if not args.time_limit:
- return True
- lower_time_limit = args.time_limit[0]
- upper_time_limit = args.time_limit[1]
- # check for upper limit
- if upper_time_limit == "":
- if time >= decimal.Decimal(lower_time_limit):
- return True
- # check for lower limit
- if lower_time_limit == "":
- if time <= decimal.Decimal(upper_time_limit):
- return True
- # quit if time exceeds upper limit. Good for big datasets
- else:
- quit()
- if lower_time_limit != "" and upper_time_limit != "":
- if (time >= decimal.Decimal(lower_time_limit) and
- time <= decimal.Decimal(upper_time_limit)):
- return True
- # quit if time exceeds upper limit. Good for big datasets
- elif time > decimal.Decimal(upper_time_limit):
- quit()
- def _prepare_fmt_precision():
- decimal_precision = 6
- time_precision = 3
- if args.ns:
- decimal_precision = 9
- time_precision = 0
- return decimal_precision, time_precision
- def _prepare_fmt_sep():
- separator = " "
- fix_csv_align = 1
- if args.csv or args.csv_summary:
- separator = ";"
- fix_csv_align = 0
- return separator, fix_csv_align
- def trace_unhandled(event_name, context, event_fields_dict, perf_sample_dict):
- pass
- def trace_begin():
- _parse_args()
- _check_color()
- _init_db()
- if not args.summary_only:
- _print_header()
- def trace_end():
- if args.summary or args.summary_extended or args.summary_only:
- Summary().print()
- def sched__sched_switch(event_name, context, common_cpu, common_secs, common_nsecs,
- common_pid, common_comm, common_callchain, prev_comm,
- prev_pid, prev_prio, prev_state, next_comm, next_pid,
- next_prio, perf_sample_dict):
- # ignore common_secs & common_nsecs cause we need
- # high res timestamp anyway, using the raw value is
- # faster
- time = _time_to_internal(perf_sample_dict["sample"]["time"])
- if not _is_within_timelimit(time):
- # user specific --time-limit a:b set
- return
- next_comm = _filter_non_printable(next_comm)
- _handle_task_finish(prev_pid, common_cpu, time, perf_sample_dict)
- _handle_task_start(next_pid, common_cpu, next_comm, time)
|