1#!/usr/bin/python
2# SPDX-License-Identifier: LGPL-2.1-or-later
3
4"""
5A helper to compare 'systemd-analyze dump' outputs.
6
7systemd-analyze dump >/var/tmp/dump1
8(reboot)
9tools/analyze-dump-sort.py /var/tmp/dump1 → this does a diff from dump1 to current
10
11systemd-analyze dump >/var/tmp/dump2
12tools/analyze-dump-sort.py /var/tmp/{dump1,dump2} → this does a diff from dump1 to dump2
13"""
14
15import argparse
16import tempfile
17import subprocess
18
19def sort_dump(sourcefile, destfile=None):
20    if destfile is None:
21        destfile = tempfile.NamedTemporaryFile('wt')
22
23    units = {}
24    unit = []
25
26    same = []
27
28    for line in sourcefile:
29        line = line.rstrip()
30
31        header = line.split(':')[0]
32        if 'Timestamp' in header or 'Invocation ID' in header or 'PID' in header:
33            line = header + ': …'
34
35        if line.startswith('->'):
36            if unit:
37                units[unit[0]] = unit
38            unit = [line]
39        elif line.startswith('\t'):
40            assert unit
41
42            if same and same[0].startswith(header):
43                same.append(line)
44            else:
45                unit.extend(sorted(same, key=str.lower))
46                same = [line]
47        else:
48            print(line, file=destfile)
49
50    if unit:
51        units[unit[0]] = unit
52
53    for unit in sorted(units.values()):
54        print('\n'.join(unit), file=destfile)
55
56    destfile.flush()
57    return destfile
58
59def parse_args():
60    p = argparse.ArgumentParser(description=__doc__)
61    p.add_argument('one')
62    p.add_argument('two', nargs='?')
63    p.add_argument('--user', action='store_true')
64    return p.parse_args()
65
66if __name__ == '__main__':
67    opts = parse_args()
68
69    one = sort_dump(open(opts.one))
70    if opts.two:
71        two = sort_dump(open(opts.two))
72    else:
73        user = ['--user'] if opts.user else []
74        two = subprocess.run(['systemd-analyze', 'dump', *user],
75                             capture_output=True, text=True, check=True)
76        two = sort_dump(two.stdout.splitlines())
77    with subprocess.Popen(['diff', '-U10', one.name, two.name], stdout=subprocess.PIPE) as diff:
78        subprocess.Popen(['less'], stdin=diff.stdout)
79