diff --git a/cmds/atrace/Android.mk b/cmds/atrace/Android.mk index 12526d023..028ca8f35 100644 --- a/cmds/atrace/Android.mk +++ b/cmds/atrace/Android.mk @@ -3,15 +3,18 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= atrace.c +LOCAL_SRC_FILES:= atrace.cpp LOCAL_C_INCLUDES += external/zlib -LOCAL_CFLAGS += -std=c99 LOCAL_MODULE:= atrace LOCAL_MODULE_TAGS:= optional -LOCAL_SHARED_LIBRARIES := libz +LOCAL_SHARED_LIBRARIES := \ + libbinder \ + libcutils \ + libutils \ + libz \ include $(BUILD_EXECUTABLE) diff --git a/cmds/atrace/atrace.c b/cmds/atrace/atrace.cpp similarity index 55% rename from cmds/atrace/atrace.c rename to cmds/atrace/atrace.cpp index 64f13e4eb..a4bf674ea 100644 --- a/cmds/atrace/atrace.c +++ b/cmds/atrace/atrace.cpp @@ -26,18 +26,90 @@ #include #include +#include +#include +#include + +#include + +#include +#include + +using namespace android; + #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) +enum { MAX_SYS_FILES = 8 }; + +const char* k_traceTagsProperty = "debug.atrace.tags.enableflags"; + +typedef enum { OPT, REQ } requiredness ; + +struct TracingCategory { + // The name identifying the category. + const char* name; + + // A longer description of the category. + const char* longname; + + // The userland tracing tags that the category enables. + uint64_t tags; + + // The fname==NULL terminated list of /sys/ files that the category + // enables. + struct { + // Whether the file must be writable in order to enable the tracing + // category. + requiredness required; + + // The path to the enable file. + const char* path; + } sysfiles[MAX_SYS_FILES]; +}; + +/* Tracing categories */ +static const TracingCategory k_categories[] = { + { "gfx", "Graphics", ATRACE_TAG_GRAPHICS, { } }, + { "input", "Input", ATRACE_TAG_INPUT, { } }, + { "view", "View System", ATRACE_TAG_VIEW, { } }, + { "wm", "Window Manager", ATRACE_TAG_WINDOW_MANAGER, { } }, + { "am", "Activity Manager", ATRACE_TAG_ACTIVITY_MANAGER, { } }, + { "audio", "Audio", ATRACE_TAG_AUDIO, { } }, + { "video", "Video", ATRACE_TAG_VIDEO, { } }, + { "camera", "Camera", ATRACE_TAG_CAMERA, { } }, + { "sched", "CPU Scheduling", 0, { + { REQ, "/sys/kernel/debug/tracing/events/sched/sched_switch/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/sched/sched_wakeup/enable" }, + } }, + { "freq", "CPU Frequency", 0, { + { REQ, "/sys/kernel/debug/tracing/events/power/cpu_frequency/enable" }, + { OPT, "/sys/kernel/debug/tracing/events/power/clock_set_rate/enable" }, + } }, + { "membus", "Memory Bus Utilization", 0, { + { REQ, "/sys/kernel/debug/tracing/events/memory_bus/enable" }, + } }, + { "idle", "CPU Idle", 0, { + { REQ, "/sys/kernel/debug/tracing/events/power/cpu_idle/enable" }, + } }, + { "disk", "Disk I/O", 0, { + { REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/block/block_rq_issue/enable" }, + { REQ, "/sys/kernel/debug/tracing/events/block/block_rq_complete/enable" }, + } }, + { "load", "CPU Load", 0, { + { REQ, "/sys/kernel/debug/tracing/events/cpufreq_interactive/enable" }, + } }, + { "sync", "Synchronization", 0, { + { REQ, "/sys/kernel/debug/tracing/events/sync/enable" }, + } }, + { "workq", "Kernel Workqueues", 0, { + { REQ, "/sys/kernel/debug/tracing/events/workqueue/enable" }, + } }, +}; + /* Command line options */ static int g_traceDurationSeconds = 5; -static bool g_traceSchedSwitch = false; -static bool g_traceFrequency = false; -static bool g_traceBusUtilization = false; -static bool g_traceCpuIdle = false; -static bool g_traceDisk = false; -static bool g_traceGovernorLoad = false; -static bool g_traceSync = false; -static bool g_traceWorkqueue = false; static bool g_traceOverwrite = false; static int g_traceBufferSizeKB = 2048; static bool g_compress = false; @@ -46,6 +118,7 @@ static int g_initialSleepSecs = 0; /* Global state */ static bool g_traceAborted = false; +static bool g_categoryEnables[NELEM(k_categories)] = {}; /* Sys file paths */ static const char* k_traceClockPath = @@ -57,56 +130,24 @@ static const char* k_traceBufferSizePath = static const char* k_tracingOverwriteEnablePath = "/sys/kernel/debug/tracing/options/overwrite"; -static const char* k_schedSwitchEnablePath = - "/sys/kernel/debug/tracing/events/sched/sched_switch/enable"; - -static const char* k_schedWakeupEnablePath = - "/sys/kernel/debug/tracing/events/sched/sched_wakeup/enable"; - -static const char* k_memoryBusEnablePath = - "/sys/kernel/debug/tracing/events/memory_bus/enable"; - -static const char* k_cpuFreqEnablePath = - "/sys/kernel/debug/tracing/events/power/cpu_frequency/enable"; - -static const char *k_clockSetRateEnablePath = - "/sys/kernel/debug/tracing/events/power/clock_set_rate/enable"; - -static const char* k_cpuIdleEnablePath = - "/sys/kernel/debug/tracing/events/power/cpu_idle/enable"; - -static const char* k_governorLoadEnablePath = - "/sys/kernel/debug/tracing/events/cpufreq_interactive/enable"; - -static const char* k_syncEnablePath = - "/sys/kernel/debug/tracing/events/sync/enable"; - -static const char* k_workqueueEnablePath = - "/sys/kernel/debug/tracing/events/workqueue/enable"; - -static const char* k_diskEnablePaths[] = { - "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable", - "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable", - "/sys/kernel/debug/tracing/events/block/block_rq_issue/enable", - "/sys/kernel/debug/tracing/events/block/block_rq_complete/enable", -}; - static const char* k_tracingOnPath = "/sys/kernel/debug/tracing/tracing_on"; static const char* k_tracePath = "/sys/kernel/debug/tracing/trace"; -static const char* k_traceMarkerPath = - "/sys/kernel/debug/tracing/trace_marker"; - // Check whether a file exists. static bool fileExists(const char* filename) { return access(filename, F_OK) != -1; } +// Check whether a file is writable. +static bool fileIsWritable(const char* filename) { + return access(filename, W_OK) != -1; +} + // Write a string to a file, returning true if the write was successful. -bool writeStr(const char* filename, const char* str) +static bool writeStr(const char* filename, const char* str) { int fd = open(filename, O_WRONLY); if (fd == -1) { @@ -128,20 +169,61 @@ bool writeStr(const char* filename, const char* str) return ok; } -// Enable or disable a kernel option by writing a "1" or a "0" into a /sys file. +// Enable or disable a kernel option by writing a "1" or a "0" into a /sys +// file. static bool setKernelOptionEnable(const char* filename, bool enable) { return writeStr(filename, enable ? "1" : "0"); } -// Enable or disable a collection of kernel options by writing a "1" or a "0" into each /sys file. -static bool setMultipleKernelOptionsEnable(const char** filenames, size_t count, bool enable) +// Check whether the category is supported on the device with the current +// rootness. A category is supported only if all its required /sys/ files are +// writable and if enabling the category will enable one or more tracing tags +// or /sys/ files. +static bool isCategorySupported(const TracingCategory& category) { - bool result = true; - for (size_t i = 0; i < count; i++) { - result &= setKernelOptionEnable(filenames[i], enable); + bool ok = category.tags != 0; + for (int i = 0; i < MAX_SYS_FILES; i++) { + const char* path = category.sysfiles[i].path; + bool req = category.sysfiles[i].required == REQ; + if (path != NULL) { + if (req) { + if (!fileIsWritable(path)) { + return false; + } else { + ok = true; + } + } else { + ok |= fileIsWritable(path); + } + } } - return result; + return ok; +} + +// Check whether the category would be supported on the device if the user +// were root. This function assumes that root is able to write to any file +// that exists. It performs the same logic as isCategorySupported, but it +// uses file existance rather than writability in the /sys/ file checks. +static bool isCategorySupportedForRoot(const TracingCategory& category) +{ + bool ok = category.tags != 0; + for (int i = 0; i < MAX_SYS_FILES; i++) { + const char* path = category.sysfiles[i].path; + bool req = category.sysfiles[i].required == REQ; + if (path != NULL) { + if (req) { + if (!fileExists(path)) { + return false; + } else { + ok = true; + } + } else { + ok |= fileExists(path); + } + } + } + return ok; } // Enable or disable overwriting of the kernel trace buffers. Disabling this @@ -151,78 +233,6 @@ static bool setTraceOverwriteEnable(bool enable) return setKernelOptionEnable(k_tracingOverwriteEnablePath, enable); } -// Enable or disable tracing of the kernel scheduler switching. -static bool setSchedSwitchTracingEnable(bool enable) -{ - bool ok = true; - ok &= setKernelOptionEnable(k_schedSwitchEnablePath, enable); - ok &= setKernelOptionEnable(k_schedWakeupEnablePath, enable); - return ok; -} - -// Enable or disable tracing of the Bus utilization. -static bool setBusUtilizationTracingEnable(bool enable) -{ - bool ok = true, oneSet = false; - // these can be platform specific so make sure that at least - // one succeeds. - if (fileExists(k_memoryBusEnablePath)) { - ok &= setKernelOptionEnable(k_memoryBusEnablePath, enable); - oneSet |= ok; - } - return ok && (oneSet || !enable); -} - -// Enable or disable tracing of the CPU clock frequency. -static bool setFrequencyTracingEnable(bool enable) -{ - bool ok = true; - ok &= setKernelOptionEnable(k_cpuFreqEnablePath, enable); - if (fileExists(k_clockSetRateEnablePath)) { - ok &= setKernelOptionEnable(k_clockSetRateEnablePath, enable); - } - return ok; -} - -// Enable or disable tracing of CPU idle events. -static bool setCpuIdleTracingEnable(bool enable) -{ - return setKernelOptionEnable(k_cpuIdleEnablePath, enable); -} - -// Enable or disable tracing of the interactive CPU frequency governor's idea of -// the CPU load. -static bool setGovernorLoadTracingEnable(bool enable) -{ - bool ok = true; - if (fileExists(k_governorLoadEnablePath) || enable) { - ok &= setKernelOptionEnable(k_governorLoadEnablePath, enable); - } - return ok; -} - -// Enable or disable tracing of sync timelines and waits. -static bool setSyncTracingEnabled(bool enable) -{ - bool ok = true; - if (fileExists(k_syncEnablePath) || enable) { - ok &= setKernelOptionEnable(k_syncEnablePath, enable); - } - return ok; -} - -// Enable or disable tracing of the kernel workqueues. -static bool setWorkqueueTracingEnabled(bool enable) -{ - return setKernelOptionEnable(k_workqueueEnablePath, enable); -} - -// Enable or disable tracing of disk I/O. -static bool setDiskTracingEnabled(bool enable) -{ - return setMultipleKernelOptionsEnable(k_diskEnablePaths, NELEM(k_diskEnablePaths), enable); -} - // Enable or disable kernel tracing. static bool setTracingEnabled(bool enable) { @@ -263,60 +273,126 @@ static bool setGlobalClockEnable(bool enable) return writeStr(k_traceClockPath, enable ? "global" : "local"); } +// Poke all the binder-enabled processes in the system to get them to re-read +// their system properties. +static bool pokeBinderServices() +{ + sp sm = defaultServiceManager(); + Vector services = sm->listServices(); + for (size_t i = 0; i < services.size(); i++) { + sp obj = sm->checkService(services[i]); + if (obj != NULL) { + Parcel data; + if (obj->transact(IBinder::SYSPROPS_TRANSACTION, data, + NULL, 0) != OK) { + if (false) { + // XXX: For some reason this fails on tablets trying to + // poke the "phone" service. It's not clear whether some + // are expected to fail. + String8 svc(services[i]); + fprintf(stderr, "error poking binder service %s\n", + svc.string()); + return false; + } + } + } + } + return true; +} + +// Set the trace tags that userland tracing uses, and poke the running +// processes to pick up the new value. +static bool setTagsProperty(uint64_t tags) +{ + char buf[64]; + snprintf(buf, 64, "%#llx", tags); + if (property_set(k_traceTagsProperty, buf) < 0) { + fprintf(stderr, "error setting trace tags system property\n"); + return false; + } + return pokeBinderServices(); +} + +// Disable all /sys/ enable files. +static bool disableKernelTraceEvents() { + bool ok = true; + for (int i = 0; i < NELEM(k_categories); i++) { + const TracingCategory &c = k_categories[i]; + for (int j = 0; j < MAX_SYS_FILES; j++) { + const char* path = c.sysfiles[j].path; + if (path != NULL && fileIsWritable(path)) { + ok &= setKernelOptionEnable(path, false); + } + } + } + return ok; +} + // Enable tracing in the kernel. -static bool startTrace(bool isRoot) +static bool startTrace() { bool ok = true; - // Set up the tracing options that don't require root. + // Set up the tracing options. ok &= setTraceOverwriteEnable(g_traceOverwrite); - ok &= setSchedSwitchTracingEnable(g_traceSchedSwitch); - ok &= setFrequencyTracingEnable(g_traceFrequency); - ok &= setCpuIdleTracingEnable(g_traceCpuIdle); - ok &= setGovernorLoadTracingEnable(g_traceGovernorLoad); ok &= setTraceBufferSizeKB(g_traceBufferSizeKB); ok &= setGlobalClockEnable(true); - // Set up the tracing options that do require root. The options that - // require root should have errored out earlier if we're not running as - // root. - if (isRoot) { - ok &= setBusUtilizationTracingEnable(g_traceBusUtilization); - ok &= setSyncTracingEnabled(g_traceSync); - ok &= setWorkqueueTracingEnabled(g_traceWorkqueue); - ok &= setDiskTracingEnabled(g_traceDisk); + // Set up the tags property. + uint64_t tags = 0; + for (int i = 0; i < NELEM(k_categories); i++) { + if (g_categoryEnables[i]) { + const TracingCategory &c = k_categories[i]; + tags |= c.tags; + } + } + ok &= setTagsProperty(tags); + + // Disable all the sysfs enables. This is done as a separate loop from + // the enables to allow the same enable to exist in multiple categories. + ok &= disableKernelTraceEvents(); + + // Enable all the sysfs enables that are in an enabled category. + for (int i = 0; i < NELEM(k_categories); i++) { + if (g_categoryEnables[i]) { + const TracingCategory &c = k_categories[i]; + for (int j = 0; j < MAX_SYS_FILES; j++) { + const char* path = c.sysfiles[j].path; + bool required = c.sysfiles[j].required == REQ; + if (path != NULL) { + if (fileIsWritable(path)) { + ok &= setKernelOptionEnable(path, true); + } else if (required) { + fprintf(stderr, "error writing file %s\n", path); + ok = false; + } + } + } + } } // Enable tracing. ok &= setTracingEnabled(true); - if (!ok) { - fprintf(stderr, "error: unable to start trace\n"); - } - return ok; } // Disable tracing in the kernel. -static void stopTrace(bool isRoot) +static void stopTrace() { // Disable tracing. setTracingEnabled(false); + // Disable all tracing that we're able to. + disableKernelTraceEvents(); + + // Disable all the trace tags. + setTagsProperty(0); + // Set the options back to their defaults. setTraceOverwriteEnable(true); - setSchedSwitchTracingEnable(false); - setFrequencyTracingEnable(false); - setGovernorLoadTracingEnable(false); setGlobalClockEnable(false); - if (isRoot) { - setBusUtilizationTracingEnable(false); - setSyncTracingEnabled(false); - setWorkqueueTracingEnabled(false); - setDiskTracingEnabled(false); - } - // Note that we can't reset the trace buffer size here because that would // clear the trace before we've read it. } @@ -418,36 +494,15 @@ static void dumpTrace() close(traceFD); } -// Print the command usage help to stderr. -static void showHelp(const char *cmd) +static void handleSignal(int signo) { - fprintf(stderr, "usage: %s [options]\n", cmd); - fprintf(stderr, "options include:\n" - " -b N use a trace buffer size of N KB\n" - " -c trace into a circular buffer\n" - " -d trace disk I/O\n" - " -f trace clock frequency changes\n" - " -l trace CPU frequency governor load\n" - " -s trace the kernel scheduler switches\n" - " -t N trace for N seconds [defualt 5]\n" - " -u trace bus utilization\n" - " -w trace the kernel workqueue\n" - " -y trace sync timelines and waits\n" - " -z compress the trace dump\n" - " --async_start start circular trace and return immediatly\n" - " --async_dump dump the current contents of circular trace buffer\n" - " --async_stop stop tracing and dump the current contents of circular\n" - " trace buffer\n" - ); -} - -static void handleSignal(int signo) { if (!g_nohup) { g_traceAborted = true; } } -static void registerSigHandler() { +static void registerSigHandler() +{ struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; @@ -458,9 +513,60 @@ static void registerSigHandler() { sigaction(SIGTERM, &sa, NULL); } +static bool setCategoryEnable(const char* name, bool enable) +{ + for (int i = 0; i < NELEM(k_categories); i++) { + const TracingCategory& c = k_categories[i]; + if (strcmp(name, c.name) == 0) { + if (isCategorySupported(c)) { + g_categoryEnables[i] = enable; + return true; + } else { + if (isCategorySupportedForRoot(c)) { + fprintf(stderr, "error: category \"%s\" requires root " + "privileges.\n", name); + } else { + fprintf(stderr, "error: category \"%s\" is not supported " + "on this device.\n", name); + } + return false; + } + } + } + fprintf(stderr, "error: unknown tracing category \"%s\"\n", name); + return false; +} + +static void listSupportedCategories() +{ + for (int i = 0; i < NELEM(k_categories); i++) { + const TracingCategory& c = k_categories[i]; + if (isCategorySupported(c)) { + printf(" %10s - %s\n", c.name, c.longname); + } + } +} + +// Print the command usage help to stderr. +static void showHelp(const char *cmd) +{ + fprintf(stderr, "usage: %s [options] [categories...]\n", cmd); + fprintf(stderr, "options include:\n" + " -b N use a trace buffer size of N KB\n" + " -c trace into a circular buffer\n" + " -n ignore signals\n" + " -s N sleep for N seconds before tracing [default 0]\n" + " -t N trace for N seconds [defualt 5]\n" + " -z compress the trace dump\n" + " --async_start start circular trace and return immediatly\n" + " --async_dump dump the current contents of circular trace buffer\n" + " --async_stop stop tracing and dump the current contents of circular\n" + " trace buffer\n" + ); +} + int main(int argc, char **argv) { - bool isRoot = (getuid() == 0); bool async = false; bool traceStart = true; bool traceStop = true; @@ -478,13 +584,20 @@ int main(int argc, char **argv) {"async_start", no_argument, 0, 0 }, {"async_stop", no_argument, 0, 0 }, {"async_dump", no_argument, 0, 0 }, - {0, 0, 0, 0 } + {"list_categories", no_argument, 0, 0 }, + { 0, 0, 0, 0 } }; - ret = getopt_long(argc, argv, "b:cidflst:uwyznS:", + ret = getopt_long(argc, argv, "b:cns:t:z", long_options, &option_index); if (ret < 0) { + for (int i = optind; i < argc; i++) { + if (!setCategoryEnable(argv[i], true)) { + fprintf(stderr, "error enabling tracing category \"%s\"\n", argv[i]); + exit(1); + } + } break; } @@ -497,35 +610,11 @@ int main(int argc, char **argv) g_traceOverwrite = true; break; - case 'i': - g_traceCpuIdle = true; - break; - - case 'l': - g_traceGovernorLoad = true; - break; - - case 'd': - if (!isRoot) { - fprintf(stderr, "error: tracing disk activity requires root privileges\n"); - exit(1); - } - g_traceDisk = true; - break; - - case 'f': - g_traceFrequency = true; - break; - case 'n': g_nohup = true; break; case 's': - g_traceSchedSwitch = true; - break; - - case 'S': g_initialSleepSecs = atoi(optarg); break; @@ -533,30 +622,6 @@ int main(int argc, char **argv) g_traceDurationSeconds = atoi(optarg); break; - case 'u': - if (!isRoot) { - fprintf(stderr, "error: tracing bus utilization requires root privileges\n"); - exit(1); - } - g_traceBusUtilization = true; - break; - - case 'w': - if (!isRoot) { - fprintf(stderr, "error: tracing kernel work queues requires root privileges\n"); - exit(1); - } - g_traceWorkqueue = true; - break; - - case 'y': - if (!isRoot) { - fprintf(stderr, "error: tracing sync requires root privileges\n"); - exit(1); - } - g_traceSync = true; - break; - case 'z': g_compress = true; break; @@ -574,6 +639,9 @@ int main(int argc, char **argv) async = true; traceStart = false; traceStop = false; + } else if (!strcmp(long_options[option_index].name, "list_categories")) { + listSupportedCategories(); + exit(0); } break; @@ -591,7 +659,7 @@ int main(int argc, char **argv) sleep(g_initialSleepSecs); } - bool ok = startTrace(isRoot); + bool ok = startTrace(); if (ok && traceStart) { printf("capturing trace..."); @@ -619,7 +687,7 @@ int main(int argc, char **argv) // Stop the trace and restore the default settings. if (traceStop) - stopTrace(isRoot); + stopTrace(); if (ok && traceDump) { if (!g_traceAborted) {