source: MondoRescue/branches/3.3/mindi-busybox/init/bootchartd.c@ 3647

Last change on this file since 3647 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

  • Property svn:eol-style set to native
File size: 13.5 KB
RevLine 
[2725]1/* vi: set sw=4 ts=4: */
2/*
3 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
4 */
5
[3232]6//applet:IF_BOOTCHARTD(APPLET(bootchartd, BB_DIR_SBIN, BB_SUID_DROP))
[2725]7
8//kbuild:lib-$(CONFIG_BOOTCHARTD) += bootchartd.o
9
10//config:config BOOTCHARTD
11//config: bool "bootchartd"
12//config: default y
13//config: help
14//config: bootchartd is commonly used to profile the boot process
15//config: for the purpose of speeding it up. In this case, it is started
16//config: by the kernel as the init process. This is configured by adding
17//config: the init=/sbin/bootchartd option to the kernel command line.
18//config:
19//config: It can also be used to monitor the resource usage of a specific
20//config: application or the running system in general. In this case,
21//config: bootchartd is started interactively by running bootchartd start
22//config: and stopped using bootchartd stop.
23//config:
24//config:config FEATURE_BOOTCHARTD_BLOATED_HEADER
25//config: bool "Compatible, bloated header"
26//config: default y
27//config: depends on BOOTCHARTD
28//config: help
29//config: Create extended header file compatible with "big" bootchartd.
30//config: "Big" bootchartd is a shell script and it dumps some
31//config: "convenient" info int the header, such as:
32//config: title = Boot chart for `hostname` (`date`)
33//config: system.uname = `uname -srvm`
34//config: system.release = `cat /etc/DISTRO-release`
35//config: system.cpu = `grep '^model name' /proc/cpuinfo | head -1` ($cpucount)
36//config: system.kernel.options = `cat /proc/cmdline`
37//config: This data is not mandatory for bootchart graph generation,
38//config: and is considered bloat. Nevertheless, this option
39//config: makes bootchartd applet to dump a subset of it.
40//config:
41//config:config FEATURE_BOOTCHARTD_CONFIG_FILE
42//config: bool "Support bootchartd.conf"
43//config: default y
44//config: depends on BOOTCHARTD
45//config: help
46//config: Enable reading and parsing of $PWD/bootchartd.conf
47//config: and /etc/bootchartd.conf files.
48
49#include "libbb.h"
[3621]50#include "common_bufsiz.h"
[2725]51/* After libbb.h, since it needs sys/types.h on some systems */
52#include <sys/utsname.h>
53
54#ifdef __linux__
55# include <sys/mount.h>
56# ifndef MS_SILENT
57# define MS_SILENT (1 << 15)
58# endif
59# ifndef MNT_DETACH
60# define MNT_DETACH 0x00000002
61# endif
62#endif
63
[3621]64#if !ENABLE_TAR && !ENABLE_WERROR
65# warning Note: bootchartd requires tar command, but you did not select it.
66#elif !ENABLE_FEATURE_SEAMLESS_GZ && !ENABLE_WERROR
67# warning Note: bootchartd requires tar -z support, but you did not select it.
68#endif
69
[2725]70#define BC_VERSION_STR "0.8"
71
72/* For debugging, set to 0:
73 * strace won't work with DO_SIGNAL_SYNC set to 1.
74 */
75#define DO_SIGNAL_SYNC 1
76
77
78//$PWD/bootchartd.conf and /etc/bootchartd.conf:
79//supported options:
80//# Sampling period (in seconds)
81//SAMPLE_PERIOD=0.2
82//
83//not yet supported:
84//# tmpfs size
85//# (32 MB should suffice for ~20 minutes worth of log data, but YMMV)
86//TMPFS_SIZE=32m
87//
88//# Whether to enable and store BSD process accounting information. The
89//# kernel needs to be configured to enable v3 accounting
90//# (CONFIG_BSD_PROCESS_ACCT_V3). accton from the GNU accounting utilities
91//# is also required.
92//PROCESS_ACCOUNTING="no"
93//
94//# Tarball for the various boot log files
95//BOOTLOG_DEST=/var/log/bootchart.tgz
96//
97//# Whether to automatically stop logging as the boot process completes.
98//# The logger will look for known processes that indicate bootup completion
99//# at a specific runlevel (e.g. gdm-binary, mingetty, etc.).
100//AUTO_STOP_LOGGER="yes"
101//
102//# Whether to automatically generate the boot chart once the boot logger
103//# completes. The boot chart will be generated in $AUTO_RENDER_DIR.
104//# Note that the bootchart package must be installed.
105//AUTO_RENDER="no"
106//
107//# Image format to use for the auto-generated boot chart
108//# (choose between png, svg and eps).
109//AUTO_RENDER_FORMAT="png"
110//
111//# Output directory for auto-generated boot charts
112//AUTO_RENDER_DIR="/var/log"
113
114
115/* Globals */
116struct globals {
117 char jiffy_line[COMMON_BUFSIZE];
118} FIX_ALIASING;
[3621]119#define G (*(struct globals*)bb_common_bufsiz1)
120#define INIT_G() do { setup_common_bufsiz(); } while (0)
[2725]121
122static void dump_file(FILE *fp, const char *filename)
123{
124 int fd = open(filename, O_RDONLY);
125 if (fd >= 0) {
126 fputs(G.jiffy_line, fp);
127 fflush(fp);
128 bb_copyfd_eof(fd, fileno(fp));
129 close(fd);
130 fputc('\n', fp);
131 }
132}
133
134static int dump_procs(FILE *fp, int look_for_login_process)
135{
136 struct dirent *entry;
137 DIR *dir = opendir("/proc");
138 int found_login_process = 0;
139
140 fputs(G.jiffy_line, fp);
141 while ((entry = readdir(dir)) != NULL) {
142 char name[sizeof("/proc/%u/cmdline") + sizeof(int)*3];
143 int stat_fd;
144 unsigned pid = bb_strtou(entry->d_name, NULL, 10);
145 if (errno)
146 continue;
147
148 /* Android's version reads /proc/PID/cmdline and extracts
149 * non-truncated process name. Do we want to do that? */
150
151 sprintf(name, "/proc/%u/stat", pid);
152 stat_fd = open(name, O_RDONLY);
153 if (stat_fd >= 0) {
154 char *p;
155 char stat_line[4*1024];
156 int rd = safe_read(stat_fd, stat_line, sizeof(stat_line)-2);
157
158 close(stat_fd);
159 if (rd < 0)
160 continue;
161 stat_line[rd] = '\0';
162 p = strchrnul(stat_line, '\n');
163 *p++ = '\n';
164 *p = '\0';
165 fputs(stat_line, fp);
166 if (!look_for_login_process)
167 continue;
168 p = strchr(stat_line, '(');
169 if (!p)
170 continue;
171 p++;
172 strchrnul(p, ')')[0] = '\0';
173 /* Is it gdm, kdm or a getty? */
174 if (((p[0] == 'g' || p[0] == 'k' || p[0] == 'x') && p[1] == 'd' && p[2] == 'm')
175 || strstr(p, "getty")
176 ) {
177 found_login_process = 1;
178 }
179 }
180 }
181 closedir(dir);
182 fputc('\n', fp);
183 return found_login_process;
184}
185
186static char *make_tempdir(void)
187{
188 char template[] = "/tmp/bootchart.XXXXXX";
189 char *tempdir = xstrdup(mkdtemp(template));
190 if (!tempdir) {
191#ifdef __linux__
192 /* /tmp is not writable (happens when we are used as init).
193 * Try to mount a tmpfs, them cd and lazily unmount it.
194 * Since we unmount it at once, we can mount it anywhere.
195 * Try a few locations which are likely ti exist.
196 */
[3621]197 static const char dirs[] ALIGN1 = "/mnt\0""/tmp\0""/boot\0""/proc\0";
[2725]198 const char *try_dir = dirs;
199 while (mount("none", try_dir, "tmpfs", MS_SILENT, "size=16m") != 0) {
200 try_dir += strlen(try_dir) + 1;
201 if (!try_dir[0])
202 bb_perror_msg_and_die("can't %smount tmpfs", "");
203 }
204 //bb_error_msg("mounted tmpfs on %s", try_dir);
205 xchdir(try_dir);
206 if (umount2(try_dir, MNT_DETACH) != 0) {
207 bb_perror_msg_and_die("can't %smount tmpfs", "un");
208 }
209#else
210 bb_perror_msg_and_die("can't create temporary directory");
211#endif
212 } else {
213 xchdir(tempdir);
214 }
215 return tempdir;
216}
217
[3232]218static void do_logging(unsigned sample_period_us, int process_accounting)
[2725]219{
220 FILE *proc_stat = xfopen("proc_stat.log", "w");
221 FILE *proc_diskstats = xfopen("proc_diskstats.log", "w");
222 //FILE *proc_netdev = xfopen("proc_netdev.log", "w");
223 FILE *proc_ps = xfopen("proc_ps.log", "w");
224 int look_for_login_process = (getppid() == 1);
225 unsigned count = 60*1000*1000 / sample_period_us; /* ~1 minute */
226
[3232]227 if (process_accounting) {
228 close(xopen("kernel_pacct", O_WRONLY | O_CREAT | O_TRUNC));
229 acct("kernel_pacct");
230 }
231
[2725]232 while (--count && !bb_got_signal) {
233 char *p;
234 int len = open_read_close("/proc/uptime", G.jiffy_line, sizeof(G.jiffy_line)-2);
235 if (len < 0)
236 goto wait_more;
237 /* /proc/uptime has format "NNNNNN.MM NNNNNNN.MM" */
238 /* we convert it to "NNNNNNMM\n" (using first value) */
239 G.jiffy_line[len] = '\0';
240 p = strchr(G.jiffy_line, '.');
241 if (!p)
242 goto wait_more;
243 while (isdigit(*++p))
244 p[-1] = *p;
245 p[-1] = '\n';
246 p[0] = '\0';
247
248 dump_file(proc_stat, "/proc/stat");
249 dump_file(proc_diskstats, "/proc/diskstats");
250 //dump_file(proc_netdev, "/proc/net/dev");
251 if (dump_procs(proc_ps, look_for_login_process)) {
252 /* dump_procs saw a getty or {g,k,x}dm
253 * stop logging in 2 seconds:
254 */
255 if (count > 2*1000*1000 / sample_period_us)
256 count = 2*1000*1000 / sample_period_us;
257 }
258 fflush_all();
259 wait_more:
260 usleep(sample_period_us);
261 }
262}
263
[3232]264static void finalize(char *tempdir, const char *prog, int process_accounting)
[2725]265{
266 //# Stop process accounting if configured
267 //local pacct=
268 //[ -e kernel_pacct ] && pacct=kernel_pacct
269
270 FILE *header_fp = xfopen("header", "w");
271
[3232]272 if (process_accounting)
273 acct(NULL);
274
[2725]275 if (prog)
276 fprintf(header_fp, "profile.process = %s\n", prog);
277
278 fputs("version = "BC_VERSION_STR"\n", header_fp);
279 if (ENABLE_FEATURE_BOOTCHARTD_BLOATED_HEADER) {
280 char *hostname;
281 char *kcmdline;
282 time_t t;
283 struct tm tm_time;
284 /* x2 for possible localized weekday/month names */
285 char date_buf[sizeof("Mon Jun 21 05:29:03 CEST 2010") * 2];
286 struct utsname unamebuf;
287
288 hostname = safe_gethostname();
289 time(&t);
290 localtime_r(&t, &tm_time);
291 strftime(date_buf, sizeof(date_buf), "%a %b %e %H:%M:%S %Z %Y", &tm_time);
292 fprintf(header_fp, "title = Boot chart for %s (%s)\n", hostname, date_buf);
293 if (ENABLE_FEATURE_CLEAN_UP)
294 free(hostname);
295
296 uname(&unamebuf); /* never fails */
297 /* same as uname -srvm */
298 fprintf(header_fp, "system.uname = %s %s %s %s\n",
299 unamebuf.sysname,
300 unamebuf.release,
301 unamebuf.version,
302 unamebuf.machine
303 );
304
305 //system.release = `cat /etc/DISTRO-release`
306 //system.cpu = `grep '^model name' /proc/cpuinfo | head -1` ($cpucount)
307
308 kcmdline = xmalloc_open_read_close("/proc/cmdline", NULL);
309 /* kcmdline includes trailing "\n" */
310 fprintf(header_fp, "system.kernel.options = %s", kcmdline);
311 if (ENABLE_FEATURE_CLEAN_UP)
312 free(kcmdline);
313 }
314 fclose(header_fp);
315
316 /* Package log files */
[3232]317 system(xasprintf("tar -zcf /var/log/bootlog.tgz header %s *.log", process_accounting ? "kernel_pacct" : ""));
[2725]318 /* Clean up (if we are not in detached tmpfs) */
319 if (tempdir) {
320 unlink("header");
321 unlink("proc_stat.log");
322 unlink("proc_diskstats.log");
323 //unlink("proc_netdev.log");
324 unlink("proc_ps.log");
[3232]325 if (process_accounting)
326 unlink("kernel_pacct");
[2725]327 rmdir(tempdir);
328 }
329
330 /* shell-based bootchartd tries to run /usr/bin/bootchart if $AUTO_RENDER=yes:
331 * /usr/bin/bootchart -o "$AUTO_RENDER_DIR" -f $AUTO_RENDER_FORMAT "$BOOTLOG_DEST"
332 */
333}
334
335//usage:#define bootchartd_trivial_usage
336//usage: "start [PROG ARGS]|stop|init"
337//usage:#define bootchartd_full_usage "\n\n"
338//usage: "Create /var/log/bootchart.tgz with boot chart data\n"
339//usage: "\nstart: start background logging; with PROG, run PROG, then kill logging with USR1"
340//usage: "\nstop: send USR1 to all bootchartd processes"
341//usage: "\ninit: start background logging; stop when getty/xdm is seen (for init scripts)"
342//usage: "\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init"
343
344int bootchartd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
345int bootchartd_main(int argc UNUSED_PARAM, char **argv)
346{
347 unsigned sample_period_us;
348 pid_t parent_pid, logger_pid;
349 smallint cmd;
[3232]350 int process_accounting;
[2725]351 enum {
352 CMD_STOP = 0,
353 CMD_START,
354 CMD_INIT,
355 CMD_PID1, /* used to mark pid 1 case */
356 };
357
358 INIT_G();
359
360 parent_pid = getpid();
361 if (argv[1]) {
362 cmd = index_in_strings("stop\0""start\0""init\0", argv[1]);
363 if (cmd < 0)
364 bb_show_usage();
365 if (cmd == CMD_STOP) {
366 pid_t *pidList = find_pid_by_name("bootchartd");
367 while (*pidList != 0) {
368 if (*pidList != parent_pid)
369 kill(*pidList, SIGUSR1);
370 pidList++;
371 }
372 return EXIT_SUCCESS;
373 }
374 } else {
375 if (parent_pid != 1)
376 bb_show_usage();
377 cmd = CMD_PID1;
378 }
379
380 /* Here we are in START, INIT or CMD_PID1 state */
381
382 /* Read config file: */
383 sample_period_us = 200 * 1000;
[3232]384 process_accounting = 0;
[2725]385 if (ENABLE_FEATURE_BOOTCHARTD_CONFIG_FILE) {
386 char* token[2];
387 parser_t *parser = config_open2("/etc/bootchartd.conf" + 5, fopen_for_read);
388 if (!parser)
389 parser = config_open2("/etc/bootchartd.conf", fopen_for_read);
390 while (config_read(parser, token, 2, 0, "#=", PARSE_NORMAL & ~PARSE_COLLAPSE)) {
391 if (strcmp(token[0], "SAMPLE_PERIOD") == 0 && token[1])
392 sample_period_us = atof(token[1]) * 1000000;
[3232]393 if (strcmp(token[0], "PROCESS_ACCOUNTING") == 0 && token[1]
394 && (strcmp(token[1], "on") == 0 || strcmp(token[1], "yes") == 0)
395 ) {
396 process_accounting = 1;
397 }
[2725]398 }
399 config_close(parser);
[3232]400 if ((int)sample_period_us <= 0)
401 sample_period_us = 1; /* prevent division by 0 */
[2725]402 }
403
404 /* Create logger child: */
405 logger_pid = fork_or_rexec(argv);
406
407 if (logger_pid == 0) { /* child */
408 char *tempdir;
409
410 bb_signals(0
411 + (1 << SIGUSR1)
412 + (1 << SIGUSR2)
413 + (1 << SIGTERM)
414 + (1 << SIGQUIT)
415 + (1 << SIGINT)
416 + (1 << SIGHUP)
417 , record_signo);
418
419 if (DO_SIGNAL_SYNC)
420 /* Inform parent that we are ready */
421 raise(SIGSTOP);
422
423 /* If we are started by kernel, PATH might be unset.
424 * In order to find "tar", let's set some sane PATH:
425 */
426 if (cmd == CMD_PID1 && !getenv("PATH"))
427 putenv((char*)bb_PATH_root_path);
428
429 tempdir = make_tempdir();
[3232]430 do_logging(sample_period_us, process_accounting);
431 finalize(tempdir, cmd == CMD_START ? argv[2] : NULL, process_accounting);
[2725]432 return EXIT_SUCCESS;
433 }
434
435 /* parent */
436
[3232]437 USE_FOR_NOMMU(argv[0][0] &= 0x7f); /* undo fork_or_rexec() damage */
438
[2725]439 if (DO_SIGNAL_SYNC) {
440 /* Wait for logger child to set handlers, then unpause it.
441 * Otherwise with short-lived PROG (e.g. "bootchartd start true")
442 * we might send SIGUSR1 before logger sets its handler.
443 */
444 waitpid(logger_pid, NULL, WUNTRACED);
445 kill(logger_pid, SIGCONT);
446 }
447
448 if (cmd == CMD_PID1) {
449 char *bootchart_init = getenv("bootchart_init");
450 if (bootchart_init)
451 execl(bootchart_init, bootchart_init, NULL);
452 execl("/init", "init", NULL);
453 execl("/sbin/init", "init", NULL);
454 bb_perror_msg_and_die("can't execute '%s'", "/sbin/init");
455 }
456
457 if (cmd == CMD_START && argv[2]) { /* "start PROG ARGS" */
458 pid_t pid = xvfork();
459 if (pid == 0) { /* child */
460 argv += 2;
[3232]461 BB_EXECVP_or_die(argv);
[2725]462 }
463 /* parent */
464 waitpid(pid, NULL, 0);
465 kill(logger_pid, SIGUSR1);
466 }
467
468 return EXIT_SUCCESS;
469}
Note: See TracBrowser for help on using the repository browser.