bootchartd.c 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /* bootchartd.c - bootchartd is commonly used to profile the boot process.
  2. *
  3. * Copyright 2014 Bilal Qureshi <bilal.jmi@gmail.com>
  4. * Copyright 2014 Kyungwan Han <asura321@gmail.com>
  5. *
  6. * No Standard
  7. USE_BOOTCHARTD(NEWTOY(bootchartd, 0, TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN))
  8. config BOOTCHARTD
  9. bool "bootchartd"
  10. default n
  11. depends on TOYBOX_FORK
  12. help
  13. usage: bootchartd {start [PROG ARGS]}|stop|init
  14. Create /var/log/bootlog.tgz with boot chart data
  15. start: start background logging; with PROG, run PROG,
  16. then kill logging with USR1
  17. stop: send USR1 to all bootchartd processes
  18. init: start background logging; stop when getty/xdm is seen
  19. (for init scripts)
  20. Under PID 1: as init, then exec $bootchart_init, /init, /sbin/init
  21. */
  22. #define FOR_bootchartd
  23. #include "toys.h"
  24. GLOBALS(
  25. char timestamp[32];
  26. long msec;
  27. int proc_accounting;
  28. pid_t pid;
  29. )
  30. static void dump_data_in_file(char *fname, int wfd)
  31. {
  32. int rfd = open(fname, O_RDONLY);
  33. if (rfd != -1) {
  34. xwrite(wfd, TT.timestamp, strlen(TT.timestamp));
  35. xsendfile(rfd, wfd);
  36. close(rfd);
  37. xwrite(wfd, "\n", 1);
  38. }
  39. }
  40. static int dump_proc_data(FILE *fp)
  41. {
  42. struct dirent *pid_dir;
  43. int login_flag = 0;
  44. pid_t pid;
  45. DIR *proc_dir = opendir("/proc");
  46. fputs(TT.timestamp, fp);
  47. while ((pid_dir = readdir(proc_dir))) {
  48. char filename[64];
  49. int fd;
  50. if (!isdigit(pid_dir->d_name[0])) continue;
  51. sscanf(pid_dir->d_name, "%d", &pid);
  52. sprintf(filename, "/proc/%d/stat", pid);
  53. if ((fd = open(filename, O_RDONLY)) != -1 ) {
  54. char *ptr;
  55. ssize_t len;
  56. if ((len = readall(fd, toybuf, sizeof(toybuf)-1)) < 0) {
  57. xclose(fd);
  58. continue;
  59. }
  60. toybuf[len] = '\0';
  61. close(fd);
  62. fputs(toybuf, fp);
  63. if (TT.pid != 1) continue;
  64. if ((ptr = strchr(toybuf, '('))) {
  65. char *tmp = strchr(++ptr, ')');
  66. if (tmp) *tmp = '\0';
  67. }
  68. // Checks for gdm, kdm or getty
  69. if (((ptr[0] == 'g' || ptr[0] == 'k' || ptr[0] == 'x') && ptr[1] == 'd'
  70. && ptr[2] == 'm') || strstr(ptr, "getty")) login_flag = 1;
  71. }
  72. }
  73. closedir(proc_dir);
  74. fputc('\n', fp);
  75. return login_flag;
  76. }
  77. static int parse_config_file(char *fname)
  78. {
  79. size_t len = 0;
  80. char *line = 0;
  81. FILE *fp = fopen(fname, "r");
  82. if (!fp) return 0;
  83. for (;getline(&line, &len, fp) != -1; line = 0) {
  84. char *ptr = line;
  85. while (*ptr == ' ' || *ptr == '\t') ptr++;
  86. if (!*ptr || *ptr == '#' || *ptr == '\n') continue;
  87. if (strstart(&ptr, "SAMPLE_PERIOD=")) {
  88. double dd;
  89. sscanf(ptr, "%lf", &dd);
  90. if ((TT.msec = dd*1000)<1) TT.msec = 1;
  91. } else if (strstart(&ptr, "PROCESS_ACCOUNTING="))
  92. if (strstart(&ptr, "\"on\"") || strstart(&ptr, "\"yes\""))
  93. TT.proc_accounting = 1;
  94. free(line);
  95. }
  96. fclose(fp);
  97. return 1;
  98. }
  99. static char *create_tmp_dir()
  100. {
  101. char *dir_list[] = {"/tmp", "/mnt", "/boot", "/proc"}, **target = dir_list;
  102. char *dir, dir_path[] = "/tmp/bootchart.XXXXXX";
  103. if ((dir = mkdtemp(dir_path))) {
  104. xchdir((dir = xstrdup(dir)));
  105. return dir;
  106. }
  107. while (mount("none", *target, "tmpfs", (1<<15), "size=16m")) //MS_SILENT
  108. if (!++target) perror_exit("can't mount tmpfs");
  109. xchdir(*target);
  110. if (umount2(*target, MNT_DETACH)) perror_exit("Can't unmount tmpfs");
  111. return *target;
  112. }
  113. static void start_logging()
  114. {
  115. struct timespec ts;
  116. int proc_stat_fd = xcreate("proc_stat.log",
  117. O_WRONLY | O_CREAT | O_TRUNC, 0644);
  118. int proc_diskstats_fd = xcreate("proc_diskstats.log",
  119. O_WRONLY | O_CREAT | O_TRUNC, 0644);
  120. FILE *proc_ps_fp = xfopen("proc_ps.log", "w");
  121. long tcnt = 60 * 1000 / TT.msec;
  122. if (tcnt <= 0) tcnt = 1;
  123. if (TT.proc_accounting) {
  124. int kp_fd = xcreate("kernel_procs_acct", O_WRONLY | O_CREAT | O_TRUNC,0666);
  125. xclose(kp_fd);
  126. acct("kernel_procs_acct");
  127. }
  128. while (--tcnt && !toys.signal) {
  129. clock_gettime(CLOCK_BOOTTIME, &ts);
  130. sprintf(TT.timestamp, "%ld.%02d\n", (long) ts.tv_sec,
  131. (int) (ts.tv_nsec/10000000));
  132. dump_data_in_file("/proc/stat", proc_stat_fd);
  133. dump_data_in_file("/proc/diskstats", proc_diskstats_fd);
  134. // stop proc dumping in 2 secs if getty or gdm, kdm, xdm found
  135. if (dump_proc_data(proc_ps_fp))
  136. if (tcnt > 2 * 1000 / TT.msec) tcnt = 2 * 1000 / TT.msec;
  137. fflush(0);
  138. msleep(TT.msec);
  139. }
  140. xclose(proc_stat_fd);
  141. xclose(proc_diskstats_fd);
  142. fclose(proc_ps_fp);
  143. }
  144. static void stop_logging(char *tmp_dir, char *prog)
  145. {
  146. char host_name[32];
  147. int kcmd_line_fd;
  148. time_t t;
  149. struct tm st;
  150. struct utsname uts;
  151. FILE *hdr_fp = xfopen("header", "w");
  152. if (TT.proc_accounting) acct(NULL);
  153. if (prog) fprintf(hdr_fp, "profile.process = %s\n", prog);
  154. gethostname(host_name, sizeof(host_name));
  155. time(&t);
  156. localtime_r(&t, &st);
  157. memset(toybuf, 0, sizeof(toybuf));
  158. strftime(toybuf, sizeof(toybuf), "%a %b %e %H:%M:%S %Z %Y", &st);
  159. fprintf(hdr_fp, "version = TBX_BCHARTD_VER 1.0.0\n");
  160. fprintf(hdr_fp, "title = Boot chart for %s (%s)\n", host_name, toybuf);
  161. if (uname(&uts) < 0) perror_exit("uname");
  162. fprintf(hdr_fp, "system.uname = %s %s %s %s\n", uts.sysname, uts.release,
  163. uts.version, uts.machine);
  164. memset(toybuf, 0, sizeof(toybuf));
  165. if ((kcmd_line_fd = open("/proc/cmdline", O_RDONLY)) != -1) {
  166. ssize_t len;
  167. if ((len = readall(kcmd_line_fd, toybuf, sizeof(toybuf)-1)) > 0) {
  168. toybuf[len] = 0;
  169. while (--len >= 0 && !toybuf[len]) continue;
  170. for (; len > 0; len--) if (toybuf[len] < ' ') toybuf[len] = ' ';
  171. } else *toybuf = 0;
  172. }
  173. fprintf(hdr_fp, "system.kernel.options = %s", toybuf);
  174. close(kcmd_line_fd);
  175. fclose(hdr_fp);
  176. memset(toybuf, 0, sizeof(toybuf));
  177. snprintf(toybuf, sizeof(toybuf), "tar -zcf /var/log/bootlog.tgz header %s *.log",
  178. TT.proc_accounting ? "kernel_procs_acct" : "");
  179. system(toybuf);
  180. if (tmp_dir) {
  181. unlink("header");
  182. unlink("proc_stat.log");
  183. unlink("proc_diskstats.log");
  184. unlink("proc_ps.log");
  185. if (TT.proc_accounting) unlink("kernel_procs_acct");
  186. rmdir(tmp_dir);
  187. }
  188. }
  189. static int signal_pid(pid_t pid, char *name)
  190. {
  191. if (pid != TT.pid) kill(pid, SIGUSR1);
  192. return 0;
  193. }
  194. void bootchartd_main()
  195. {
  196. pid_t lgr_pid;
  197. int bchartd_opt = 0; // 0=PID1, 1=start, 2=stop, 3=init
  198. TT.pid = getpid();
  199. TT.msec = 200;
  200. if (*toys.optargs) {
  201. if (!strcmp("start", *toys.optargs)) bchartd_opt = 1;
  202. else if (!strcmp("stop", *toys.optargs)) bchartd_opt = 2;
  203. else if (!strcmp("init", *toys.optargs)) bchartd_opt = 3;
  204. else error_exit("Unknown option '%s'", *toys.optargs);
  205. if (bchartd_opt == 2) {
  206. char *process_name[] = {"bootchartd", NULL};
  207. names_to_pid(process_name, signal_pid, 0);
  208. return;
  209. }
  210. } else if (TT.pid != 1) error_exit("not PID 1");
  211. // Execute the code below for start or init or PID1
  212. if (!parse_config_file("bootchartd.conf"))
  213. parse_config_file("/etc/bootchartd.conf");
  214. memset(toybuf, 0, sizeof(toybuf));
  215. if (!(lgr_pid = xfork())) {
  216. char *tmp_dir = create_tmp_dir();
  217. sigatexit(generic_signal);
  218. raise(SIGSTOP);
  219. if (!bchartd_opt && !getenv("PATH"))
  220. putenv("PATH=/sbin:/usr/sbin:/bin:/usr/bin");
  221. start_logging();
  222. stop_logging(tmp_dir, bchartd_opt == 1 ? toys.optargs[1] : NULL);
  223. return;
  224. }
  225. waitpid(lgr_pid, NULL, WUNTRACED);
  226. kill(lgr_pid, SIGCONT);
  227. if (!bchartd_opt) {
  228. char *pbchart_init = getenv("bootchart_init");
  229. if (pbchart_init) execl(pbchart_init, pbchart_init, NULL);
  230. execl("/init", "init", (void *)0);
  231. execl("/sbin/init", "init", (void *)0);
  232. }
  233. if (bchartd_opt == 1 && toys.optargs[1]) {
  234. pid_t prog_pid;
  235. if (!(prog_pid = xfork())) xexec(toys.optargs+1);
  236. waitpid(prog_pid, NULL, 0);
  237. kill(lgr_pid, SIGUSR1);
  238. }
  239. }