1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2009-2013 Intel Corporation
9 Auke Kok <auke-jan.h.kok@intel.com>
11 systemd is free software; you can redistribute it and/or modify it
12 under the terms of the GNU Lesser General Public License as published by
13 the Free Software Foundation; either version 2.1 of the License, or
14 (at your option) any later version.
16 systemd is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public License
22 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 Many thanks to those who contributed ideas and code:
28 - Ziga Mahkovec - Original bootchart author
29 - Anders Norgaard - PyBootchartgui
30 - Michael Meeks - bootchart2
31 - Scott James Remnant - Ubuntu C-based logger
32 - Arjan van der Ven - for the idea to merge bootgraph.pl functionality
37 #include <sys/types.h>
38 #include <sys/resource.h>
51 #include <systemd/sd-journal.h>
56 #include "conf-parser.h"
58 #include "path-util.h"
61 #include "bootchart.h"
66 struct ps_struct *ps_first;
72 static int exiting = 0;
76 bool arg_entropy = false;
78 bool arg_relative = false;
79 bool arg_filter = true;
80 bool arg_show_cmdline = false;
83 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
84 double arg_hz = 25.0; /* 20 seconds log time */
85 double arg_scale_x = 100.0; /* 100px = 1sec */
86 double arg_scale_y = 20.0; /* 16px = 1 process bar */
87 static struct list_sample_data *sampledata;
88 struct list_sample_data *head;
90 char arg_init_path[PATH_MAX] = "/sbin/init";
91 char arg_output_path[PATH_MAX] = "/run/log";
93 static void signal_handler(int sig) {
99 #define BOOTCHART_CONF "/etc/systemd/bootchart.conf"
101 #define BOOTCHART_MAX (16*1024*1024)
103 static void parse_conf(void) {
104 char *init = NULL, *output = NULL;
105 const ConfigTableItem items[] = {
106 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
107 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
108 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
109 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
110 { "Bootchart", "Output", config_parse_path, 0, &output },
111 { "Bootchart", "Init", config_parse_path, 0, &init },
112 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
113 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
114 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
115 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
116 { NULL, NULL, NULL, 0, NULL }
118 _cleanup_fclose_ FILE *f;
121 f = fopen(BOOTCHART_CONF, "re");
125 r = config_parse(NULL, BOOTCHART_CONF, f,
126 NULL, config_item_table_lookup, (void*) items, true, false, NULL);
128 log_warning("Failed to parse configuration file: %s", strerror(-r));
131 strscpy(arg_init_path, sizeof(arg_init_path), init);
133 strscpy(arg_output_path, sizeof(arg_output_path), output);
136 static int parse_args(int argc, char *argv[]) {
137 static struct option options[] = {
138 {"rel", no_argument, NULL, 'r'},
139 {"freq", required_argument, NULL, 'f'},
140 {"samples", required_argument, NULL, 'n'},
141 {"pss", no_argument, NULL, 'p'},
142 {"output", required_argument, NULL, 'o'},
143 {"init", required_argument, NULL, 'i'},
144 {"no-filter", no_argument, NULL, 'F'},
145 {"cmdline", no_argument, NULL, 'C'},
146 {"help", no_argument, NULL, 'h'},
147 {"scale-x", required_argument, NULL, 'x'},
148 {"scale-y", required_argument, NULL, 'y'},
149 {"entropy", no_argument, NULL, 'e'},
154 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FChx:y:", options, NULL)) >= 0) {
162 r = safe_atod(optarg, &arg_hz);
164 log_warning("failed to parse --freq/-f argument '%s': %s",
165 optarg, strerror(-r));
171 arg_show_cmdline = true;
174 r = safe_atoi(optarg, &arg_samples_len);
176 log_warning("failed to parse --samples/-n argument '%s': %s",
177 optarg, strerror(-r));
180 path_kill_slashes(optarg);
181 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
184 path_kill_slashes(optarg);
185 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
191 r = safe_atod(optarg, &arg_scale_x);
193 log_warning("failed to parse --scale-x/-x argument '%s': %s",
194 optarg, strerror(-r));
197 r = safe_atod(optarg, &arg_scale_y);
199 log_warning("failed to parse --scale-y/-y argument '%s': %s",
200 optarg, strerror(-r));
206 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
207 fprintf(stderr, " --rel, -r Record time relative to recording\n");
208 fprintf(stderr, " --freq, -f f Sample frequency [%f]\n", arg_hz);
209 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", arg_samples_len);
210 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", arg_scale_x);
211 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", arg_scale_y);
212 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
213 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
214 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", arg_output_path);
215 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", arg_init_path);
216 fprintf(stderr, " --no-filter, -F Disable filtering of processes from the graph\n");
217 fprintf(stderr, " that are of less importance or short-lived\n");
218 fprintf(stderr, " --cmdline, -C Display the full command line with arguments\n");
219 fprintf(stderr, " of processes, instead of only the process name\n");
220 fprintf(stderr, " --help, -h Display this message\n");
221 fprintf(stderr, "See bootchart.conf for more information.\n");
230 fprintf(stderr, "Error: Frequency needs to be > 0\n");
237 static void do_journal_append(char *file) {
238 struct iovec iovec[5];
241 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
244 bootchart_file = strappend("BOOTCHART_FILE=", file);
246 IOVEC_SET_STRING(iovec[j++], bootchart_file);
248 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
249 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
250 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
251 if (bootchart_message)
252 IOVEC_SET_STRING(iovec[j++], bootchart_message);
254 p = malloc(9 + BOOTCHART_MAX);
260 memcpy(p, "BOOTCHART=", 10);
262 f = open(file, O_RDONLY);
264 log_error("Failed to read bootchart data: %m");
267 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
269 log_error("Failed to read bootchart data: %s", strerror(-n));
275 iovec[j].iov_base = p;
276 iovec[j].iov_len = 10 + n;
279 r = sd_journal_sendv(iovec, j);
281 log_error("Failed to send bootchart: %s", strerror(-r));
284 int main(int argc, char *argv[]) {
285 _cleanup_free_ char *build = NULL;
286 struct sigaction sig = {
287 .sa_handler = signal_handler,
289 struct ps_struct *ps;
290 char output_file[PATH_MAX];
298 r = parse_args(argc, argv);
303 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
305 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
311 execl(arg_init_path, arg_init_path, NULL);
316 rlim.rlim_cur = 4096;
317 rlim.rlim_max = 4096;
318 (void) setrlimit(RLIMIT_NOFILE, &rlim);
320 /* start with empty ps LL */
321 ps_first = calloc(1, sizeof(struct ps_struct));
323 perror("calloc(ps_struct)");
327 /* handle TERM/INT nicely */
328 sigaction(SIGHUP, &sig, NULL);
330 interval = (1.0 / arg_hz) * 1000000000.0;
334 LIST_HEAD_INIT(head);
336 /* main program loop */
337 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
346 sampledata = new0(struct list_sample_data, 1);
347 if (sampledata == NULL) {
348 log_error("Failed to allocate memory for a node: %m");
352 sampledata->sampletime = gettime_ns();
353 sampledata->counter = samples;
355 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
357 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
358 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
359 of = fopen(output_file, "w");
363 sysfd = open("/sys", O_RDONLY);
366 parse_env_file("/etc/os-release", NEWLINE,
367 "PRETTY_NAME", &build,
370 /* wait for /proc to become available, discarding samples */
371 if (graph_start <= 0.0)
374 log_sample(samples, &sampledata);
376 sample_stop = gettime_ns();
378 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
379 timeleft = interval - elapsed;
381 newint_s = (time_t)(timeleft / 1000000000.0);
382 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
385 * check if we have not consumed our entire timeslice. If we
386 * do, don't sleep and take a new sample right away.
387 * we'll lose all the missed samples and overrun our total
390 if (newint_ns > 0 || newint_s > 0) {
391 req.tv_sec = newint_s;
392 req.tv_nsec = newint_ns;
394 res = nanosleep(&req, NULL);
396 if (errno == EINTR) {
397 /* caught signal, probably HUP! */
400 perror("nanosleep()");
405 /* calculate how many samples we lost and scrap them */
406 arg_samples_len -= (int)(newint_ns / interval);
408 LIST_PREPEND(link, head, sampledata);
411 /* do some cleanup, close fd's */
413 while (ps->next_ps) {
416 close(ps->schedstat);
425 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
426 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
427 of = fopen(output_file, "w");
431 fprintf(stderr, "opening output file '%s': %m\n", output_file);
437 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
439 do_journal_append(output_file);
448 /* nitpic cleanups */
449 ps = ps_first->next_ps;
450 while (ps->next_ps) {
451 struct ps_struct *old;
454 old->sample = ps->first;
456 while (old->sample->next) {
457 struct ps_sched_struct *oldsample = old->sample;
459 old->sample = old->sample->next;
469 while (sampledata->link_prev) {
470 struct list_sample_data *old_sampledata = sampledata;
471 sampledata = sampledata->link_prev;
472 free(old_sampledata);
475 /* don't complain when overrun once, happens most commonly on 1st sample */
477 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);