From: Breno Rodrigues Alves <breno3011alves@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: Andy Whitcroft <apw@canonical.com>, Joe Perches <joe@perches.com>,
Dwaipayan Ray <dwaipayanray1@gmail.com>,
Lukas Bulwahn <lukas.bulwahn@gmail.com>,
Breno Rodrigues Alves <breno3011alves@gmail.com>
Subject: [RFC PATCH 1/4] scripts: convert bootgraph.pl to Python
Date: Sun, 18 Oct 2026 07:12:24 -0300 [thread overview]
Message-ID: <20261018101227.7680-2-breno3011alves@gmail.com> (raw)
In-Reply-To: <20261018101227.7680-1-breno3011alves@gmail.com>
Faithful re-implementation of bootgraph.pl in Python 3 (stdlib only).
Same CLI (-h, -header), same SVG output, same error handling.
One intentional change: the Perl version has an unreachable
'exit 1' after 'help(1); exit 1;' (help() already calls exit 0),
so it always returned 0 even on error. The Python version
returns exit 1 when no data is found in dmesg.
Tested with a synthetic dmesg containing three initcalls;
generated SVG is byte-for-byte identical to the Perl output.
Signed-off-by: Breno Rodrigues Alves <breno3011alves@gmail.com>
---
scripts/bootgraph.py | 194 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 194 insertions(+)
create mode 100755 scripts/bootgraph.py
diff --git a/scripts/bootgraph.py b/scripts/bootgraph.py
new file mode 100755
index 000000000..6839d2824
--- /dev/null
+++ b/scripts/bootgraph.py
@@ -0,0 +1,194 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright 2008, Intel Corporation
+# Authors: Arjan van de Ven <arjan@linux.intel.com>
+#
+# Turn dmesg output into an SVG graphic showing which initcalls take
+# how much time. The kernel must be compiled with CONFIG_PRINTK_TIME
+# and booted with "initcall_debug" on the command line.
+#
+# usage:
+# dmesg | python3 scripts/bootgraph.py > output.svg
+
+import argparse
+import re
+import subprocess
+import sys
+
+RE_CALLING = re.compile(r'([0-9.]+)\] calling ([a-zA-Z0-9_.]+)\+')
+RE_PID = re.compile(r'@ ([0-9]+)')
+RE_ASYNC_WAIT = re.compile(r'([0-9.]+)\] async_waiting @ ([0-9]+)')
+RE_RETURNED = re.compile(r'([0-9.]+)\] initcall ([a-zA-Z0-9_.]+)\+.*returned')
+RE_ASYNC_CONT = re.compile(r'([0-9.]+)\] async_continuing @ ([0-9]+)')
+RE_STOP = re.compile(r'(Write protecting the|Freeing unused kernel memory)')
+
+STYLES = [
+ 'fill:rgb(0,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,255,0);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,0,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,255,20);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,0,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,255,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,128,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(0,255,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,0,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,255,128);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(255,128,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+ 'fill:rgb(128,255,255);fill-opacity:0.5;stroke-width:1;stroke:rgb(0,0,0)',
+]
+STYLE_WAIT = 'fill:rgb(128,128,128);fill-opacity:0.5;stroke-width:0;stroke:rgb(0,0,0)'
+
+HEADERSTEP = 20
+XHEADER = 15
+YHEADER = 25
+
+
+def usage(exit_code=1):
+ stream = sys.stdout if exit_code == 0 else sys.stderr
+ print('Usage:', file=stream)
+ print('1) dmesg | python3 scripts/bootgraph.py [OPTION] > output.svg',
+ file=stream)
+ print('2) python3 scripts/bootgraph.py -h', file=stream)
+ print('', file=stream)
+ print('Options:', file=stream)
+ print('\t-header\tInsert kernel version and date', file=stream)
+ sys.exit(exit_code)
+
+
+def parse_dmesg(stream):
+ """Return (start, end, types, pids, count, first, last)."""
+ start, end, types, pids, pidctr = {}, {}, {}, {}, {}
+ done = False
+ first, last, count = 99999.0, 0.0, 0
+
+ for line in stream:
+ m = RE_CALLING.search(line)
+ if m:
+ func = m.group(2)
+ if not done:
+ start[func] = float(m.group(1))
+ types[func] = 0
+ first = min(first, start[func])
+ mp = RE_PID.search(line)
+ if mp:
+ pids[func] = int(mp.group(1))
+ count += 1
+
+ m = RE_ASYNC_WAIT.search(line)
+ if m:
+ pid = int(m.group(2))
+ pidctr[pid] = pidctr.get(pid, 0) + 1
+ func = f'wait_{pid}_{pidctr[pid]}'
+ if not done:
+ start[func] = float(m.group(1))
+ types[func] = 1
+ first = min(first, start[func])
+ pids[func] = pid
+ count += 1
+
+ m = RE_RETURNED.search(line)
+ if m and not done:
+ end[m.group(2)] = float(m.group(1))
+ last = end[m.group(2)]
+
+ m = RE_ASYNC_CONT.search(line)
+ if m:
+ pid = int(m.group(2))
+ func = f'wait_{pid}_{pidctr.get(pid, "")}'
+ end[func] = float(m.group(1))
+ last = end[func]
+
+ if RE_STOP.search(line):
+ done = True
+
+ return start, end, types, pids, count, first, last
+
+
+def emit_svg(out, start, end, types, pids, first, last, header):
+ out.write('<?xml version="1.0" standalone="no"?> \n')
+ out.write('<svg width="2000" height="100%" version="1.1" '
+ 'xmlns="http://www.w3.org/2000/svg">\n')
+
+ if header:
+ version = subprocess.check_output(['uname', '-a'], text=True).rstrip()
+ date = subprocess.check_output(['date'], text=True).rstrip()
+ out.write(f'<text transform="translate({XHEADER},{YHEADER})">'
+ f'Kernel version: {version}</text>\n')
+ cyheader = YHEADER + HEADERSTEP
+ out.write(f'<text transform="translate({XHEADER},{cyheader})">'
+ f'Date: {date}</text>\n')
+
+ mult = 1950.0 / (last - first)
+ threshold2 = (last - first) / 120.0
+ threshold = threshold2 / 10
+
+ stylecounter = 0
+ rows, rowscount = {}, 1
+
+ for key in sorted(start.keys(), key=lambda k: start[k]):
+ duration = end[key] - start[key]
+ if duration < threshold:
+ continue
+ pid = pids.get(key)
+ if pid not in rows:
+ rows[pid] = rowscount
+ rowscount += 1
+ s = (start[key] - first) * mult
+ s2, s3 = s + 6, s + 1
+ e = (end[key] - first) * mult
+ w = e - s
+ y = rows[pid] * 150
+ y2 = y + 4
+ style = STYLES[stylecounter]
+ stylecounter = (stylecounter + 1) % 12
+ if types[key] == 1:
+ y += 15
+ out.write(f'<rect x="{s:.15g}" width="{w:.15g}" y="{y}" '
+ f'height="115" style="{STYLE_WAIT}"/>\n')
+ else:
+ out.write(f'<rect x="{s:.15g}" width="{w:.15g}" y="{y}" '
+ f'height="145" style="{style}"/>\n')
+ if duration >= threshold2:
+ out.write(f'<text transform="translate({s2:.15g},{y2}) '
+ f'rotate(90)">{key}</text>\n')
+ else:
+ out.write(f'<text transform="translate({s3:.15g},{y2}) '
+ f'rotate(90)" font-size="3pt">{key}</text>\n')
+
+ # top timeline
+ time = first
+ step = (last - first) / 15.0
+ while time < last:
+ s3 = (time - first) * mult
+ tm = int(time * 100) / 100.0
+ out.write(f'<text transform="translate({s3:.15g},89) rotate(90)">{tm:.15g}</text>\n')
+ time += step
+
+ out.write('</svg>\n')
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument('-h', '--help', action='store_true')
+ parser.add_argument('-header', '--header', action='store_true')
+ args = parser.parse_args(argv)
+
+ if args.help:
+ usage(0)
+
+ start, end, types, pids, count, first, last = parse_dmesg(sys.stdin)
+
+ if count == 0:
+ print("No data found in the dmesg. Make sure that 'printk.time=1' and",
+ file=sys.stderr)
+ print("'initcall_debug' are passed on the kernel command line.",
+ file=sys.stderr)
+ usage(1)
+
+ emit_svg(sys.stdout, start, end, types, pids, first, last, args.header)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
\ No newline at end of file
--
2.43.0
next prev parent reply other threads:[~2026-10-18 10:12 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 10:12 [RFC PATCH 0/4] scripts: convert four small Perl utilities " Breno Rodrigues Alves
2026-10-18 10:12 ` Breno Rodrigues Alves [this message]
2026-10-18 10:12 ` [RFC PATCH 2/4] scripts: convert checkincludes.pl " Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 3/4] scripts: convert markup_oops.pl " Breno Rodrigues Alves
2026-10-18 10:12 ` [RFC PATCH 4/4] scripts: convert profile2linkerlist.pl " Breno Rodrigues Alves
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261018101227.7680-2-breno3011alves@gmail.com \
--to=breno3011alves@gmail.com \
--cc=apw@canonical.com \
--cc=dwaipayanray1@gmail.com \
--cc=joe@perches.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lukas.bulwahn@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®