logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

time.c (4955B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. #include "../libutils/getopt_nolong.h"
  6. #include <errno.h>
  7. #include <signal.h>
  8. #include <spawn.h>
  9. #include <stdio.h> // perror, fprintf
  10. #include <stdlib.h> // abort
  11. #include <string.h> // strerror
  12. #include <sys/resource.h> // getrusage
  13. #include <sys/times.h>
  14. #include <sys/wait.h> // waitpid
  15. #include <unistd.h> // sysconf, getopt
  16. extern char **environ;
  17. const char *argv0 = "time";
  18. static pid_t child = -1;
  19. enum cmd_time_mode
  20. {
  21. CMD_TIME_POSIX = 0,
  22. CMD_TIME_VERBOSE = 1,
  23. };
  24. /*
  25. * no-op signal handler so sigprocmask/sigwait works on BSDs where signals
  26. * that are ignored/discarded by default aren't blocked unless they
  27. * have a handler
  28. */
  29. static void
  30. sig_noop(int sig)
  31. {
  32. return;
  33. }
  34. static void
  35. usage(void)
  36. {
  37. fprintf(stderr, "Usage: time [-p|-v] command [argument ...]\n");
  38. }
  39. int
  40. main(int argc, char *argv[])
  41. {
  42. struct tms tms;
  43. int ret = 0;
  44. enum cmd_time_mode mode = CMD_TIME_POSIX;
  45. if(argc <= 1)
  46. {
  47. usage();
  48. return 0;
  49. }
  50. for(int c = -1; (c = getopt_nolong(argc, argv, ":pv")) != -1;)
  51. {
  52. switch(c)
  53. {
  54. case 'p': // POSIX format (default)
  55. mode = CMD_TIME_POSIX;
  56. break;
  57. case 'v':
  58. mode = CMD_TIME_VERBOSE;
  59. break;
  60. case ':':
  61. fprintf(stderr, "time: error: Missing operand for option: '-%c'\n", optopt);
  62. usage();
  63. return 1;
  64. case '?':
  65. GETOPT_UNKNOWN_OPT
  66. usage();
  67. return 1;
  68. }
  69. }
  70. argc -= optind;
  71. argv += optind;
  72. (void)argc;
  73. long ticks = sysconf(_SC_CLK_TCK);
  74. if(ticks <= 0)
  75. {
  76. perror("time: error: sysconf(_SC_CLK_TCK)");
  77. return 1;
  78. }
  79. clock_t t0 = times(&tms);
  80. if(t0 == (clock_t)-1)
  81. {
  82. perror("time: error: times");
  83. return 1;
  84. }
  85. sigset_t sigset;
  86. if(sigfillset(&sigset) != 0) return 1;
  87. struct sigaction sa = {
  88. .sa_handler = &sig_noop,
  89. .sa_flags = 0,
  90. };
  91. if(sigaction(SIGCHLD, &sa, NULL) != 0)
  92. {
  93. fprintf(stderr, "time: error: Failed setting SIGCHLD handler: %s\n", strerror(errno));
  94. return 1;
  95. }
  96. if(sigprocmask(SIG_BLOCK, &sigset, NULL) != 0)
  97. {
  98. fprintf(stderr, "time: error: Failed setting process' signal mask: %s\n", strerror(errno));
  99. return 1;
  100. }
  101. if(posix_spawnp(&child, argv[0], NULL, NULL, argv, environ) != 0)
  102. {
  103. ret = 126 + (errno == ENOENT);
  104. fprintf(stderr, "time: error: Failed executing '%s': %s\n", argv[0], strerror(errno));
  105. return ret;
  106. }
  107. int status = 0;
  108. // Loop so signals are repeateadly propagated until child terminates
  109. while(true)
  110. {
  111. int sig = -1;
  112. if(sigwait(&sigset, &sig) != 0)
  113. {
  114. fprintf(stderr, "time: error: Failed waiting for signals: %s\n", strerror(errno));
  115. return 1;
  116. }
  117. int ret = waitpid(child, &status, WNOHANG);
  118. if(ret == child)
  119. {
  120. child = -1;
  121. break;
  122. }
  123. else if(ret == -1)
  124. {
  125. fprintf(stderr, "time: error: Failed waiting for child process: %s\n", strerror(errno));
  126. return 1;
  127. }
  128. if(kill(child, sig) != 0)
  129. {
  130. // candicate for sig2str
  131. fprintf(stderr,
  132. "time: warning: Failed propagating signal number %d to child process (%d): %s\n",
  133. sig,
  134. child,
  135. strerror(errno));
  136. }
  137. }
  138. clock_t t1 = times(&tms);
  139. if(t1 == -1)
  140. {
  141. perror("time: error: times");
  142. return 1;
  143. }
  144. if(WIFSIGNALED(status))
  145. {
  146. fprintf(stderr, "time: error: Command terminated by signal %d\n", WTERMSIG(status));
  147. ret = 128 + WTERMSIG(status);
  148. }
  149. struct rusage usage;
  150. if(getrusage(RUSAGE_CHILDREN, &usage) != 0)
  151. {
  152. fprintf(
  153. stderr, "time: error: Failed getting resource usage of children: %s\n", strerror(errno));
  154. return 1;
  155. }
  156. double utime_s = usage.ru_utime.tv_sec + (usage.ru_utime.tv_usec * 0.000001);
  157. double stime_s = usage.ru_stime.tv_sec + (usage.ru_stime.tv_usec * 0.000001);
  158. switch(mode)
  159. {
  160. case CMD_TIME_POSIX:
  161. fprintf(stderr, "real %f\nuser %f\nsys %f\n", (t1 - t0) / (double)ticks, utime_s, stime_s);
  162. break;
  163. case CMD_TIME_VERBOSE:
  164. fprintf(stderr, "\tExecutable: %s\n", argv[0]);
  165. fprintf(stderr, "\tReal time : %06f\n", (t1 - t0) / (double)ticks);
  166. fprintf(stderr, "\tUser time : %06f\n", utime_s);
  167. fprintf(stderr, "\tSystem time: %06f\n", stime_s);
  168. #ifdef __linux__
  169. fprintf(stderr, "\tMaximum resident set size (maxrss): %ld kB\n", usage.ru_maxrss);
  170. fprintf(stderr, "\tMinor (no I/O required) page faults (minflt): %ld\n", usage.ru_minflt);
  171. fprintf(stderr, "\tMajor (I/O required) page faults (majflt): %ld\n", usage.ru_majflt);
  172. fprintf(stderr, "\tMajor (I/O required) page faults (majflt): %ld\n", usage.ru_majflt);
  173. fprintf(stderr, "\tVoluntary context switch (nvcsw): %ld\n", usage.ru_nvcsw);
  174. fprintf(stderr, "\tInvoluntary context switch (nivcsw): %ld\n", usage.ru_nivcsw);
  175. #endif
  176. if(WIFEXITED(status)) fprintf(stderr, "\tExit status: %d\n", WEXITSTATUS(status));
  177. break;
  178. default:
  179. abort();
  180. }
  181. if(WIFEXITED(status))
  182. {
  183. ret = WEXITSTATUS(status);
  184. }
  185. return ret;
  186. }