aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2010-05-04 15:45:25 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2010-05-04 15:45:25 +0200
commitff027d6f50bfa24228e230b84a3297c51d37d000 (patch)
tree9131e36c21870d52b02c079fe877be8a5c87970c
parentfe360480aa30adf776a06dc9f09c704abcfa9000 (diff)
downloadbusybox-w32-ff027d6f50bfa24228e230b84a3297c51d37d000.tar.gz
busybox-w32-ff027d6f50bfa24228e230b84a3297c51d37d000.tar.bz2
busybox-w32-ff027d6f50bfa24228e230b84a3297c51d37d000.zip
bootchartd: new applet
Usage: bootchartd start [PROG ARGS]|init|stop Create /var/log/bootchart.tgz with boot chart data Options: start: start background logging; with PROG, run PROG, then kill logging with USR1 stop: send USR1 to all bootchartd processes init: start background logging; stop when getty/gdm is seen (for init scripts) Under PID 1: as init, then exec $bootchart_init, /init, /sbin/init function old new delta bootchartd_main - 907 +907 dump_procs - 353 +353 packed_usage 26566 26735 +169 dump_file - 91 +91 static.dirs - 23 +23 applet_names 2176 2187 +11 applet_main 1284 1288 +4 applet_nameofs 642 644 +2 ------------------------------------------------------------------------------ (add/remove: 5/0 grow/shrink: 4/0 up/down: 1560/0) Total: 1560 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--include/applets.h1
-rw-r--r--include/usage.h34
-rw-r--r--init/Config.in14
-rw-r--r--init/Kbuild7
-rw-r--r--init/bootchartd.c355
5 files changed, 396 insertions, 15 deletions
diff --git a/include/applets.h b/include/applets.h
index ef5dd781d..dfb20b9be 100644
--- a/include/applets.h
+++ b/include/applets.h
@@ -83,6 +83,7 @@ IF_BBCONFIG(APPLET(bbconfig, _BB_DIR_BIN, _BB_SUID_DROP))
83//IF_BBSH(APPLET(bbsh, _BB_DIR_BIN, _BB_SUID_DROP)) 83//IF_BBSH(APPLET(bbsh, _BB_DIR_BIN, _BB_SUID_DROP))
84IF_BEEP(APPLET(beep, _BB_DIR_USR_BIN, _BB_SUID_DROP)) 84IF_BEEP(APPLET(beep, _BB_DIR_USR_BIN, _BB_SUID_DROP))
85IF_BLKID(APPLET(blkid, _BB_DIR_SBIN, _BB_SUID_DROP)) 85IF_BLKID(APPLET(blkid, _BB_DIR_SBIN, _BB_SUID_DROP))
86IF_BOOTCHARTD(APPLET(bootchartd, _BB_DIR_SBIN, _BB_SUID_DROP))
86IF_BRCTL(APPLET(brctl, _BB_DIR_USR_SBIN, _BB_SUID_DROP)) 87IF_BRCTL(APPLET(brctl, _BB_DIR_USR_SBIN, _BB_SUID_DROP))
87IF_BUNZIP2(APPLET(bunzip2, _BB_DIR_USR_BIN, _BB_SUID_DROP)) 88IF_BUNZIP2(APPLET(bunzip2, _BB_DIR_USR_BIN, _BB_SUID_DROP))
88IF_BUNZIP2(APPLET_ODDNAME(bzcat, bunzip2, _BB_DIR_USR_BIN, _BB_SUID_DROP, bzcat)) 89IF_BUNZIP2(APPLET_ODDNAME(bzcat, bunzip2, _BB_DIR_USR_BIN, _BB_SUID_DROP, bzcat))
diff --git a/include/usage.h b/include/usage.h
index 2c2a90d16..636d01943 100644
--- a/include/usage.h
+++ b/include/usage.h
@@ -154,18 +154,15 @@
154 "\n -r Repetitions" \ 154 "\n -r Repetitions" \
155 "\n -n Start new tone" \ 155 "\n -n Start new tone" \
156 156
157#define fbsplash_trivial_usage \ 157#define bootchartd_trivial_usage \
158 "-s IMGFILE [-c] [-d DEV] [-i INIFILE] [-f CMD]" 158 "start [PROG ARGS]|stop|init"
159#define fbsplash_full_usage "\n\n" \ 159#define bootchartd_full_usage "\n\n" \
160 "Options:" \ 160 "Create /var/log/bootchart.tgz with boot chart data\n" \
161 "\n -s Image" \ 161 "\nOptions:" \
162 "\n -c Hide cursor" \ 162 "\nstart: start background logging; with PROG, run PROG, then kill logging with USR1" \
163 "\n -d Framebuffer device (default /dev/fb0)" \ 163 "\nstop: send USR1 to all bootchartd processes" \
164 "\n -i Config file (var=value):" \ 164 "\ninit: start background logging; stop when getty/xdm is seen (for init scripts)" \
165 "\n BAR_LEFT,BAR_TOP,BAR_WIDTH,BAR_HEIGHT" \ 165 "\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init" \
166 "\n BAR_R,BAR_G,BAR_B" \
167 "\n -f Control pipe (else exit after drawing image)" \
168 "\n commands: 'NN' (% for progress bar) or 'exit'" \
169 166
170#define brctl_trivial_usage \ 167#define brctl_trivial_usage \
171 "COMMAND [BRIDGE [INTERFACE]]" 168 "COMMAND [BRIDGE [INTERFACE]]"
@@ -1174,6 +1171,19 @@
1174 "$ echo $?\n" \ 1171 "$ echo $?\n" \
1175 "1\n" 1172 "1\n"
1176 1173
1174#define fbsplash_trivial_usage \
1175 "-s IMGFILE [-c] [-d DEV] [-i INIFILE] [-f CMD]"
1176#define fbsplash_full_usage "\n\n" \
1177 "Options:" \
1178 "\n -s Image" \
1179 "\n -c Hide cursor" \
1180 "\n -d Framebuffer device (default /dev/fb0)" \
1181 "\n -i Config file (var=value):" \
1182 "\n BAR_LEFT,BAR_TOP,BAR_WIDTH,BAR_HEIGHT" \
1183 "\n BAR_R,BAR_G,BAR_B" \
1184 "\n -f Control pipe (else exit after drawing image)" \
1185 "\n commands: 'NN' (% for progress bar) or 'exit'" \
1186
1177#define fbset_trivial_usage \ 1187#define fbset_trivial_usage \
1178 "[OPTIONS] [MODE]" 1188 "[OPTIONS] [MODE]"
1179#define fbset_full_usage "\n\n" \ 1189#define fbset_full_usage "\n\n" \
diff --git a/init/Config.in b/init/Config.in
index 3d99d4792..76d509207 100644
--- a/init/Config.in
+++ b/init/Config.in
@@ -120,4 +120,18 @@ config MESG
120 Mesg controls access to your terminal by others. It is typically 120 Mesg controls access to your terminal by others. It is typically
121 used to allow or disallow other users to write to your terminal 121 used to allow or disallow other users to write to your terminal
122 122
123config BOOTCHARTD
124 bool "bootchartd"
125 default n
126 help
127 bootchartd is commonly used to profile the boot process
128 for the purpose of speeding it up. In this case, it is started
129 by the kernel as the init process. This is configured by adding
130 the init=/sbin/bootchartd option to the kernel command line.
131
132 It can also be used to monitor the resource usage of a specific
133 application or the running system in general. In this case,
134 bootchartd is started interactively by running bootchartd start
135 and stopped using bootchartd stop.
136
123endmenu 137endmenu
diff --git a/init/Kbuild b/init/Kbuild
index c060f3af4..ce3f30256 100644
--- a/init/Kbuild
+++ b/init/Kbuild
@@ -5,6 +5,7 @@
5# Licensed under the GPL v2, see the file LICENSE in this tarball. 5# Licensed under the GPL v2, see the file LICENSE in this tarball.
6 6
7lib-y:= 7lib-y:=
8lib-$(CONFIG_HALT) += halt.o 8lib-$(CONFIG_HALT) += halt.o
9lib-$(CONFIG_INIT) += init.o 9lib-$(CONFIG_INIT) += init.o
10lib-$(CONFIG_MESG) += mesg.o 10lib-$(CONFIG_MESG) += mesg.o
11lib-$(CONFIG_BOOTCHARTD) += bootchartd.o
diff --git a/init/bootchartd.c b/init/bootchartd.c
new file mode 100644
index 000000000..fccaaceb4
--- /dev/null
+++ b/init/bootchartd.c
@@ -0,0 +1,355 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
4 */
5#include "libbb.h"
6#include <sys/mount.h>
7#ifndef MS_SILENT
8# define MS_SILENT (1 << 15)
9#endif
10#ifndef MNT_DETACH
11# define MNT_DETACH 0x00000002
12#endif
13
14#define BC_VERSION_STR "0.8"
15
16/* For debugging, set to 0:
17 * strace won't work with DO_SIGNAL_SYNC set to 1.
18 */
19#define DO_SIGNAL_SYNC 1
20
21
22//Not supported: $PWD/bootchartd.conf and /etc/bootchartd.conf
23
24//# tmpfs size
25//# (32 MB should suffice for ~20 minutes worth of log data, but YMMV)
26//TMPFS_SIZE=32m
27//
28//# Sampling period (in seconds)
29//SAMPLE_PERIOD=0.2
30//
31//# Whether to enable and store BSD process accounting information. The
32//# kernel needs to be configured to enable v3 accounting
33//# (CONFIG_BSD_PROCESS_ACCT_V3). accton from the GNU accounting utilities
34//# is also required.
35//PROCESS_ACCOUNTING="no"
36//
37//# Tarball for the various boot log files
38//BOOTLOG_DEST=/var/log/bootchart.tgz
39//
40//# Whether to automatically stop logging as the boot process completes.
41//# The logger will look for known processes that indicate bootup completion
42//# at a specific runlevel (e.g. gdm-binary, mingetty, etc.).
43//AUTO_STOP_LOGGER="yes"
44//
45//# Whether to automatically generate the boot chart once the boot logger
46//# completes. The boot chart will be generated in $AUTO_RENDER_DIR.
47//# Note that the bootchart package must be installed.
48//AUTO_RENDER="no"
49//
50//# Image format to use for the auto-generated boot chart
51//# (choose between png, svg and eps).
52//AUTO_RENDER_FORMAT="png"
53//
54//# Output directory for auto-generated boot charts
55//AUTO_RENDER_DIR="/var/log"
56
57
58/* Globals */
59struct globals {
60 char jiffy_line[sizeof(bb_common_bufsiz1)];
61} FIX_ALIASING;
62#define G (*(struct globals*)&bb_common_bufsiz1)
63#define INIT_G() do { } while (0)
64
65static void dump_file(FILE *fp, const char *filename)
66{
67 int fd = open(filename, O_RDONLY);
68 if (fd >= 0) {
69 fputs(G.jiffy_line, fp);
70 fflush(fp);
71 bb_copyfd_eof(fd, fileno(fp));
72 close(fd);
73 fputc('\n', fp);
74 }
75}
76
77static int dump_procs(FILE *fp, int look_for_login_process)
78{
79 struct dirent *entry;
80 DIR *dir = opendir("/proc");
81 int found_login_process = 0;
82
83 fputs(G.jiffy_line, fp);
84 while ((entry = readdir(dir)) != NULL) {
85 char name[sizeof("/proc/%u/cmdline") + sizeof(int)*3];
86 int stat_fd;
87 unsigned pid = bb_strtou(entry->d_name, NULL, 10);
88 if (errno)
89 continue;
90
91 /* Android's version reads /proc/PID/cmdline and extracts
92 * non-truncated process name. Do we want to do that? */
93
94 sprintf(name, "/proc/%u/stat", pid);
95 stat_fd = open(name, O_RDONLY);
96 if (stat_fd >= 0) {
97 char *p;
98 char stat_line[4*1024];
99 int rd = safe_read(stat_fd, stat_line, sizeof(stat_line)-2);
100
101 close(stat_fd);
102 if (rd < 0)
103 continue;
104 stat_line[rd] = '\0';
105 p = strchrnul(stat_line, '\n');
106 *p++ = '\n';
107 *p = '\0';
108 fputs(stat_line, fp);
109 if (!look_for_login_process)
110 continue;
111 p = strchr(stat_line, '(');
112 if (!p)
113 continue;
114 p++;
115 strchrnul(p, ')')[0] = '\0';
116 /* If is gdm, kdm or a getty? */
117 if (((p[0] == 'g' || p[0] == 'k' || p[0] == 'x') && p[1] == 'd' && p[2] == 'm')
118 || strstr(p, "getty")
119 ) {
120 found_login_process = 1;
121 }
122 }
123 }
124 closedir(dir);
125 fputc('\n', fp);
126 return found_login_process;
127}
128
129static char *make_tempdir(const char *prog)
130{
131 char template[] = "/tmp/bootchart.XXXXXX";
132 char *tempdir = xstrdup(mkdtemp(template));
133 if (!tempdir) {
134 /* /tmp is not writable (happens when we are used as init).
135 * Try to mount a tmpfs, them cd and lazily unmount it.
136 * Since we unmount it at once, we can mount it anywhere.
137 * Try a few locations which are likely ti exist.
138 */
139 static const char dirs[] = "/mnt\0""/tmp\0""/boot\0""/proc\0";
140 const char *try_dir = dirs;
141 while (mount("none", try_dir, "tmpfs", MS_SILENT, "size=16m") != 0) {
142 try_dir += strlen(try_dir) + 1;
143 if (!try_dir[0])
144 bb_perror_msg_and_die("can't %smount tmpfs", "");
145 }
146 //bb_error_msg("mounted tmpfs on %s", try_dir);
147 xchdir(try_dir);
148 if (umount2(try_dir, MNT_DETACH) != 0) {
149 bb_perror_msg_and_die("can't %smount tmpfs", "un");
150 }
151 } else {
152 xchdir(tempdir);
153 }
154 {
155 FILE *header_fp = xfopen("header", "w");
156 if (prog)
157 fprintf(header_fp, "profile.process = %s\n", prog);
158 fputs("version = "BC_VERSION_STR"\n", header_fp);
159 fclose(header_fp);
160 }
161
162 return tempdir;
163}
164
165static void do_logging(void)
166{
167 //# Enable process accounting if configured
168 //if [ "$PROCESS_ACCOUNTING" = "yes" ]; then
169 // [ -e kernel_pacct ] || : > kernel_pacct
170 // accton kernel_pacct
171 //fi
172
173 FILE *proc_stat = xfopen("proc_stat.log", "w");
174 FILE *proc_diskstats = xfopen("proc_diskstats.log", "w");
175 //FILE *proc_netdev = xfopen("proc_netdev.log", "w");
176 FILE *proc_ps = xfopen("proc_ps.log", "w");
177 int look_for_login_process = (getppid() == 1);
178 unsigned count = 60*1000*1000 / 200*1000; /* ~1 minute */
179
180 while (--count && !bb_got_signal) {
181 char *p;
182 int len = open_read_close("/proc/uptime", G.jiffy_line, sizeof(G.jiffy_line)-2);
183 if (len < 0)
184 goto wait_more;
185 /* /proc/uptime has format "NNNNNN.MM NNNNNNN.MM" */
186 /* we convert it to "NNNNNNMM\n" (using first value) */
187 G.jiffy_line[len] = '\0';
188 p = strchr(G.jiffy_line, '.');
189 if (!p)
190 goto wait_more;
191 while (isdigit(*++p))
192 p[-1] = *p;
193 p[-1] = '\n';
194 p[0] = '\0';
195
196 dump_file(proc_stat, "/proc/stat");
197 dump_file(proc_diskstats, "/proc/diskstats");
198 //dump_file(proc_netdev, "/proc/net/dev");
199 if (dump_procs(proc_ps, look_for_login_process)) {
200 /* dump_procs saw a getty or {g,k,x}dm
201 * stop logging in 2 seconds:
202 */
203 if (count > 2*1000*1000 / 200*1000)
204 count = 2*1000*1000 / 200*1000;
205 }
206 fflush_all();
207 wait_more:
208 usleep(200*1000);
209 }
210
211 // [ -e kernel_pacct ] && accton off
212}
213
214static void finalize(char *tempdir)
215{
216 //# Stop process accounting if configured
217 //local pacct=
218 //[ -e kernel_pacct ] && pacct=kernel_pacct
219
220 //(
221 // echo "version = $VERSION"
222 // echo "title = Boot chart for $( hostname | sed q ) ($( date ))"
223 // echo "system.uname = $( uname -srvm | sed q )"
224 // echo "system.release = $( sed q /etc/SuSE-release )"
225 // echo "system.cpu = $( grep '^model name' /proc/cpuinfo | sed q ) ($cpucount)"
226 // echo "system.kernel.options = $( sed q /proc/cmdline )"
227 //) >> header
228
229 /* Package log files */
230 system("tar -zcf /var/log/bootchart.tgz header *.log"); // + $pacct
231 /* Clean up (if we are not in detached tmpfs) */
232 if (tempdir) {
233 unlink("header");
234 unlink("proc_stat.log");
235 unlink("proc_diskstats.log");
236 //unlink("proc_netdev.log");
237 unlink("proc_ps.log");
238 rmdir(tempdir);
239 }
240
241 /* shell-based bootchartd tries to run /usr/bin/bootchart if $AUTO_RENDER=yes:
242 * /usr/bin/bootchart -o "$AUTO_RENDER_DIR" -f $AUTO_RENDER_FORMAT "$BOOTLOG_DEST"
243 */
244}
245
246/* Usage:
247 * bootchartd start [PROG ARGS]: start logging in background, USR1 stops it.
248 * With PROG, runs PROG, then kills background logging.
249 * bootchartd stop: same as "killall -USR1 bootchartd"
250 * bootchartd init: start logging in background
251 * Stop when getty/gdm is seen (if AUTO_STOP_LOGGER = yes).
252 * Meant to be used from init scripts.
253 * bootchartd (pid==1): as init, but then execs $bootchart_init, /init, /sbin/init
254 * Meant to be used as kernel's init process.
255 */
256int bootchartd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
257int bootchartd_main(int argc UNUSED_PARAM, char **argv)
258{
259 pid_t parent_pid, logger_pid;
260 smallint cmd;
261 enum {
262 CMD_STOP = 0,
263 CMD_START,
264 CMD_INIT,
265 CMD_PID1, /* used to mark pid 1 case */
266 };
267
268 INIT_G();
269
270 parent_pid = getpid();
271 if (argv[1]) {
272 cmd = index_in_strings("stop\0""start\0""init\0", argv[1]);
273 if (cmd < 0)
274 bb_show_usage();
275 if (cmd == CMD_STOP) {
276 pid_t *pidList = find_pid_by_name("bootchartd");
277 while (*pidList != 0) {
278 if (*pidList != parent_pid)
279 kill(*pidList, SIGUSR1);
280 pidList++;
281 }
282 return EXIT_SUCCESS;
283 }
284 } else {
285 if (parent_pid != 1)
286 bb_show_usage();
287 cmd = CMD_PID1;
288 }
289
290 /* Here we are in START or INIT state. Create logger child: */
291 logger_pid = fork_or_rexec(argv);
292
293 if (logger_pid == 0) { /* child */
294 char *tempdir;
295
296 bb_signals(0
297 + (1 << SIGUSR1)
298 + (1 << SIGUSR2)
299 + (1 << SIGTERM)
300 + (1 << SIGQUIT)
301 + (1 << SIGINT)
302 + (1 << SIGHUP)
303 , record_signo);
304
305 if (DO_SIGNAL_SYNC)
306 /* Inform parent that we are ready */
307 raise(SIGSTOP);
308
309 /* If we started by kernel, PATH might be not set.
310 * And in order to run tar we may need PATH to be set:
311 */
312 if (cmd == CMD_PID1 && !getenv("PATH"))
313 putenv((char*)bb_PATH_root_path);
314 tempdir = make_tempdir(cmd == CMD_START ? argv[2] : NULL);
315 do_logging();
316 finalize(tempdir);
317 return EXIT_SUCCESS;
318 }
319
320 /* parent */
321
322 if (DO_SIGNAL_SYNC) {
323 /* Wait for logger child to set handlers, then unpause it.
324 * Otherwise with short-lived PROG (e.g. "bootchartd start true")
325 * we might send SIGUSR1 before logger sets its handler.
326 */
327 waitpid(logger_pid, NULL, WUNTRACED);
328 kill(logger_pid, SIGCONT);
329 }
330
331 if (cmd == CMD_PID1) {
332 char *bootchart_init = getenv("bootchart_init");
333 if (bootchart_init)
334 execl(bootchart_init, bootchart_init, NULL);
335 execl("/init", "init", NULL);
336 execl("/sbin/init", "init", NULL);
337 bb_perror_msg_and_die("can't exec '%s'", "/sbin/init");
338 }
339
340 if (cmd == CMD_START && argv[2]) { /* "start PROG ARGS" */
341 pid_t pid = vfork();
342 if (pid < 0)
343 bb_perror_msg_and_die("vfork");
344 if (pid == 0) { /* child */
345 argv += 2;
346 execvp(argv[0], argv);
347 bb_perror_msg_and_die("can't exec '%s'", argv[0]);
348 }
349 /* parent */
350 waitpid(pid, NULL, 0);
351 kill(logger_pid, SIGUSR1);
352 }
353
354 return EXIT_SUCCESS;
355}