atrace.c revision 6b995818535af84c4a6829af7733684861f20144
1/*
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}
305