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>
55 #include "conf-parser.h"
57 #include "path-util.h"
60 #include "bootchart.h"
64 double sampletime[MAXSAMPLES];
65 struct ps_struct *ps_first;
66 struct block_stat_struct blockstat[MAXSAMPLES];
67 int entropy_avail[MAXSAMPLES];
68 struct cpu_stat_struct cpustat[MAXCPUS];
74 static int exiting = 0;
78 bool arg_entropy = false;
80 bool arg_relative = false;
81 bool arg_filter = true;
82 bool arg_show_cmdline = false;
85 int arg_samples_len = 500; /* we record len+1 (1 start sample) */
86 double arg_hz = 25.0; /* 20 seconds log time */
87 double arg_scale_x = 100.0; /* 100px = 1sec */
88 double arg_scale_y = 20.0; /* 16px = 1 process bar */
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 static void parse_conf(void) {
102 char *init = NULL, *output = NULL;
103 const ConfigTableItem items[] = {
104 { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len },
105 { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz },
106 { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative },
107 { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter },
108 { "Bootchart", "Output", config_parse_path, 0, &output },
109 { "Bootchart", "Init", config_parse_path, 0, &init },
110 { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss },
111 { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy },
112 { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x },
113 { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y },
114 { NULL, NULL, NULL, 0, NULL }
116 _cleanup_fclose_ FILE *f;
119 f = fopen(BOOTCHART_CONF, "re");
123 r = config_parse(BOOTCHART_CONF, f,
124 NULL, config_item_table_lookup, (void*) items, true, NULL);
126 log_warning("Failed to parse configuration file: %s", strerror(-r));
129 strscpy(arg_init_path, sizeof(arg_init_path), init);
131 strscpy(arg_output_path, sizeof(arg_output_path), output);
134 static int parse_args(int argc, char *argv[]) {
135 static struct option options[] = {
136 {"rel", no_argument, NULL, 'r'},
137 {"freq", required_argument, NULL, 'f'},
138 {"samples", required_argument, NULL, 'n'},
139 {"pss", no_argument, NULL, 'p'},
140 {"output", required_argument, NULL, 'o'},
141 {"init", required_argument, NULL, 'i'},
142 {"no-filter", no_argument, NULL, 'F'},
143 {"cmdline", no_argument, NULL, 'C'},
144 {"help", no_argument, NULL, 'h'},
145 {"scale-x", required_argument, NULL, 'x'},
146 {"scale-y", required_argument, NULL, 'y'},
147 {"entropy", no_argument, NULL, 'e'},
152 while ((c = getopt_long(argc, argv, "erpf:n:o:i:FChx:y:", options, NULL)) >= 0) {
160 r = safe_atod(optarg, &arg_hz);
162 log_warning("failed to parse --freq/-f argument '%s': %s",
163 optarg, strerror(-r));
169 arg_show_cmdline = true;
172 r = safe_atoi(optarg, &arg_samples_len);
174 log_warning("failed to parse --samples/-n argument '%s': %s",
175 optarg, strerror(-r));
178 path_kill_slashes(optarg);
179 strscpy(arg_output_path, sizeof(arg_output_path), optarg);
182 path_kill_slashes(optarg);
183 strscpy(arg_init_path, sizeof(arg_init_path), optarg);
189 r = safe_atod(optarg, &arg_scale_x);
191 log_warning("failed to parse --scale-x/-x argument '%s': %s",
192 optarg, strerror(-r));
195 r = safe_atod(optarg, &arg_scale_y);
197 log_warning("failed to parse --scale-y/-y argument '%s': %s",
198 optarg, strerror(-r));
204 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
205 fprintf(stderr, " --rel, -r Record time relative to recording\n");
206 fprintf(stderr, " --freq, -f f Sample frequency [%f]\n", arg_hz);
207 fprintf(stderr, " --samples, -n N Stop sampling at [%d] samples\n", arg_samples_len);
208 fprintf(stderr, " --scale-x, -x N Scale the graph horizontally [%f] \n", arg_scale_x);
209 fprintf(stderr, " --scale-y, -y N Scale the graph vertically [%f] \n", arg_scale_y);
210 fprintf(stderr, " --pss, -p Enable PSS graph (CPU intensive)\n");
211 fprintf(stderr, " --entropy, -e Enable the entropy_avail graph\n");
212 fprintf(stderr, " --output, -o [PATH] Path to output files [%s]\n", arg_output_path);
213 fprintf(stderr, " --init, -i [PATH] Path to init executable [%s]\n", arg_init_path);
214 fprintf(stderr, " --no-filter, -F Disable filtering of processes from the graph\n");
215 fprintf(stderr, " that are of less importance or short-lived\n");
216 fprintf(stderr, " --cmdline, -C Display the full command line with arguments\n");
217 fprintf(stderr, " of processes, instead of only the process name\n");
218 fprintf(stderr, " --help, -h Display this message\n");
219 fprintf(stderr, "See bootchart.conf for more information.\n");
227 if (arg_samples_len > MAXSAMPLES) {
228 fprintf(stderr, "Error: samples exceeds maximum\n");
233 fprintf(stderr, "Error: Frequency needs to be > 0\n");
240 int main(int argc, char *argv[]) {
241 _cleanup_free_ char *build = NULL;
242 struct sigaction sig = {
243 .sa_handler = signal_handler,
245 struct ps_struct *ps;
246 char output_file[PATH_MAX];
254 r = parse_args(argc, argv);
259 * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then
261 * - parent execs executable specified via init_path[] (/sbin/init by default) as pid=1
267 execl(arg_init_path, arg_init_path, NULL);
272 rlim.rlim_cur = 4096;
273 rlim.rlim_max = 4096;
274 (void) setrlimit(RLIMIT_NOFILE, &rlim);
276 /* start with empty ps LL */
277 ps_first = calloc(1, sizeof(struct ps_struct));
279 perror("calloc(ps_struct)");
283 /* handle TERM/INT nicely */
284 sigaction(SIGHUP, &sig, NULL);
286 interval = (1.0 / arg_hz) * 1000000000.0;
290 /* main program loop */
291 for (samples = 0; !exiting && samples < arg_samples_len; samples++) {
300 sampletime[samples] = gettime_ns();
302 if (!of && (access(arg_output_path, R_OK|W_OK|X_OK) == 0)) {
304 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
305 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
306 of = fopen(output_file, "w");
310 sysfd = open("/sys", O_RDONLY);
313 parse_env_file("/etc/os-release", NEWLINE,
314 "PRETTY_NAME", &build,
317 /* wait for /proc to become available, discarding samples */
318 if (graph_start <= 0.0)
323 sample_stop = gettime_ns();
325 elapsed = (sample_stop - sampletime[samples]) * 1000000000.0;
326 timeleft = interval - elapsed;
328 newint_s = (time_t)(timeleft / 1000000000.0);
329 newint_ns = (long)(timeleft - (newint_s * 1000000000.0));
332 * check if we have not consumed our entire timeslice. If we
333 * do, don't sleep and take a new sample right away.
334 * we'll lose all the missed samples and overrun our total
337 if (newint_ns > 0 || newint_s > 0) {
338 req.tv_sec = newint_s;
339 req.tv_nsec = newint_ns;
341 res = nanosleep(&req, NULL);
343 if (errno == EINTR) {
344 /* caught signal, probably HUP! */
347 perror("nanosleep()");
352 /* calculate how many samples we lost and scrap them */
353 arg_samples_len -= (int)(newint_ns / interval);
357 /* do some cleanup, close fd's */
359 while (ps->next_ps) {
362 close(ps->schedstat);
371 strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t));
372 snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr);
373 of = fopen(output_file, "w");
377 fprintf(stderr, "opening output file '%s': %m\n", output_file);
383 fprintf(stderr, "systemd-bootchart wrote %s\n", output_file);
392 /* nitpic cleanups */
394 while (ps->next_ps) {
395 struct ps_struct *old = ps;
403 /* don't complain when overrun once, happens most commonly on 1st sample */
405 fprintf(stderr, "systemd-boochart: Warning: sample time overrun %i times\n", overrun);