1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2009-2013 Intel Coproration
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)
239 struct iovec iovec[5];
242 _cleanup_free_ char *bootchart_file = NULL, *bootchart_message = NULL,
245 bootchart_file = strappend("BOOTCHART_FILE=", file);
247 IOVEC_SET_STRING(iovec[j++], bootchart_file);
249 IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518");
250 IOVEC_SET_STRING(iovec[j++], "PRIORITY=7");
251 bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL);
252 if (bootchart_message)
253 IOVEC_SET_STRING(iovec[j++], bootchart_message);
255 p = malloc(9 + BOOTCHART_MAX);
261 memcpy(p, "BOOTCHART=", 10);
263 f = open(file, O_RDONLY);
265 log_error("Failed to read bootchart data: %m\n");
268 n = loop_read(f, p + 10, BOOTCHART_MAX, false);
270 log_error("Failed to read bootchart data: %s\n", strerror(-n));
276 iovec[j].iov_base = p;
277 iovec[j].iov_len = 10 + n;
280 r = sd_journal_sendv(iovec, j);
282 log_error("Failed to send bootchart: %s", strerror(-r));
285 int main(int argc, char *argv[]) {
286 _cleanup_free_ char *build = NULL;
287 struct sigaction sig = {
288 .sa_handler = signal_handler,
290 struct ps_struct *ps;
291 char output_file[PATH_MAX];
299 r = parse_args(argc, argv);
304 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
306 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
312 execl(arg_init_path, arg_init_path, NULL);
317 rlim.rlim_cur = 4096;
318 rlim.rlim_max = 4096;
319 (void) setrlimit(RLIMIT_NOFILE, &rlim);
321 /* start with empty ps LL */
322 ps_first = calloc(1, sizeof(struct ps_struct));
324 perror("calloc(ps_struct)");
328 /* handle TERM/INT nicely */
329 sigaction(SIGHUP, &sig, NULL);
331 interval = (1.0 / arg_hz) * 1000000000.0;
335 LIST_HEAD_INIT(struct list_sample_data, head);
337 /* main program loop */
338 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
347 sampledata = new0(struct list_sample_data, 1);
348 if (sampledata == NULL) {
349 log_error("Failed to allocate memory for a node: %m");
353 sampledata->sampletime = gettime_ns();
354 sampledata->counter = samples;
356 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
358 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
359 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
360 of = fopen(output_file, "w");
364 sysfd = open("/sys", O_RDONLY);
367 parse_env_file("/etc/os-release", NEWLINE,
368 "PRETTY_NAME", &build,
371 /* wait for /proc to become available, discarding samples */
372 if (graph_start <= 0.0)
375 log_sample(samples, &sampledata);
377 sample_stop = gettime_ns();
379 elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0;
380 timeleft = interval - elapsed;
382 newint_s = (time_t)(timeleft / 1000000000.0);
383 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
386 * check if we have not consumed our entire timeslice. If we
387 * do, don't sleep and take a new sample right away.
388 * we'll lose all the missed samples and overrun our total
391 if (newint_ns > 0 || newint_s > 0) {
392 req.tv_sec = newint_s;
393 req.tv_nsec = newint_ns;
395 res = nanosleep(&req, NULL);
397 if (errno == EINTR) {
398 /* caught signal, probably HUP! */
401 perror("nanosleep()");
406 /* calculate how many samples we lost and scrap them */
407 arg_samples_len -= (int)(newint_ns / interval);
409 LIST_PREPEND(struct list_sample_data, link, head, sampledata);
412 /* do some cleanup, close fd's */
414 while (ps->next_ps) {
417 close(ps->schedstat);
426 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
427 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
428 of = fopen(output_file, "w");
432 fprintf(stderr, "opening output file '%s': %m\n", output_file);
438 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
440 do_journal_append(output_file);
449 /* nitpic cleanups */
450 ps = ps_first->next_ps;
451 while (ps->next_ps) {
452 struct ps_struct *old;
455 old->sample = ps->first;
457 while (old->sample->next) {
458 struct ps_sched_struct *oldsample = old->sample;
460 old->sample = old->sample->next;
470 while (sampledata->link_prev) {
471 struct list_sample_data *old_sampledata = sampledata;
472 sampledata = sampledata->link_prev;
473 free(old_sampledata);
476 /* don't complain when overrun once, happens most commonly on 1st sample */
478 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);