blob: 3d2a52e55fd8754fd7c5720d9a0b398e854a0c3d [file] [log] [blame]
Jamie Gennisb11abad2012-02-23 14:16:05 -08001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <errno.h>
18#include <fcntl.h>
19#include <signal.h>
20#include <stdarg.h>
21#include <stdbool.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <sys/sendfile.h>
25#include <time.h>
26
27/* Command line options */
28static int g_traceDurationSeconds = 5;
29static bool g_traceSchedSwitch = false;
30static bool g_traceWorkqueue = false;
31static bool g_traceOverwrite = false;
32
33/* Global state */
34static bool g_traceAborted = false;
35
36/* Sys file paths */
37static const char* k_traceClockPath =
38 "/sys/kernel/debug/tracing/trace_clock";
39
40static const char* k_tracingOverwriteEnablePath =
41 "/sys/kernel/debug/tracing/options/overwrite";
42
43static const char* k_schedSwitchEnablePath =
44 "/sys/kernel/debug/tracing/events/sched/sched_switch/enable";
45
46static const char* k_workqueueEnablePath =
47 "/sys/kernel/debug/tracing/events/workqueue/enable";
48
49static const char* k_tracingOnPath =
50 "/sys/kernel/debug/tracing/tracing_on";
51
52static const char* k_tracePath =
53 "/sys/kernel/debug/tracing/trace";
54
55static const char* k_traceMarkerPath =
56 "/sys/kernel/debug/tracing/trace_marker";
57
58// Write a string to a file, returning true if the write was successful.
59bool writeStr(const char* filename, const char* str)
60{
61 int fd = open(filename, O_WRONLY);
62 if (fd == -1) {
63 fprintf(stderr, "error opening %s: %s (%d)\n", filename,
64 strerror(errno), errno);
65 return false;
66 }
67
68 bool ok = true;
69 ssize_t len = strlen(str);
70 if (write(fd, str, len) != len) {
71 fprintf(stderr, "error writing to %s: %s (%d)\n", filename,
72 strerror(errno), errno);
73 ok = false;
74 }
75
76 close(fd);
77
78 return ok;
79}
80
81// Enable or disable a kernel option by writing a "1" or a "0" into a /sys file.
82static bool setKernelOptionEnable(const char* filename, bool enable)
83{
84 return writeStr(filename, enable ? "1" : "0");
85}
86
87// Enable or disable overwriting of the kernel trace buffers. Disabling this
88// will cause tracing to stop once the trace buffers have filled up.
89static bool setTraceOverwriteEnable(bool enable)
90{
91 return setKernelOptionEnable(k_tracingOverwriteEnablePath, enable);
92}
93
94// Enable or disable tracing of the kernel scheduler switching.
95static bool setSchedSwitchTracingEnable(bool enable)
96{
97 return setKernelOptionEnable(k_schedSwitchEnablePath, enable);
98}
99
100// Enable or disable tracing of the kernel workqueues.
101static bool setWorkqueueTracingEnabled(bool enable)
102{
103 return setKernelOptionEnable(k_workqueueEnablePath, enable);
104}
105
106// Enable or disable kernel tracing.
107static bool setTracingEnabled(bool enable)
108{
109 return setKernelOptionEnable(k_tracingOnPath, enable);
110}
111
112// Clear the contents of the kernel trace.
113static bool clearTrace()
114{
115 int traceFD = creat(k_tracePath, 0);
116 if (traceFD == -1) {
117 fprintf(stderr, "error truncating %s: %s (%d)\n", k_tracePath,
118 strerror(errno), errno);
119 return false;
120 }
121
122 close(traceFD);
123
124 return true;
125}
126
127// Enable or disable the kernel's use of the global clock. Disabling the global
128// clock will result in the kernel using a per-CPU local clock.
129static bool setGlobalClockEnable(bool enable)
130{
131 return writeStr(k_traceClockPath, enable ? "global" : "local");
132}
133
134// Enable tracing in the kernel.
135static bool startTrace()
136{
137 bool ok = true;
138
139 // Set up the tracing options.
140 ok &= setTraceOverwriteEnable(g_traceOverwrite);
141 ok &= setSchedSwitchTracingEnable(g_traceSchedSwitch);
142 ok &= setWorkqueueTracingEnabled(g_traceWorkqueue);
143 ok &= setGlobalClockEnable(true);
144
145 // Enable tracing.
146 ok &= setTracingEnabled(true);
147
148 if (!ok) {
149 fprintf(stderr, "error: unable to start trace\n");
150 }
151
152 return ok;
153}
154
155// Disable tracing in the kernel.
156static void stopTrace()
157{
158 // Disable tracing.
159 setTracingEnabled(false);
160
161 // Set the options back to their defaults.
162 setTraceOverwriteEnable(true);
163 setSchedSwitchTracingEnable(false);
164 setWorkqueueTracingEnabled(false);
165 setGlobalClockEnable(false);
166}
167
168// Read the current kernel trace and write it to stdout.
169static void dumpTrace()
170{
171 int traceFD = open(k_tracePath, O_RDWR);
172 if (traceFD == -1) {
173 fprintf(stderr, "error opening %s: %s (%d)\n", k_tracePath,
174 strerror(errno), errno);
175 return;
176 }
177
178 ssize_t sent = 0;
179 while ((sent = sendfile(STDOUT_FILENO, traceFD, NULL, 64*1024*1024)) > 0);
180 if (sent == -1) {
181 fprintf(stderr, "error dumping trace: %s (%d)\n", strerror(errno),
182 errno);
183 }
184
185 close(traceFD);
186}
187
188// Print the command usage help to stderr.
189static void showHelp(const char *cmd)
190{
191 fprintf(stderr, "usage: %s [options]\n", cmd);
192 fprintf(stderr, "options include:\n"
193 " -c trace into a circular buffer\n"
194 " -s trace the kernel scheduler switches\n"
195 " -t N trace for N seconds [defualt 5]\n"
196 " -w trace the kernel workqueue\n");
197}
198
199static void handleSignal(int signo) {
200 g_traceAborted = true;
201}
202
203static void registerSigHandler() {
204 struct sigaction sa;
205 sigemptyset(&sa.sa_mask);
206 sa.sa_flags = 0;
207 sa.sa_handler = handleSignal;
208 sigaction(SIGHUP, &sa, NULL);
209 sigaction(SIGINT, &sa, NULL);
210 sigaction(SIGQUIT, &sa, NULL);
211 sigaction(SIGTERM, &sa, NULL);
212}
213
214int main(int argc, char **argv)
215{
216 if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
217 showHelp(argv[0]);
218 exit(0);
219 }
220
221 if (getuid() != 0) {
222 fprintf(stderr, "error: %s must be run as root.", argv[0]);
223 }
224
225 for (;;) {
226 int ret;
227
228 ret = getopt(argc, argv, "cst:w");
229
230 if (ret < 0) {
231 break;
232 }
233
234 switch(ret) {
235 case 'c':
236 g_traceOverwrite = true;
237 break;
238
239 case 's':
240 g_traceSchedSwitch = true;
241 break;
242
243 case 't':
244 g_traceDurationSeconds = atoi(optarg);
245 break;
246
247 case 'w':
248 g_traceWorkqueue = true;
249 break;
250
251 default:
252 showHelp(argv[0]);
253 exit(-1);
254 break;
255 }
256 }
257
258 registerSigHandler();
259
260 bool ok = startTrace();
261
262 if (ok) {
263 printf("capturing trace...");
264 fflush(stdout);
265
266 // We clear the trace after starting it because tracing gets enabled for
267 // each CPU individually in the kernel. Having the beginning of the trace
268 // contain entries from only one CPU can cause "begin" entries without a
269 // matching "end" entry to show up if a task gets migrated from one CPU to
270 // another.
271 ok = clearTrace();
272
273 if (ok) {
274 // Sleep to allow the trace to be captured.
275 struct timespec timeLeft;
276 timeLeft.tv_sec = g_traceDurationSeconds;
277 timeLeft.tv_nsec = 0;
278 do {
279 if (g_traceAborted) {
280 break;
281 }
282 } while (nanosleep(&timeLeft, &timeLeft) == -1 && errno == EINTR);
283 }
284 }
285
286 // Stop the trace and restore the default settings.
287 stopTrace();
288
289 if (ok) {
290 if (!g_traceAborted) {
291 printf(" done\nTRACE:\n");
292 fflush(stdout);
293 dumpTrace();
294 } else {
295 printf("\ntrace aborted.\n");
296 fflush(stdout);
297 }
298 clearTrace();
299 } else {
300 fprintf(stderr, "unable to start tracing\n");
301 }
302
303 return g_traceAborted ? 1 : 0;
304}