blob: 61fd5596ab14e2b9f655cfcac3c96a43088fb543 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm> // std::max
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <base/stringprintf.h>
#include <log/logger.h>
#include <private/android_filesystem_config.h>
#include "LogStatistics.h"
LogStatistics::LogStatistics() : enable(false) {
log_id_for_each(id) {
mSizes[id] = 0;
mElements[id] = 0;
mSizesTotal[id] = 0;
mElementsTotal[id] = 0;
}
}
namespace android {
// caller must own and free character string
char *pidToName(pid_t pid) {
char *retval = NULL;
if (pid == 0) { // special case from auditd/klogd for kernel
retval = strdup("logd");
} else {
char buffer[512];
snprintf(buffer, sizeof(buffer), "/proc/%u/cmdline", pid);
int fd = open(buffer, O_RDONLY);
if (fd >= 0) {
ssize_t ret = read(fd, buffer, sizeof(buffer));
if (ret > 0) {
buffer[sizeof(buffer)-1] = '\0';
// frameworks intermediate state
if (strcmp(buffer, "<pre-initialized>")) {
retval = strdup(buffer);
}
}
close(fd);
}
}
return retval;
}
}
void LogStatistics::add(LogBufferElement *e) {
log_id_t log_id = e->getLogId();
unsigned short size = e->getMsgLen();
mSizes[log_id] += size;
++mElements[log_id];
mSizesTotal[log_id] += size;
++mElementsTotal[log_id];
if (log_id == LOG_ID_KERNEL) {
return;
}
uidTable[log_id].add(e->getUid(), e);
if (!enable) {
return;
}
pidTable.add(e->getPid(), e);
tidTable.add(e->getTid(), e);
uint32_t tag = e->getTag();
if (tag) {
tagTable.add(tag, e);
}
}
void LogStatistics::subtract(LogBufferElement *e) {
log_id_t log_id = e->getLogId();
unsigned short size = e->getMsgLen();
mSizes[log_id] -= size;
--mElements[log_id];
if (log_id == LOG_ID_KERNEL) {
return;
}
uidTable[log_id].subtract(e->getUid(), e);
if (!enable) {
return;
}
pidTable.subtract(e->getPid(), e);
tidTable.subtract(e->getTid(), e);
uint32_t tag = e->getTag();
if (tag) {
tagTable.subtract(tag, e);
}
}
// Atomically set an entry to drop
// entry->setDropped(1) must follow this call, caller should do this explicitly.
void LogStatistics::drop(LogBufferElement *e) {
log_id_t log_id = e->getLogId();
unsigned short size = e->getMsgLen();
mSizes[log_id] -= size;
uidTable[log_id].drop(e->getUid(), e);
if (!enable) {
return;
}
pidTable.drop(e->getPid(), e);
tidTable.drop(e->getTid(), e);
}
// caller must own and free character string
char *LogStatistics::uidToName(uid_t uid) {
// Local hard coded favourites
if (uid == AID_LOGD) {
return strdup("auditd");
}
// Android hard coded
const struct android_id_info *info = android_ids;
for (size_t i = 0; i < android_id_count; ++i) {
if (info->aid == uid) {
return strdup(info->name);
}
++info;
}
// Parse /data/system/packages.list
uid_t userId = uid % AID_USER;
char *name = android::uidToName(userId);
if (!name && (userId > (AID_SHARED_GID_START - AID_APP))) {
name = android::uidToName(userId - (AID_SHARED_GID_START - AID_APP));
}
if (name) {
return name;
}
// report uid -> pid(s) -> pidToName if unique
for(pidTable_t::iterator it = pidTable.begin(); it != pidTable.end(); ++it) {
const PidEntry &entry = it->second;
if (entry.getUid() == uid) {
const char *n = entry.getName();
if (n) {
if (!name) {
name = strdup(n);
} else if (strcmp(name, n)) {
free(name);
name = NULL;
break;
}
}
}
}
// No one
return name;
}
static std::string format_line(
const std::string &name,
const std::string &size,
const std::string &pruned) {
static const size_t pruned_len = 6;
static const size_t total_len = 70 + pruned_len;
ssize_t drop_len = std::max(pruned.length() + 1, pruned_len);
ssize_t size_len = std::max(size.length() + 1,
total_len - name.length() - drop_len - 1);
if (pruned.length()) {
return android::base::StringPrintf("%s%*s%*s\n", name.c_str(),
(int)size_len, size.c_str(),
(int)drop_len, pruned.c_str());
} else {
return android::base::StringPrintf("%s%*s\n", name.c_str(),
(int)size_len, size.c_str());
}
}
std::string LogStatistics::format(uid_t uid, unsigned int logMask) {
static const unsigned short spaces_total = 19;
// Report on total logging, current and for all time
std::string 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 += android::base::StringPrintf("%*s%s", spaces, "",
android_log_id_to_name(id));
spaces += spaces_total + oldLength - output.length();
}
spaces = 4;
output += android::base::StringPrintf("\nTotal");
log_id_for_each(id) {
if (!(logMask & (1 << id))) {
continue;
}
oldLength = output.length();
if (spaces < 0) {
spaces = 0;
}
output += android::base::StringPrintf("%*s%zu/%zu", spaces, "",
sizesTotal(id),
elementsTotal(id));
spaces += spaces_total + oldLength - output.length();
}
spaces = 6;
output += android::base::StringPrintf("\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 += android::base::StringPrintf("%*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 += android::base::StringPrintf("\n\n");
std::string name;
if (uid == AID_ROOT) {
name = android::base::StringPrintf(
"Chattiest UIDs in %s log buffer:",
android_log_id_to_name(id));
} else {
name = android::base::StringPrintf(
"Logging for your UID in %s log buffer:",
android_log_id_to_name(id));
}
std::string size = "Size";
std::string pruned = "Pruned";
if (!worstUidEnabledForLogid(id)) {
pruned = "";
}
output += format_line(name, size, pruned);
name = "UID PACKAGE";
size = "BYTES";
pruned = "LINES";
if (!worstUidEnabledForLogid(id)) {
pruned = "";
}
output += format_line(name, size, pruned);
headerPrinted = true;
}
std::string name = android::base::StringPrintf("%u", u);
char *n = uidToName(u);
if (n) {
name += android::base::StringPrintf(
"%*s%s", (int)std::max(6 - name.length(), (size_t)1),
"", n);
free(n);
}
std::string size = android::base::StringPrintf("%zu",
entry->getSizes());
std::string pruned = "";
size_t dropped = entry->getDropped();
if (dropped) {
pruned = android::base::StringPrintf("%zu", dropped);
}
output += format_line(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 += android::base::StringPrintf("\n\n");
std::string name;
if (uid == AID_ROOT) {
name = android::base::StringPrintf("Chattiest PIDs:");
} else {
name = android::base::StringPrintf("Logging for this PID:");
}
std::string size = "Size";
std::string pruned = "Pruned";
output += format_line(name, size, pruned);
name = " PID/UID COMMAND LINE";
size = "BYTES";
pruned = "LINES";
output += format_line(name, size, pruned);
headerPrinted = true;
}
std::string name = android::base::StringPrintf("%5u/%u",
entry->getKey(), u);
const char *n = entry->getName();
if (n) {
name += android::base::StringPrintf(
"%*s%s", (int)std::max(12 - name.length(), (size_t)1),
"", n);
} else {
char *un = uidToName(u);
if (un) {
name += android::base::StringPrintf(
"%*s%s", (int)std::max(12 - name.length(), (size_t)1),
"", un);
free(un);
}
}
std::string size = android::base::StringPrintf("%zu",
entry->getSizes());
std::string pruned = "";
size_t dropped = entry->getDropped();
if (dropped) {
pruned = android::base::StringPrintf("%zu", dropped);
}
output += format_line(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 += android::base::StringPrintf("\n\n");
std::string name = "Chattiest TIDs:";
std::string size = "Size";
std::string pruned = "Pruned";
output += format_line(name, size, pruned);
name = " TID/UID COMM";
size = "BYTES";
pruned = "LINES";
output += format_line(name, size, pruned);
headerPrinted = true;
}
std::string name = android::base::StringPrintf("%5u/%u",
entry->getKey(), u);
const char *n = entry->getName();
if (n) {
name += android::base::StringPrintf(
"%*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 += android::base::StringPrintf(
"%*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.
}
std::string size = android::base::StringPrintf("%zu",
entry->getSizes());
std::string pruned = "";
size_t dropped = entry->getDropped();
if (dropped) {
pruned = android::base::StringPrintf("%zu", dropped);
}
output += format_line(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;
}
std::string pruned = "";
if (!headerPrinted) {
output += android::base::StringPrintf("\n\n");
std::string name = "Chattiest events log buffer TAGs:";
std::string size = "Size";
output += format_line(name, size, pruned);
name = " TAG/UID TAGNAME";
size = "BYTES";
output += format_line(name, size, pruned);
headerPrinted = true;
}
std::string name;
if (u == (uid_t)-1) {
name = android::base::StringPrintf("%7u",
entry->getKey());
} else {
name = android::base::StringPrintf("%7u/%u",
entry->getKey(), u);
}
const char *n = entry->getName();
if (n) {
name += android::base::StringPrintf(
"%*s%s", (int)std::max(14 - name.length(), (size_t)1),
"", n);
}
std::string size = android::base::StringPrintf("%zu",
entry->getSizes());
output += format_line(name, size, pruned);
}
}
return output;
}
namespace android {
uid_t pidToUid(pid_t pid) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "/proc/%u/status", pid);
FILE *fp = fopen(buffer, "r");
if (fp) {
while (fgets(buffer, sizeof(buffer), fp)) {
int uid;
if (sscanf(buffer, "Uid: %d", &uid) == 1) {
fclose(fp);
return uid;
}
}
fclose(fp);
}
return AID_LOGD; // associate this with the logger
}
}
uid_t LogStatistics::pidToUid(pid_t pid) {
return pidTable.add(pid)->second.getUid();
}
// caller must free character string
char *LogStatistics::pidToName(pid_t pid) {
const char *name = pidTable.add(pid)->second.getName();
if (!name) {
return NULL;
}
return strdup(name);
}