void LogBuffer::init() { static const char global_tuneable[] = "persist.logd.size"; // Settings App static const char global_default[] = "ro.logd.size"; // BoardConfig.mk unsigned long default_size = property_get_size(global_tuneable); if (!default_size) { default_size = property_get_size(global_default); } log_id_for_each(i) { char key[PROP_NAME_MAX]; snprintf(key, sizeof(key), "%s.%s", global_tuneable, android_log_id_to_name(i)); unsigned long property_size = property_get_size(key); if (!property_size) { snprintf(key, sizeof(key), "%s.%s", global_default, android_log_id_to_name(i)); property_size = property_get_size(key); } if (!property_size) { property_size = default_size; } if (!property_size) { property_size = LOG_BUFFER_SIZE; } if (setSize(i, property_size)) { setSize(i, LOG_BUFFER_MIN_SIZE); } } }
LIBLOG_ABI_PRIVATE unsigned long __android_logger_get_buffer_size(log_id_t logId) { static const char global_tunable[] = "persist.logd.size"; /* Settings App */ static const char global_default[] = "ro.logd.size"; /* BoardConfig.mk */ static struct cache2_property_size global = { PTHREAD_MUTEX_INITIALIZER, 0, global_tunable, { { NULL, -1 }, {} }, global_default, { { NULL, -1 }, {} }, evaluate_property_get_size }; char key_persist[PROP_NAME_MAX]; char key_ro[PROP_NAME_MAX]; struct cache2_property_size local = { PTHREAD_MUTEX_INITIALIZER, 0, key_persist, { { NULL, -1 }, {} }, key_ro, { { NULL, -1 }, {} }, evaluate_property_get_size }; unsigned long property_size, default_size; default_size = do_cache2_property_size(&global); if (!default_size) { default_size = __android_logger_property_get_bool("ro.config.low_ram", BOOL_DEFAULT_FALSE) ? LOG_BUFFER_MIN_SIZE /* 64K */ : LOG_BUFFER_SIZE; /* 256K */ } snprintf(key_persist, sizeof(key_persist), "%s.%s", global_tunable, android_log_id_to_name(logId)); snprintf(key_ro, sizeof(key_ro), "%s.%s", global_default, android_log_id_to_name(logId)); property_size = do_cache2_property_size(&local); if (!property_size) { property_size = default_size; } if (!property_size) { property_size = LOG_BUFFER_SIZE; } return property_size; }
LogSection::LogSection(int id, log_id_t logID) : WorkerThreadSection(id), mLogID(logID) { name += "logcat "; name += android_log_id_to_name(logID); switch (logID) { case LOG_ID_EVENTS: case LOG_ID_STATS: case LOG_ID_SECURITY: mBinary = true; break; default: mBinary = false; } }
LogBuffer::LogBuffer(LastLogTimes *times) : mTimes(*times) { pthread_mutex_init(&mLogElementsLock, NULL); dgram_qlen_statistics = false; static const char global_tuneable[] = "persist.logd.size"; // Settings App static const char global_default[] = "ro.logd.size"; // BoardConfig.mk unsigned long default_size = property_get_size(global_tuneable); if (!default_size) { default_size = property_get_size(global_default); } log_id_for_each(i) { char key[PROP_NAME_MAX]; snprintf(key, sizeof(key), "%s.%s", global_tuneable, android_log_id_to_name(i)); unsigned long property_size = property_get_size(key); if (!property_size) { snprintf(key, sizeof(key), "%s.%s", global_default, android_log_id_to_name(i)); property_size = property_get_size(key); } if (!property_size) { property_size = default_size; } if (!property_size) { property_size = LOG_BUFFER_SIZE; } if (setSize(i, property_size)) { setSize(i, LOG_BUFFER_MIN_SIZE); } } }
TEST(liblog, android_logger_get_) { #ifdef __ANDROID__ // This test assumes the log buffers are filled with noise from // normal operations. It will fail if done immediately after a // logcat -c. struct logger_list* logger_list = android_logger_list_alloc(ANDROID_LOG_WRONLY, 0, 0); for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) { log_id_t id = static_cast<log_id_t>(i); const char* name = android_log_id_to_name(id); if (id != android_name_to_log_id(name)) { continue; } fprintf(stderr, "log buffer %s\r", name); struct logger* logger; EXPECT_TRUE(NULL != (logger = android_logger_open(logger_list, id))); EXPECT_EQ(id, android_logger_get_id(logger)); ssize_t get_log_size = android_logger_get_log_size(logger); /* security buffer is allowed to be denied */ if (strcmp("security", name)) { EXPECT_LT(0, get_log_size); // crash buffer is allowed to be empty, that is actually healthy! // kernel buffer is allowed to be empty on "user" builds // stats buffer is allowed to be empty TEMPORARILY. // TODO: remove stats buffer from here once we start to use it in // framework (b/68266385). EXPECT_LE( // boolean 1 or 0 depending on expected content or empty !!((strcmp("crash", name) != 0) && ((strcmp("kernel", name) != 0) || __android_logger_property_get_bool( "ro.logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE)) && (strcmp("stats", name) != 0)), android_logger_get_log_readable_size(logger)); } else { EXPECT_NE(0, get_log_size); if (get_log_size < 0) { EXPECT_GT(0, android_logger_get_log_readable_size(logger)); } else { EXPECT_LE(0, android_logger_get_log_readable_size(logger)); } } EXPECT_LT(0, android_logger_get_log_version(logger)); } android_logger_list_close(logger_list); #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif }
TEST(liblog, android_logger_get_) { struct logger_list * logger_list = android_logger_list_alloc(O_WRONLY, 0, 0); for(int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) { log_id_t id = static_cast<log_id_t>(i); const char *name = android_log_id_to_name(id); if (id != android_name_to_log_id(name)) { continue; } struct logger * logger; EXPECT_TRUE(NULL != (logger = android_logger_open(logger_list, id))); EXPECT_EQ(id, android_logger_get_id(logger)); EXPECT_LT(0, android_logger_get_log_size(logger)); EXPECT_LT(0, android_logger_get_log_readable_size(logger)); EXPECT_LT(0, android_logger_get_log_version(logger)); } android_logger_list_close(logger_list); }
/* log_init_lock assumed */ static int __write_to_log_initialize() { int i, ret = 0; #if FAKE_LOG_DEVICE for (i = 0; i < LOG_ID_MAX; i++) { char buf[sizeof("/dev/log_system")]; snprintf(buf, sizeof(buf), "/dev/log_%s", android_log_id_to_name(i)); log_fds[i] = fakeLogOpen(buf, O_WRONLY); } #else if (pstore_fd < 0) { pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY)); } if (logd_fd < 0) { i = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)); if (i < 0) { ret = -errno; } else if (TEMP_FAILURE_RETRY(fcntl(i, F_SETFL, O_NONBLOCK)) < 0) { ret = -errno; close(i); } else { struct sockaddr_un un; memset(&un, 0, sizeof(struct sockaddr_un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, "/dev/socket/logdw"); if (TEMP_FAILURE_RETRY(connect(i, (struct sockaddr *)&un, sizeof(struct sockaddr_un))) < 0) { ret = -errno; close(i); } else { logd_fd = i; } } } #endif return ret; }
TEST(liblog, android_logger_get_) { struct logger_list * logger_list = android_logger_list_alloc(ANDROID_LOG_WRONLY, 0, 0); for(int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) { log_id_t id = static_cast<log_id_t>(i); const char *name = android_log_id_to_name(id); if (id != android_name_to_log_id(name)) { continue; } fprintf(stderr, "log buffer %s\r", name); struct logger * logger; EXPECT_TRUE(NULL != (logger = android_logger_open(logger_list, id))); EXPECT_EQ(id, android_logger_get_id(logger)); /* crash buffer is allowed to be empty, that is actually healthy! */ if (android_logger_get_log_size(logger) || strcmp("crash", name)) { EXPECT_LT(0, android_logger_get_log_size(logger)); } EXPECT_LT(0, android_logger_get_log_readable_size(logger)); EXPECT_LT(0, android_logger_get_log_version(logger)); } android_logger_list_close(logger_list); }
void LogStatistics::format(char **buf, uid_t uid, unsigned int logMask) { static const unsigned short spaces_total = 19; if (*buf) { free(*buf); *buf = NULL; } // Report on total logging, current and for all time android::String8 output("size/num"); size_t oldLength; short spaces = 1; log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } oldLength = output.length(); if (spaces < 0) { spaces = 0; } output.appendFormat("%*s%s", spaces, "", android_log_id_to_name(id)); spaces += spaces_total + oldLength - output.length(); } spaces = 4; output.appendFormat("\nTotal"); log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } oldLength = output.length(); if (spaces < 0) { spaces = 0; } output.appendFormat("%*s%zu/%zu", spaces, "", sizesTotal(id), elementsTotal(id)); spaces += spaces_total + oldLength - output.length(); } spaces = 6; output.appendFormat("\nNow"); log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } size_t els = elements(id); if (els) { oldLength = output.length(); if (spaces < 0) { spaces = 0; } output.appendFormat("%*s%zu/%zu", spaces, "", sizes(id), els); spaces -= output.length() - oldLength; } spaces += spaces_total; } // Report on Chattiest // Chattiest by application (UID) static const size_t maximum_sorted_entries = 32; log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } bool headerPrinted = false; std::unique_ptr<const UidEntry *[]> sorted = sort(maximum_sorted_entries, id); ssize_t index = -1; while ((index = uidTable_t::next(index, sorted, maximum_sorted_entries)) >= 0) { const UidEntry *entry = sorted[index]; uid_t u = entry->getKey(); if ((uid != AID_ROOT) && (u != uid)) { continue; } if (!headerPrinted) { if (uid == AID_ROOT) { output.appendFormat( "\n\nChattiest UIDs in %s:\n", android_log_id_to_name(id)); } else { output.appendFormat( "\n\nLogging for your UID in %s:\n", android_log_id_to_name(id)); } android::String8 name("UID"); android::String8 size("Size"); android::String8 pruned("Pruned"); if (!worstUidEnabledForLogid(id)) { pruned.setTo(""); } format_line(output, name, size, pruned); headerPrinted = true; } android::String8 name(""); name.appendFormat("%u", u); char *n = uidToName(u); if (n) { name.appendFormat("%*s%s", (int)std::max(6 - name.length(), (size_t)1), "", n); free(n); } android::String8 size(""); size.appendFormat("%zu", entry->getSizes()); android::String8 pruned(""); size_t dropped = entry->getDropped(); if (dropped) { pruned.appendFormat("%zu", dropped); } format_line(output, name, size, pruned); } } if (enable) { bool headerPrinted = false; std::unique_ptr<const PidEntry *[]> sorted = pidTable.sort(maximum_sorted_entries); ssize_t index = -1; while ((index = pidTable.next(index, sorted, maximum_sorted_entries)) >= 0) { const PidEntry *entry = sorted[index]; uid_t u = entry->getUid(); if ((uid != AID_ROOT) && (u != uid)) { continue; } if (!headerPrinted) { if (uid == AID_ROOT) { output.appendFormat("\n\nChattiest PIDs:\n"); } else { output.appendFormat("\n\nLogging for this PID:\n"); } android::String8 name(" PID/UID"); android::String8 size("Size"); android::String8 pruned("Pruned"); format_line(output, name, size, pruned); headerPrinted = true; } android::String8 name(""); name.appendFormat("%5u/%u", entry->getKey(), u); const char *n = entry->getName(); if (n) { name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", n); } else { char *un = uidToName(u); if (un) { name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", un); free(un); } } android::String8 size(""); size.appendFormat("%zu", entry->getSizes()); android::String8 pruned(""); size_t dropped = entry->getDropped(); if (dropped) { pruned.appendFormat("%zu", dropped); } format_line(output, name, size, pruned); } } *buf = strdup(output.string()); }
void LogStatistics::format(char **buf, uid_t uid, unsigned int logMask) { static const unsigned short spaces_total = 19; if (*buf) { free(*buf); *buf = NULL; } // Report on total logging, current and for all time android::String8 output("size/num"); size_t oldLength; short spaces = 1; log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } oldLength = output.length(); if (spaces < 0) { spaces = 0; } output.appendFormat("%*s%s", spaces, "", android_log_id_to_name(id)); spaces += spaces_total + oldLength - output.length(); } spaces = 4; output.appendFormat("\nTotal"); log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } oldLength = output.length(); if (spaces < 0) { spaces = 0; } output.appendFormat("%*s%zu/%zu", spaces, "", sizesTotal(id), elementsTotal(id)); spaces += spaces_total + oldLength - output.length(); } spaces = 6; output.appendFormat("\nNow"); log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } size_t els = elements(id); if (els) { oldLength = output.length(); if (spaces < 0) { spaces = 0; } output.appendFormat("%*s%zu/%zu", spaces, "", sizes(id), els); spaces -= output.length() - oldLength; } spaces += spaces_total; } // Report on Chattiest // Chattiest by application (UID) static const size_t maximum_sorted_entries = 32; log_id_for_each(id) { if (!(logMask & (1 << id))) { continue; } bool headerPrinted = false; std::unique_ptr<const UidEntry *[]> sorted = sort(maximum_sorted_entries, id); ssize_t index = -1; while ((index = uidTable_t::next(index, sorted, maximum_sorted_entries)) >= 0) { const UidEntry *entry = sorted[index]; uid_t u = entry->getKey(); if ((uid != AID_ROOT) && (u != uid)) { continue; } if (!headerPrinted) { output.appendFormat("\n\n"); android::String8 name(""); if (uid == AID_ROOT) { name.appendFormat( "Chattiest UIDs in %s log buffer:", android_log_id_to_name(id)); } else { name.appendFormat( "Logging for your UID in %s log buffer:", android_log_id_to_name(id)); } android::String8 size("Size"); android::String8 pruned("Pruned"); if (!worstUidEnabledForLogid(id)) { pruned.setTo(""); } format_line(output, name, size, pruned); name.setTo("UID PACKAGE"); size.setTo("BYTES"); pruned.setTo("LINES"); if (!worstUidEnabledForLogid(id)) { pruned.setTo(""); } format_line(output, name, size, pruned); headerPrinted = true; } android::String8 name(""); name.appendFormat("%u", u); char *n = uidToName(u); if (n) { name.appendFormat("%*s%s", (int)std::max(6 - name.length(), (size_t)1), "", n); free(n); } android::String8 size(""); size.appendFormat("%zu", entry->getSizes()); android::String8 pruned(""); size_t dropped = entry->getDropped(); if (dropped) { pruned.appendFormat("%zu", dropped); } format_line(output, name, size, pruned); } } if (enable) { // Pid table bool headerPrinted = false; std::unique_ptr<const PidEntry *[]> sorted = pidTable.sort(maximum_sorted_entries); ssize_t index = -1; while ((index = pidTable.next(index, sorted, maximum_sorted_entries)) >= 0) { const PidEntry *entry = sorted[index]; uid_t u = entry->getUid(); if ((uid != AID_ROOT) && (u != uid)) { continue; } if (!headerPrinted) { output.appendFormat("\n\n"); android::String8 name(""); if (uid == AID_ROOT) { name.appendFormat("Chattiest PIDs:"); } else { name.appendFormat("Logging for this PID:"); } android::String8 size("Size"); android::String8 pruned("Pruned"); format_line(output, name, size, pruned); name.setTo(" PID/UID COMMAND LINE"); size.setTo("BYTES"); pruned.setTo("LINES"); format_line(output, name, size, pruned); headerPrinted = true; } android::String8 name(""); name.appendFormat("%5u/%u", entry->getKey(), u); const char *n = entry->getName(); if (n) { name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", n); } else { char *un = uidToName(u); if (un) { name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", un); free(un); } } android::String8 size(""); size.appendFormat("%zu", entry->getSizes()); android::String8 pruned(""); size_t dropped = entry->getDropped(); if (dropped) { pruned.appendFormat("%zu", dropped); } format_line(output, name, size, pruned); } } if (enable) { // Tid table bool headerPrinted = false; // sort() returns list of references, unique_ptr makes sure self-delete std::unique_ptr<const TidEntry *[]> sorted = tidTable.sort(maximum_sorted_entries); ssize_t index = -1; while ((index = tidTable.next(index, sorted, maximum_sorted_entries)) >= 0) { const TidEntry *entry = sorted[index]; uid_t u = entry->getUid(); if ((uid != AID_ROOT) && (u != uid)) { continue; } if (!headerPrinted) { // Only print header if we have table to print output.appendFormat("\n\n"); android::String8 name("Chattiest TIDs:"); android::String8 size("Size"); android::String8 pruned("Pruned"); format_line(output, name, size, pruned); name.setTo(" TID/UID COMM"); size.setTo("BYTES"); pruned.setTo("LINES"); format_line(output, name, size, pruned); headerPrinted = true; } android::String8 name(""); name.appendFormat("%5u/%u", entry->getKey(), u); const char *n = entry->getName(); if (n) { name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", n); } else { // if we do not have a PID name, lets punt to try UID name? char *un = uidToName(u); if (un) { name.appendFormat("%*s%s", (int)std::max(12 - name.length(), (size_t)1), "", un); free(un); } // We tried, better to not have a name at all, we still // have TID/UID by number to report in any case. } android::String8 size(""); size.appendFormat("%zu", entry->getSizes()); android::String8 pruned(""); size_t dropped = entry->getDropped(); if (dropped) { pruned.appendFormat("%zu", dropped); } format_line(output, name, size, pruned); } } if (enable && (logMask & (1 << LOG_ID_EVENTS))) { // Tag table bool headerPrinted = false; std::unique_ptr<const TagEntry *[]> sorted = tagTable.sort(maximum_sorted_entries); ssize_t index = -1; while ((index = tagTable.next(index, sorted, maximum_sorted_entries)) >= 0) { const TagEntry *entry = sorted[index]; uid_t u = entry->getUid(); if ((uid != AID_ROOT) && (u != uid)) { continue; } android::String8 pruned(""); if (!headerPrinted) { output.appendFormat("\n\n"); android::String8 name("Chattiest events log buffer TAGs:"); android::String8 size("Size"); format_line(output, name, size, pruned); name.setTo(" TAG/UID TAGNAME"); size.setTo("BYTES"); format_line(output, name, size, pruned); headerPrinted = true; } android::String8 name(""); if (u == (uid_t)-1) { name.appendFormat("%7u", entry->getKey()); } else { name.appendFormat("%7u/%u", entry->getKey(), u); } const char *n = entry->getName(); if (n) { name.appendFormat("%*s%s", (int)std::max(14 - name.length(), (size_t)1), "", n); } android::String8 size(""); size.appendFormat("%zu", entry->getSizes()); format_line(output, name, size, pruned); } } *buf = strdup(output.string()); }
int main(int argc, char **argv) { int err; int hasSetLogFormat = 0; int clearLog = 0; int getLogSize = 0; unsigned long setLogSize = 0; int getPruneList = 0; char *setPruneList = NULL; int printStatistics = 0; int mode = O_RDONLY; const char *forceFilters = NULL; log_device_t* devices = NULL; log_device_t* dev; bool needBinary = false; struct logger_list *logger_list; unsigned int tail_lines = 0; log_time tail_time(log_time::EPOCH); signal(SIGPIPE, exit); g_logformat = android_log_format_new(); if (argc == 2 && 0 == strcmp(argv[1], "--help")) { android::show_help(argv[0]); exit(0); } for (;;) { int ret; ret = getopt(argc, argv, "cdt:T:gG:sQf:r:n:v:b:BSpP:"); if (ret < 0) { break; } switch(ret) { case 's': // default to all silent android_log_addFilterRule(g_logformat, "*:s"); break; case 'c': clearLog = 1; mode = O_WRONLY; break; case 'd': mode = O_RDONLY | O_NDELAY; break; case 't': mode = O_RDONLY | O_NDELAY; /* FALLTHRU */ case 'T': if (strspn(optarg, "0123456789") != strlen(optarg)) { char *cp = tail_time.strptime(optarg, log_time::default_format); if (!cp) { fprintf(stderr, "ERROR: -%c \"%s\" not in \"%s\" time format\n", ret, optarg, log_time::default_format); exit(1); } if (*cp) { char c = *cp; *cp = '\0'; fprintf(stderr, "WARNING: -%c \"%s\"\"%c%s\" time truncated\n", ret, optarg, c, cp + 1); *cp = c; } } else { tail_lines = atoi(optarg); if (!tail_lines) { fprintf(stderr, "WARNING: -%c %s invalid, setting to 1\n", ret, optarg); tail_lines = 1; } } break; case 'g': getLogSize = 1; break; case 'G': { // would use atol if not for the multiplier char *cp = optarg; setLogSize = 0; while (('0' <= *cp) && (*cp <= '9')) { setLogSize *= 10; setLogSize += *cp - '0'; ++cp; } switch(*cp) { case 'g': case 'G': setLogSize *= 1024; /* FALLTHRU */ case 'm': case 'M': setLogSize *= 1024; /* FALLTHRU */ case 'k': case 'K': setLogSize *= 1024; /* FALLTHRU */ case '\0': break; default: setLogSize = 0; } if (!setLogSize) { fprintf(stderr, "ERROR: -G <num><multiplier>\n"); exit(1); } } break; case 'p': getPruneList = 1; break; case 'P': setPruneList = optarg; break; case 'b': { if (strcmp(optarg, "all") == 0) { while (devices) { dev = devices; devices = dev->next; delete dev; } devices = dev = NULL; android::g_devCount = 0; needBinary = false; for(int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) { const char *name = android_log_id_to_name((log_id_t)i); log_id_t log_id = android_name_to_log_id(name); if (log_id != (log_id_t)i) { continue; } bool binary = strcmp(name, "events") == 0; log_device_t* d = new log_device_t(name, binary, *name); if (dev) { dev->next = d; dev = d; } else { devices = dev = d; } android::g_devCount++; if (binary) { needBinary = true; } } break; } bool binary = strcmp(optarg, "events") == 0; if (binary) { needBinary = true; } if (devices) { dev = devices; while (dev->next) { dev = dev->next; } dev->next = new log_device_t(optarg, binary, optarg[0]); } else { devices = new log_device_t(optarg, binary, optarg[0]); } android::g_devCount++; } break; case 'B': android::g_printBinary = 1; break; case 'f': // redirect output to a file android::g_outputFileName = optarg; break; case 'r': if (optarg == NULL) { android::g_logRotateSizeKBytes = DEFAULT_LOG_ROTATE_SIZE_KBYTES; } else { if (!isdigit(optarg[0])) { fprintf(stderr,"Invalid parameter to -r\n"); android::show_help(argv[0]); exit(-1); } android::g_logRotateSizeKBytes = atoi(optarg); } break; case 'n': if (!isdigit(optarg[0])) { fprintf(stderr,"Invalid parameter to -r\n"); android::show_help(argv[0]); exit(-1); } android::g_maxRotatedLogs = atoi(optarg); break; case 'v': err = setLogFormat (optarg); if (err < 0) { fprintf(stderr,"Invalid parameter to -v\n"); android::show_help(argv[0]); exit(-1); } if (strcmp("color", optarg)) { // exception for modifiers hasSetLogFormat = 1; } break; case 'Q': /* this is a *hidden* option used to start a version of logcat */ /* in an emulated device only. it basically looks for androidboot.logcat= */ /* on the kernel command line. If something is found, it extracts a log filter */ /* and uses it to run the program. If nothing is found, the program should */ /* quit immediately */ #define KERNEL_OPTION "androidboot.logcat=" #define CONSOLE_OPTION "androidboot.console=" { int fd; char* logcat; char* console; int force_exit = 1; static char cmdline[1024]; fd = open("/proc/cmdline", O_RDONLY); if (fd >= 0) { int n = read(fd, cmdline, sizeof(cmdline)-1 ); if (n < 0) n = 0; cmdline[n] = 0; close(fd); } else { cmdline[0] = 0; } logcat = strstr( cmdline, KERNEL_OPTION ); console = strstr( cmdline, CONSOLE_OPTION ); if (logcat != NULL) { char* p = logcat + sizeof(KERNEL_OPTION)-1;; char* q = strpbrk( p, " \t\n\r" );; if (q != NULL) *q = 0; forceFilters = p; force_exit = 0; } /* if nothing found or invalid filters, exit quietly */ if (force_exit) exit(0); /* redirect our output to the emulator console */ if (console) { char* p = console + sizeof(CONSOLE_OPTION)-1; char* q = strpbrk( p, " \t\n\r" ); char devname[64]; int len; if (q != NULL) { len = q - p; } else len = strlen(p); len = snprintf( devname, sizeof(devname), "/dev/%.*s", len, p ); fprintf(stderr, "logcat using %s (%d)\n", devname, len); if (len < (int)sizeof(devname)) { fd = open( devname, O_WRONLY ); if (fd >= 0) { dup2(fd, 1); dup2(fd, 2); close(fd); } } } } break; case 'S': printStatistics = 1; break; default: fprintf(stderr,"Unrecognized Option\n"); android::show_help(argv[0]); exit(-1); break; } } if (!devices) { dev = devices = new log_device_t("main", false, 'm'); android::g_devCount = 1; if (android_name_to_log_id("system") == LOG_ID_SYSTEM) { dev = dev->next = new log_device_t("system", false, 's'); android::g_devCount++; } if (android_name_to_log_id("crash") == LOG_ID_CRASH) { dev = dev->next = new log_device_t("crash", false, 'c'); android::g_devCount++; } } if (android::g_logRotateSizeKBytes != 0 && android::g_outputFileName == NULL ) { fprintf(stderr,"-r requires -f as well\n"); android::show_help(argv[0]); exit(-1); } android::setupOutput(); if (hasSetLogFormat == 0) { const char* logFormat = getenv("ANDROID_PRINTF_LOG"); if (logFormat != NULL) { err = setLogFormat(logFormat); if (err < 0) { fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n", logFormat); } } else { setLogFormat("threadtime"); } } if (forceFilters) { err = android_log_addFilterString(g_logformat, forceFilters); if (err < 0) { fprintf (stderr, "Invalid filter expression in -logcat option\n"); exit(0); } } else if (argc == optind) { // Add from environment variable char *env_tags_orig = getenv("ANDROID_LOG_TAGS"); if (env_tags_orig != NULL) { err = android_log_addFilterString(g_logformat, env_tags_orig); if (err < 0) { fprintf(stderr, "Invalid filter expression in" " ANDROID_LOG_TAGS\n"); android::show_help(argv[0]); exit(-1); } } } else { // Add from commandline for (int i = optind ; i < argc ; i++) { err = android_log_addFilterString(g_logformat, argv[i]); if (err < 0) { fprintf (stderr, "Invalid filter expression '%s'\n", argv[i]); android::show_help(argv[0]); exit(-1); } } } dev = devices; if (tail_time != log_time::EPOCH) { logger_list = android_logger_list_alloc_time(mode, tail_time, 0); } else { logger_list = android_logger_list_alloc(mode, tail_lines, 0); } while (dev) { dev->logger_list = logger_list; dev->logger = android_logger_open(logger_list, android_name_to_log_id(dev->device)); if (!dev->logger) { fprintf(stderr, "Unable to open log device '%s'\n", dev->device); exit(EXIT_FAILURE); } if (clearLog) { int ret; ret = android_logger_clear(dev->logger); if (ret) { perror("failed to clear the log"); exit(EXIT_FAILURE); } } if (setLogSize && android_logger_set_log_size(dev->logger, setLogSize)) { perror("failed to set the log size"); exit(EXIT_FAILURE); } if (getLogSize) { long size, readable; size = android_logger_get_log_size(dev->logger); if (size < 0) { perror("failed to get the log size"); exit(EXIT_FAILURE); } readable = android_logger_get_log_readable_size(dev->logger); if (readable < 0) { perror("failed to get the readable log size"); exit(EXIT_FAILURE); } printf("%s: ring buffer is %ld%sb (%ld%sb consumed), " "max entry is %db, max payload is %db\n", dev->device, value_of_size(size), multiplier_of_size(size), value_of_size(readable), multiplier_of_size(readable), (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD); } dev = dev->next; } if (setPruneList) { size_t len = strlen(setPruneList) + 32; // margin to allow rc char *buf = (char *) malloc(len); strcpy(buf, setPruneList); int ret = android_logger_set_prune_list(logger_list, buf, len); free(buf); if (ret) { perror("failed to set the prune list"); exit(EXIT_FAILURE); } } if (printStatistics || getPruneList) { size_t len = 8192; char *buf; for(int retry = 32; (retry >= 0) && ((buf = new char [len])); delete [] buf, --retry) { if (getPruneList) { android_logger_get_prune_list(logger_list, buf, len); } else { android_logger_get_statistics(logger_list, buf, len); } buf[len-1] = '\0'; size_t ret = atol(buf) + 1; if (ret < 4) { delete [] buf; buf = NULL; break; } bool check = ret <= len; len = ret; if (check) { break; } } if (!buf) { perror("failed to read data"); exit(EXIT_FAILURE); } // remove trailing FF char *cp = buf + len - 1; *cp = '\0'; bool truncated = *--cp != '\f'; if (!truncated) { *cp = '\0'; } // squash out the byte count cp = buf; if (!truncated) { while (isdigit(*cp)) { ++cp; } if (*cp == '\n') { ++cp; } } printf("%s", cp); delete [] buf; exit(0); } if (getLogSize) { exit(0); } if (setLogSize || setPruneList) { exit(0); } if (clearLog) { exit(0); } //LOG_EVENT_INT(10, 12345); //LOG_EVENT_LONG(11, 0x1122334455667788LL); //LOG_EVENT_STRING(0, "whassup, doc?"); if (needBinary) android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE); while (1) { struct log_msg log_msg; int ret = android_logger_list_read(logger_list, &log_msg); if (ret == 0) { fprintf(stderr, "read: Unexpected EOF!\n"); exit(EXIT_FAILURE); } if (ret < 0) { if (ret == -EAGAIN) { break; } if (ret == -EIO) { fprintf(stderr, "read: Unexpected EOF!\n"); exit(EXIT_FAILURE); } if (ret == -EINVAL) { fprintf(stderr, "read: unexpected length.\n"); exit(EXIT_FAILURE); } perror("logcat read failure"); exit(EXIT_FAILURE); } for(dev = devices; dev; dev = dev->next) { if (android_name_to_log_id(dev->device) == log_msg.id()) { break; } } if (!dev) { fprintf(stderr, "read: Unexpected log ID!\n"); exit(EXIT_FAILURE); } android::maybePrintStart(dev); if (android::g_printBinary) { android::printBinary(&log_msg); } else { android::processBuffer(dev, &log_msg); } } android_logger_list_free(logger_list); return 0; }
void LogStatistics::format(char **buf, uid_t uid, unsigned int logMask, log_time oldest) { static const unsigned short spaces_current = 13; static const unsigned short spaces_total = 19; if (*buf) { free(*buf); *buf = NULL; } android::String8 string(" span -> size/num"); size_t oldLength; short spaces = 2; log_id_for_each(i) { if (!(logMask & (1 << i))) { continue; } oldLength = string.length(); if (spaces < 0) { spaces = 0; } string.appendFormat("%*s%s", spaces, "", android_log_id_to_name(i)); spaces += spaces_total + oldLength - string.length(); LidStatistics &l = id(i); l.sort(); UidStatisticsCollection::iterator iu; for (iu = l.begin(); iu != l.end(); ++iu) { (*iu)->sort(); } } spaces = 1; log_time t(CLOCK_MONOTONIC); unsigned long long d; if (mStatistics) { d = t.nsec() - start.nsec(); string.appendFormat("\nTotal%4llu:%02llu:%02llu.%09llu", d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60, (d / NS_PER_SEC) % 60, d % NS_PER_SEC); log_id_for_each(i) { if (!(logMask & (1 << i))) { continue; } oldLength = string.length(); if (spaces < 0) { spaces = 0; } string.appendFormat("%*s%zu/%zu", spaces, "", sizesTotal(i), elementsTotal(i)); spaces += spaces_total + oldLength - string.length(); } spaces = 1; } d = t.nsec() - oldest.nsec(); string.appendFormat("\nNow%6llu:%02llu:%02llu.%09llu", d / NS_PER_SEC / 60 / 60, (d / NS_PER_SEC / 60) % 60, (d / NS_PER_SEC) % 60, d % NS_PER_SEC); log_id_for_each(i) { if (!(logMask & (1 << i))) { continue; } size_t els = elements(i); if (els) { oldLength = string.length(); if (spaces < 0) { spaces = 0; } string.appendFormat("%*s%zu/%zu", spaces, "", sizes(i), els); spaces -= string.length() - oldLength; } spaces += spaces_total; } // Construct list of worst spammers by Pid static const unsigned char num_spammers = 10; bool header = false; log_id_for_each(i) { if (!(logMask & (1 << i))) { continue; } PidStatisticsCollection pids; pids.clear(); LidStatistics &l = id(i); UidStatisticsCollection::iterator iu; for (iu = l.begin(); iu != l.end(); ++iu) { UidStatistics &u = *(*iu); PidStatisticsCollection::iterator ip; for (ip = u.begin(); ip != u.end(); ++ip) { PidStatistics *p = (*ip); if (p->getPid() == p->gone) { break; } size_t mySizes = p->sizes(); PidStatisticsCollection::iterator q; unsigned char num = 0; for (q = pids.begin(); q != pids.end(); ++q) { if (mySizes > (*q)->sizes()) { pids.insert(q, p); break; } // do we need to traverse deeper in the list? if (++num > num_spammers) { break; } } if (q == pids.end()) { pids.push_back(p); } } } size_t threshold = sizes(i); if (threshold < 65536) { threshold = 65536; } threshold /= 100; PidStatisticsCollection::iterator pt = pids.begin(); for(int line = 0; (pt != pids.end()) && (line < num_spammers); ++line, pt = pids.erase(pt)) { PidStatistics *p = *pt; size_t sizes = p->sizes(); if (sizes < threshold) { break; } char *name = p->getName(); pid_t pid = p->getPid(); if (!name || !*name) { name = pidToName(pid); if (name) { if (*name) { p->setName(name); } else { free(name); name = NULL; } } } if (!header) { string.appendFormat("\n\nChattiest clients:\n" "log id %-*s PID[?] name", spaces_total, "size/total"); header = true; } size_t sizesTotal = p->sizesTotal(); android::String8 sz(""); sz.appendFormat((sizes != sizesTotal) ? "%zu/%zu" : "%zu", sizes, sizesTotal); android::String8 pd(""); pd.appendFormat("%u%c", pid, p->pidGone() ? '?' : ' '); string.appendFormat("\n%-7s%-*s %-7s%s", line ? "" : android_log_id_to_name(i), spaces_total, sz.string(), pd.string(), name ? name : ""); } pids.clear(); } if (dgramQlenStatistics) { const unsigned short spaces_time = 6; const unsigned long long max_seconds = 100000; spaces = 0; string.append("\n\nMinimum time between log events per max_dgram_qlen:\n"); for(unsigned short i = 0; dgramQlen(i); ++i) { oldLength = string.length(); if (spaces < 0) { spaces = 0; } string.appendFormat("%*s%u", spaces, "", dgramQlen(i)); spaces += spaces_time + oldLength - string.length(); } string.append("\n"); spaces = 0; unsigned short n; for(unsigned short i = 0; (n = dgramQlen(i)); ++i) { unsigned long long duration = minimum(i); if (duration) { duration /= n; if (duration >= (NS_PER_SEC * max_seconds)) { duration = NS_PER_SEC * (max_seconds - 1); } oldLength = string.length(); if (spaces < 0) { spaces = 0; } string.appendFormat("%*s", spaces, ""); if (duration >= (NS_PER_SEC * 10)) { string.appendFormat("%llu", (duration + (NS_PER_SEC / 2)) / NS_PER_SEC); } else if (duration >= (NS_PER_SEC / (1000 / 10))) { string.appendFormat("%llum", (duration + (NS_PER_SEC / 2 / 1000)) / (NS_PER_SEC / 1000)); } else if (duration >= (NS_PER_SEC / (1000000 / 10))) { string.appendFormat("%lluu", (duration + (NS_PER_SEC / 2 / 1000000)) / (NS_PER_SEC / 1000000)); } else { string.appendFormat("%llun", duration); } spaces -= string.length() - oldLength; } spaces += spaces_time; } } log_id_for_each(i) { if (!(logMask & (1 << i))) { continue; } header = false; bool first = true; UidStatisticsCollection::iterator ut; for(ut = id(i).begin(); ut != id(i).end(); ++ut) { UidStatistics *up = *ut; if ((uid != AID_ROOT) && (uid != up->getUid())) { continue; } PidStatisticsCollection::iterator pt = up->begin(); if (pt == up->end()) { continue; } android::String8 intermediate; if (!header) { // header below tuned to match spaces_total and spaces_current spaces = 0; intermediate = string.format("%s: UID/PID Total size/num", android_log_id_to_name(i)); string.appendFormat("\n\n%-31sNow " "UID/PID[?] Total Now", intermediate.string()); intermediate.clear(); header = true; } bool oneline = ++pt == up->end(); --pt; if (!oneline) { first = true; } else if (!first && (spaces > 0)) { string.appendFormat("%*s", spaces, ""); } spaces = 0; uid_t u = up->getUid(); PidStatistics *pp = *pt; pid_t p = pp->getPid(); intermediate = string.format(oneline ? ((p == PidStatistics::gone) ? "%d/?" : "%d/%d%c") : "%d", u, p, pp->pidGone() ? '?' : '\0'); string.appendFormat(first ? "\n%-12s" : "%-12s", intermediate.string()); intermediate.clear(); size_t elsTotal = up->elementsTotal(); oldLength = string.length(); string.appendFormat("%zu/%zu", up->sizesTotal(), elsTotal); spaces += spaces_total + oldLength - string.length(); size_t els = up->elements(); if (els == elsTotal) { if (spaces < 0) { spaces = 0; } string.appendFormat("%*s=", spaces, ""); spaces = -1; } else if (els) { oldLength = string.length(); if (spaces < 0) { spaces = 0; } string.appendFormat("%*s%zu/%zu", spaces, "", up->sizes(), els); spaces -= string.length() - oldLength; } spaces += spaces_current; first = !first; if (oneline) { continue; } size_t gone_szs = 0; size_t gone_els = 0; for(; pt != up->end(); ++pt) { pp = *pt; p = pp->getPid(); // If a PID no longer has any current logs, and is not // active anymore, skip & report totals for gone. elsTotal = pp->elementsTotal(); size_t szsTotal = pp->sizesTotal(); if (p == pp->gone) { gone_szs += szsTotal; gone_els += elsTotal; continue; } els = pp->elements(); bool gone = pp->pidGone(); if (gone && (els == 0)) { // ToDo: garbage collection: move this statistical bucket // from its current UID/PID to UID/? (races and // wrap around are our achilles heel). Below is // merely lipservice to catch PIDs that were still // around when the stats were pruned to zero. gone_szs += szsTotal; gone_els += elsTotal; continue; } if (!first && (spaces > 0)) { string.appendFormat("%*s", spaces, ""); } spaces = 0; intermediate = string.format(gone ? "%d/%d?" : "%d/%d", u, p); string.appendFormat(first ? "\n%-12s" : "%-12s", intermediate.string()); intermediate.clear(); oldLength = string.length(); string.appendFormat("%zu/%zu", szsTotal, elsTotal); spaces += spaces_total + oldLength - string.length(); if (els == elsTotal) { if (spaces < 0) { spaces = 0; } string.appendFormat("%*s=", spaces, ""); spaces = -1; } else if (els) { oldLength = string.length(); if (spaces < 0) { spaces = 0; } string.appendFormat("%*s%zu/%zu", spaces, "", pp->sizes(), els); spaces -= string.length() - oldLength; } spaces += spaces_current; first = !first; } if (gone_els) { if (!first && (spaces > 0)) { string.appendFormat("%*s", spaces, ""); } intermediate = string.format("%d/?", u); string.appendFormat(first ? "\n%-12s" : "%-12s", intermediate.string()); intermediate.clear(); spaces = spaces_total + spaces_current; oldLength = string.length(); string.appendFormat("%zu/%zu", gone_szs, gone_els); spaces -= string.length() - oldLength; first = !first; } } } *buf = strdup(string.string()); }