chiark / gitweb /
bcced18c2c5d21a84370aeafa0ca673bfffa7c40
[elogind.git] / src / analyze / systemd-analyze.in
1 #!@PYTHON_BINARY@
2 # -*-python-*-
3
4 #  This file is part of systemd.
5 #
6 #  Copyright 2010-2013 Lennart Poettering
7 #
8 #  systemd is free software; you can redistribute it and/or modify it
9 #  under the terms of the GNU Lesser General Public License as published by
10 #  the Free Software Foundation; either version 2.1 of the License, or
11 #  (at your option) any later version.
12 #
13 #  systemd is distributed in the hope that it will be useful, but
14 #  WITHOUT ANY WARRANTY; without even the implied warranty of
15 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 #  Lesser General Public License for more details.
17 #
18 #  You should have received a copy of the GNU Lesser General Public License
19 #  along with systemd; If not, see <http://www.gnu.org/licenses/>.
20
21 import sys, os
22 import argparse
23 from gi.repository import Gio
24 try:
25         import cairo
26 except ImportError:
27         cairo = None
28
29 def acquire_time_data():
30         manager = Gio.DBusProxy.new_for_bus_sync(bus, Gio.DBusProxyFlags.NONE,
31                 None, 'org.freedesktop.systemd1', '/org/freedesktop/systemd1', 'org.freedesktop.systemd1.Manager', None)
32         units = manager.ListUnits()
33
34         l = []
35
36         for i in units:
37                 if i[5] != "":
38                         continue
39
40                 properties = Gio.DBusProxy.new_for_bus_sync(bus, Gio.DBusProxyFlags.NONE,
41                         None, 'org.freedesktop.systemd1', i[6], 'org.freedesktop.DBus.Properties', None)
42
43                 ixt = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'InactiveExitTimestampMonotonic')
44                 aet = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'ActiveEnterTimestampMonotonic')
45                 axt = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'ActiveExitTimestampMonotonic')
46                 iet = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'InactiveEnterTimestampMonotonic')
47
48                 l.append((str(i[0]), ixt, aet, axt, iet))
49
50         return l
51
52 def acquire_start_time():
53         properties = Gio.DBusProxy.new_for_bus_sync(bus, Gio.DBusProxyFlags.NONE,
54                 None, 'org.freedesktop.systemd1', '/org/freedesktop/systemd1', 'org.freedesktop.DBus.Properties', None)
55
56         # Note that the firmware/loader times are returned as positive
57         # values but are actually considered negative from the point
58         # in time of kernel initialization. Also, the monotonic kernel
59         # time will always be 0 since that's the epoch of the
60         # monotonic clock. Since we want to know whether the kernel
61         # timestamp is set at all we will instead ask for the realtime
62         # clock for this timestamp.
63
64         firmware_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'FirmwareTimestampMonotonic')
65         loader_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'LoaderTimestampMonotonic')
66         kernel_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'KernelTimestamp')
67         initrd_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'InitRDTimestampMonotonic')
68         userspace_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'UserspaceTimestampMonotonic')
69         finish_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'FinishTimestampMonotonic')
70
71         if finish_time == 0:
72                 sys.exit("Bootup is not yet finished. Please try again later.")
73
74         assert firmware_time >= loader_time
75         assert initrd_time <= userspace_time
76         assert userspace_time <= finish_time
77
78         return firmware_time, loader_time, kernel_time, initrd_time, userspace_time, finish_time
79
80 def draw_box(context, j, k, l, m, r = 0, g = 0, b = 0):
81         context.save()
82         context.set_source_rgb(r, g, b)
83         context.rectangle(j, k, l, m)
84         context.fill()
85         context.restore()
86
87 def draw_text(context, x, y, text, size = 12, r = 0, g = 0, b = 0, vcenter = 0.5, hcenter = 0.5):
88         context.save()
89
90         context.set_source_rgb(r, g, b)
91         context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
92         context.set_font_size(size)
93
94         if vcenter or hcenter:
95                 x_bearing, y_bearing, width, height = context.text_extents(text)[:4]
96
97                 if hcenter:
98                         x = x - width*hcenter - x_bearing
99
100                 if vcenter:
101                         y = y - height*vcenter - y_bearing
102
103         context.move_to(x, y)
104         context.show_text(text)
105
106         context.restore()
107
108 def time():
109
110         firmware_time, loader_time, kernel_time, initrd_time, userspace_time, finish_time = acquire_start_time()
111
112         sys.stdout.write("Startup finished in ")
113
114         if firmware_time > 0:
115                 sys.stdout.write("%lums (firmware) + " % ((firmware_time - loader_time) / 1000))
116         if loader_time > 0:
117                 sys.stdout.write("%lums (loader) + " % (loader_time / 1000))
118         if initrd_time > 0:
119                 sys.stdout.write("%lums (kernel) + %lums (initrd) + " % (initrd_time / 1000, (userspace_time - initrd_time) / 1000))
120         elif kernel_time > 0:
121                 sys.stdout.write("%lums (kernel) + " % (userspace_time  / 1000))
122
123         sys.stdout.write("%lums (userspace) " % ((finish_time - userspace_time) / 1000))
124
125         if kernel_time > 0:
126                 sys.stdout.write("= %lums\n" % ((firmware_time + finish_time) / 1000))
127         else:
128                 sys.stdout.write("= %lums\n" % ((finish_time - userspace_time) / 1000))
129
130 def blame():
131
132         data = acquire_time_data()
133         s = sorted(data, key = lambda i: i[2] - i[1], reverse = True)
134
135         for name, ixt, aet, axt, iet in s:
136
137                 if ixt <= 0 or aet <= 0:
138                         continue
139
140                 if aet <= ixt:
141                         continue
142
143                 sys.stdout.write("%6lums %s\n" % ((aet - ixt) / 1000, name))
144
145 def plot():
146         if cairo is None:
147                 sys.exit("Failed to initilize python-cairo required for 'plot' verb.")
148         firmware_time, loader_time, kernel_time, initrd_time, userspace_time, finish_time = acquire_start_time()
149         data = acquire_time_data()
150         s = sorted(data, key = lambda i: i[1])
151
152         # Account for kernel and initramfs bars if they exist
153         if initrd_time > 0:
154                 count = 3
155         else:
156                 count = 2
157
158         for name, ixt, aet, axt, iet in s:
159
160                 if (ixt >= userspace_time and ixt <= finish_time) or \
161                                 (aet >= userspace_time and aet <= finish_time) or \
162                                 (axt >= userspace_time and axt <= finish_time):
163                         count += 1
164
165         border = 100
166         bar_height = 20
167         bar_space = bar_height * 0.1
168
169         # 1000px = 10s, 1px = 10ms
170         width = finish_time/10000 + border*2
171         height = count * (bar_height + bar_space) + border * 2
172
173         if width < 1000:
174                 width = 1000
175
176         surface = cairo.SVGSurface(sys.stdout, width, height)
177         context = cairo.Context(surface)
178
179         draw_box(context, 0, 0, width, height, 1, 1, 1)
180
181         context.translate(border + 0.5, border + 0.5)
182
183         context.save()
184         context.set_line_width(1)
185         context.set_source_rgb(0.7, 0.7, 0.7)
186
187         for x in range(0, int(finish_time/10000) + 100, 100):
188                 context.move_to(x, 0)
189                 context.line_to(x, height-border*2)
190
191         context.move_to(0, 0)
192         context.line_to(width-border*2, 0)
193
194         context.move_to(0, height-border*2)
195         context.line_to(width-border*2, height-border*2)
196
197         context.stroke()
198         context.restore()
199
200         osrel = "Linux"
201         if os.path.exists("/etc/os-release"):
202                 for line in open("/etc/os-release"):
203                         if line.startswith('PRETTY_NAME='):
204                                 osrel = line[12:]
205                                 osrel = osrel.strip('\"\n')
206                                 break
207
208         banner = "{} {} ({} {}) {}".format(osrel, *(os.uname()[1:5]))
209         draw_text(context, 0, -15, banner, hcenter = 0, vcenter = 1)
210
211         for x in range(0, int(finish_time/10000) + 100, 100):
212                 draw_text(context, x, -5, "%lus" % (x/100), vcenter = 0, hcenter = 0)
213
214         y = 0
215
216         # draw boxes for kernel and initramfs boot time
217         if initrd_time > 0:
218                 draw_box(context, 0, y, initrd_time/10000, bar_height, 0.7, 0.7, 0.7)
219                 draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0)
220                 y += bar_height + bar_space
221
222                 draw_box(context, initrd_time/10000, y, userspace_time/10000-initrd_time/10000, bar_height, 0.7, 0.7, 0.7)
223                 draw_text(context, initrd_time/10000 + 10, y + bar_height/2, "initramfs", hcenter = 0)
224                 y += bar_height + bar_space
225
226         else:
227                 draw_box(context, 0, y, userspace_time/10000, bar_height, 0.6, 0.6, 0.6)
228                 draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0)
229                 y += bar_height + bar_space
230
231         draw_box(context, userspace_time/10000, y, finish_time/10000-userspace_time/10000, bar_height, 0.7, 0.7, 0.7)
232         draw_text(context, userspace_time/10000 + 10, y + bar_height/2, "userspace", hcenter = 0)
233         y += bar_height + bar_space
234
235         for name, ixt, aet, axt, iet in s:
236
237                 drawn = False
238                 left = -1
239
240                 if ixt >= userspace_time and ixt <= finish_time:
241
242                         # Activating
243                         a = ixt
244                         b = min(filter(lambda x: x >= ixt, (aet, axt, iet, finish_time))) - ixt
245
246                         draw_box(context, a/10000, y, b/10000, bar_height, 1, 0, 0)
247                         drawn = True
248
249                         if left < 0:
250                                 left = a
251
252                 if aet >= userspace_time and aet <= finish_time:
253
254                         # Active
255                         a = aet
256                         b = min(filter(lambda x: x >= aet, (axt, iet, finish_time))) - aet
257
258                         draw_box(context, a/10000, y, b/10000, bar_height, .8, .6, .6)
259                         drawn = True
260
261                         if left < 0:
262                                 left = a
263
264                 if axt >= userspace_time and axt <= finish_time:
265
266                         # Deactivating
267                         a = axt
268                         b = min(filter(lambda x: x >= axt, (iet, finish_time))) - axt
269
270                         draw_box(context, a/10000, y, b/10000, bar_height, .6, .4, .4)
271                         drawn = True
272
273                         if left < 0:
274                                 left = a
275
276                 if drawn:
277                         x = left/10000
278
279                         if x < width/2-border:
280                                 draw_text(context, x + 10, y + bar_height/2, name, hcenter = 0)
281                         else:
282                                 draw_text(context, x - 10, y + bar_height/2, name, hcenter = 1)
283
284                         y += bar_height + bar_space
285
286         draw_text(context, 0, height-border*2, "Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating", hcenter = 0, vcenter = -1)
287
288         if initrd_time > 0:
289                 draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (initramfs) + %lums (userspace) = %lums" % ( \
290                         initrd_time/1000, \
291                         (userspace_time - initrd_time)/1000, \
292                         (finish_time - userspace_time)/1000, \
293                         finish_time/1000), hcenter = 0, vcenter = -1)
294         else:
295                 draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (userspace) = %lums" % ( \
296                         userspace_time/1000, \
297                         (finish_time - userspace_time)/1000, \
298                         finish_time/1000), hcenter = 0, vcenter = -1)
299
300         surface.finish()
301
302 parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
303                                  version='systemd-analyze @PACKAGE_VERSION@',
304                                  description='Process systemd profiling information',
305                                  epilog='''\
306 time - print time spent in the kernel before reaching userspace
307 blame - print list of running units ordered by time to init
308 plot - output SVG graphic showing service initialization
309 ''')
310
311 parser.add_argument('action', choices=('time', 'blame', 'plot'),
312                     default='time', nargs='?',
313                     help='action to perform (default: time)')
314 parser.add_argument('--user', action='store_true',
315                     help='use the session bus')
316
317 args = parser.parse_args()
318
319 if args.user:
320         bus = Gio.BusType.SESSION
321 else:
322         bus = Gio.BusType.SYSTEM
323
324 verb = {'time' : time,
325         'blame': blame,
326         'plot' : plot,
327         }
328 verb.get(args.action)()