From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0C7ACFF8870 for ; Tue, 28 Apr 2026 07:23:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Type:Cc:To:From: Subject:Message-ID:References:Mime-Version:In-Reply-To:Date:Reply-To: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=4vO51mkUv8czx4QMNQNbvlHwQFmUDbpKAVfeUaPjKEk=; b=yQxKgHwad4n38WbQ4W+QE6YI8D BeFMUu94qO8UgwxfBlkOAErWVTv+SFj7d08E4eDs/S1WWBmVD1UhN6VvmmQUenW4z3sJu0sVgU2dw bdAq35zf10n2uIRDMRV+HA1bfw0O7Nn85Pf3H51ZLDZ872wcRKXnWDs+fE1W6Mu7nAIlaBWNfaCpT 658W6J0HGQ6PUEE4hpb+F0EKWPUpRe5dr+7LmWtCiSbRh8oOtZpB94VFpNwkEeFKmXl+iPLLwisWo XFCBUP7tIVLzixHAtBB/id+DpmUBowbXJwGPS+us1nl3pZBQw1io/2CFRAaiChiROdihqhLlUVaH+ 5lRH7qOg==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98.2 #2 (Red Hat Linux)) id 1wHcmu-00000000kHv-2zDl; Tue, 28 Apr 2026 07:23:20 +0000 Received: from mail-pg1-x54a.google.com ([2607:f8b0:4864:20::54a]) by bombadil.infradead.org with esmtps (Exim 4.98.2 #2 (Red Hat Linux)) id 1wHckg-00000000hvk-2lF1 for linux-arm-kernel@lists.infradead.org; Tue, 28 Apr 2026 07:21:21 +0000 Received: by mail-pg1-x54a.google.com with SMTP id 41be03b00d2f7-c79798cd9b9so4095829a12.2 for ; Tue, 28 Apr 2026 00:21:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1777360861; x=1777965661; darn=lists.infradead.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=4vO51mkUv8czx4QMNQNbvlHwQFmUDbpKAVfeUaPjKEk=; b=LEwl+h/l2YUdFPiNjhf8cOntY+M5dH5/GOgQ/UekJ1R78jLVvt9Sx+HanzDKHGGQ6d 3x48J+dHCCh6ztOE4YE5xT32D3i3tHCjvjKCVAijhfl/7o8NYAdI7TNWpkZZgl4aJWed nlXkDQVVHJLNDX+2t50N9NKJyEviWYsn7BHx58qld1OlfwVhugUHVIb+zdgiymYg0+6z V9sNdMMuG13r2gLLCLd9emTyDv7ghjAyP02vD0B3XJ772+J0syZSMBysgbPV3sJO0ApY FlEdOfrryeHl6SMgupdWPTZ2Bk1FOAOIJDPiPrjuT5OoPskkJZHS8JZjt/jDy06z8AXA rc3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777360861; x=1777965661; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=4vO51mkUv8czx4QMNQNbvlHwQFmUDbpKAVfeUaPjKEk=; b=gDo2BQW7BppKpoMDJu/+l2/+rFg7hiHNCAHN1et6aS0T6O8LQOldliAWPVqFkk1YYf i7xeE9YHeVSG5y2gAAQxJSSDx3EptVwCSUtkF/eu/koJ9g4MmX52tvXtnhfNNLlaNiHY UM+mR1as/KumpgVmR64Cp9TZJpey4TZyGa8NDplxsrQf8wf2fTrlhG8LkSOEhZqADyD0 cnqc7TeESxOWas09zCUqTui+UAZaHglT+1z75bBMpS3FOxmjH4ATEbuf9Ejq28dykndr 6BYwdp7lWYXN1vJOPfKbwDEiTfaRGNTd1o0jLqzM75spJuxS5PZiuQBLHiK+ZfsPlK1Y SDug== X-Forwarded-Encrypted: i=1; AFNElJ9uRi2h/8FqWHDhu0QS67/Ep1RljuoT/UAg/RQiwjF1G16vu5zz9kMvFPxMBRJiTJAguOItSwYVLMR03LbLa3W/@lists.infradead.org X-Gm-Message-State: AOJu0YxdARyU79qRyEGSw7Tlx1zNUakQxaZD5BRDcky+4OH5PtFh9Mdb aMpiZlP3JIcZaMG1koWRe6h/qbD1oO0afeTVkt9lTB0YPdMtDZTELbjJlaKsXBZV2PhQc65Zcoi AnPEr8UwXdw== X-Received: from pgac11.prod.google.com ([2002:a05:6a02:294b:b0:c76:a6b1:ed23]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a20:a120:b0:3a2:f14a:4290 with SMTP id adf61e73a8af0-3a39c27ff64mr2278598637.38.1777360860923; Tue, 28 Apr 2026 00:21:00 -0700 (PDT) Date: Tue, 28 Apr 2026 00:18:56 -0700 In-Reply-To: <20260428071903.1886173-1-irogers@google.com> Mime-Version: 1.0 References: <20260425224951.174663-1-irogers@google.com> <20260428071903.1886173-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.545.g6539524ca2-goog Message-ID: <20260428071903.1886173-52-irogers@google.com> Subject: [PATCH v8 51/58] perf wakeup-latency: Port wakeup-latency to use python module From: Ian Rogers To: acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, alice.mei.rogers@gmail.com, dapeng1.mi@linux.intel.com, james.clark@linaro.org, leo.yan@linux.dev, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org, tmricht@linux.ibm.com, Ian Rogers Content-Type: text/plain; charset="UTF-8" X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260428_002102_742565_2BDEA9F3 X-CRM114-Status: GOOD ( 16.86 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org Port the legacy Perl script wakeup-latency.pl to a python script using the perf module in tools/perf/python. The new script uses a class-based architecture and leverages the perf.session API for event processing. It measures wakeup latency by tracking timestamps of sched:sched_wakeup and sched:sched_switch events. Complications: - Used min() and max() built-in functions instead of if blocks to satisfy pylint recommendations. - pylint warns about the module name not being snake_case, but it is kept for consistency with the original script name. Assisted-by: Gemini:gemini-3.1-pro-preview Signed-off-by: Ian Rogers --- v2: - Fixed Wakeup Latency Logic: Modified the script to track wakeup timestamps per task (using sample.pid as the key) instead of per CPU. This ensures that context switches are correctly paired with the specific task that was woken up, even if multiple tasks are woken up on the same CPU or if a task is migrated to a different CPU before running. - Prevented Memory Growth: Added del self.last_wakeup[next_pid] after successful latency calculation to prevent the dictionary from growing unbounded over time. - Added Error Tracking: Added try-except blocks around tracepoint field access in process_event() and tracked missing fields in self. unhandled instead of ignoring them. --- tools/perf/python/wakeup-latency.py | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100755 tools/perf/python/wakeup-latency.py diff --git a/tools/perf/python/wakeup-latency.py b/tools/perf/python/wakeup-latency.py new file mode 100755 index 000000000000..1b0db115abcf --- /dev/null +++ b/tools/perf/python/wakeup-latency.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Display avg/min/max wakeup latency.""" + +import argparse +from collections import defaultdict +import sys +from typing import Optional, Dict +import perf + +class WakeupLatency: + """Tracks and displays wakeup latency statistics.""" + def __init__(self) -> None: + self.last_wakeup: Dict[int, int] = defaultdict(int) + self.max_wakeup_latency = 0 + self.min_wakeup_latency = 1000000000 + self.total_wakeup_latency = 0 + self.total_wakeups = 0 + self.unhandled: Dict[str, int] = defaultdict(int) + self.session: Optional[perf.session] = None + + def process_event(self, sample: perf.sample_event) -> None: + """Process events.""" + event_name = str(sample.evsel) + sample_time = sample.sample_time + + if "sched:sched_wakeup" in event_name: + try: + pid = sample.pid + self.last_wakeup[pid] = sample_time + except AttributeError: + self.unhandled[event_name] += 1 + elif "sched:sched_switch" in event_name: + try: + next_pid = sample.next_pid + wakeup_ts = self.last_wakeup.get(next_pid, 0) + if wakeup_ts: + latency = sample_time - wakeup_ts + self.max_wakeup_latency = max(self.max_wakeup_latency, latency) + self.min_wakeup_latency = min(self.min_wakeup_latency, latency) + self.total_wakeup_latency += latency + self.total_wakeups += 1 + del self.last_wakeup[next_pid] + except AttributeError: + self.unhandled[event_name] += 1 + else: + self.unhandled[event_name] += 1 + + def print_totals(self) -> None: + """Print summary statistics.""" + print("wakeup_latency stats:\n") + print(f"total_wakeups: {self.total_wakeups}") + if self.total_wakeups: + avg = self.total_wakeup_latency // self.total_wakeups + print(f"avg_wakeup_latency (ns): {avg}") + else: + print("avg_wakeup_latency (ns): N/A") + print(f"min_wakeup_latency (ns): {self.min_wakeup_latency}") + print(f"max_wakeup_latency (ns): {self.max_wakeup_latency}") + + if self.unhandled: + print("\nunhandled events:\n") + print(f"{'event':<40s} {'count':>10s}") + print(f"{'-'*40} {'-'*10}") + for event_name, count in self.unhandled.items(): + print(f"{event_name:<40s} {count:10d}") + + def run(self, input_file: str) -> None: + """Run the session.""" + self.session = perf.session(perf.data(input_file), sample=self.process_event) + self.session.process_events() + self.print_totals() + +def main() -> None: + """Main function.""" + parser = argparse.ArgumentParser(description="Trace wakeup latency") + parser.add_argument("-i", "--input", default="perf.data", help="Input file") + args = parser.parse_args() + + analyzer = WakeupLatency() + try: + analyzer.run(args.input) + except IOError as e: + print(e, file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() -- 2.54.0.545.g6539524ca2-goog