blob: 81c9babb06d81eba6fe66e90b4fc8e17b70b4f6d [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 <fcntl.h>
#include <stdarg.h>
#include <time.h>
#include <log/logger.h>
#include <private/android_filesystem_config.h>
#include <utils/String8.h>
#include "LogStatistics.h"
PidStatistics::PidStatistics(pid_t pid, char *name)
: pid(pid)
, mSizesTotal(0)
, mElementsTotal(0)
, mSizes(0)
, mElements(0)
, name(name)
, mGone(false)
{ }
#ifdef DO_NOT_ERROR_IF_PIDSTATISTICS_USES_A_COPY_CONSTRUCTOR
PidStatistics::PidStatistics(const PidStatistics &copy)
: pid(copy->pid)
, name(copy->name ? strdup(copy->name) : NULL)
, mSizesTotal(copy->mSizesTotal)
, mElementsTotal(copy->mElementsTotal)
, mSizes(copy->mSizes)
, mElements(copy->mElements)
, mGone(copy->mGone)
{ }
#endif
PidStatistics::~PidStatistics() {
free(name);
}
bool PidStatistics::pidGone() {
if (mGone) {
return true;
}
if (pid == gone) {
return true;
}
if (kill(pid, 0) && (errno != EPERM)) {
mGone = true;
return true;
}
return false;
}
void PidStatistics::setName(char *new_name) {
free(name);
name = new_name;
}
void PidStatistics::add(unsigned short size) {
mSizesTotal += size;
++mElementsTotal;
mSizes += size;
++mElements;
}
bool PidStatistics::subtract(unsigned short size) {
mSizes -= size;
--mElements;
return (mElements == 0) && pidGone();
}
void PidStatistics::addTotal(size_t size, size_t element) {
if (pid == gone) {
mSizesTotal += size;
mElementsTotal += element;
}
}
// must call free to release return value
char *PidStatistics::pidToName(pid_t pid) {
char *retval = NULL;
if (pid != gone) {
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;
}
UidStatistics::UidStatistics(uid_t uid)
: uid(uid)
, mSizes(0)
, mElements(0) {
Pids.clear();
}
UidStatistics::~UidStatistics() {
PidStatisticsCollection::iterator it;
for (it = begin(); it != end();) {
delete (*it);
it = Pids.erase(it);
}
}
void UidStatistics::add(unsigned short size, pid_t pid) {
mSizes += size;
++mElements;
PidStatistics *p;
PidStatisticsCollection::iterator last;
PidStatisticsCollection::iterator it;
for (last = it = begin(); it != end(); last = it, ++it) {
p = *it;
if (pid == p->getPid()) {
p->add(size);
return;
}
}
// insert if the gone entry.
bool insert = (last != it) && (p->getPid() == p->gone);
p = new PidStatistics(pid, pidToName(pid));
if (insert) {
Pids.insert(last, p);
} else {
Pids.push_back(p);
}
p->add(size);
}
void UidStatistics::subtract(unsigned short size, pid_t pid) {
mSizes -= size;
--mElements;
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
if (pid == p->getPid()) {
if (p->subtract(size)) {
size_t szsTotal = p->sizesTotal();
size_t elsTotal = p->elementsTotal();
delete p;
Pids.erase(it);
it = end();
--it;
if (it == end()) {
p = new PidStatistics(p->gone);
Pids.push_back(p);
} else {
p = *it;
if (p->getPid() != p->gone) {
p = new PidStatistics(p->gone);
Pids.push_back(p);
}
}
p->addTotal(szsTotal, elsTotal);
}
return;
}
}
}
void UidStatistics::sort() {
for (bool pass = true; pass;) {
pass = false;
PidStatisticsCollection::iterator it = begin();
if (it != end()) {
PidStatisticsCollection::iterator lt = it;
PidStatistics *l = (*lt);
while (++it != end()) {
PidStatistics *n = (*it);
if ((n->getPid() != n->gone) && (n->sizes() > l->sizes())) {
pass = true;
Pids.erase(it);
Pids.insert(lt, n);
it = lt;
n = l;
}
lt = it;
l = n;
}
}
}
}
size_t UidStatistics::sizes(pid_t pid) {
if (pid == pid_all) {
return sizes();
}
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
if (pid == p->getPid()) {
return p->sizes();
}
}
return 0;
}
size_t UidStatistics::elements(pid_t pid) {
if (pid == pid_all) {
return elements();
}
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
if (pid == p->getPid()) {
return p->elements();
}
}
return 0;
}
size_t UidStatistics::sizesTotal(pid_t pid) {
size_t sizes = 0;
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
if ((pid == pid_all) || (pid == p->getPid())) {
sizes += p->sizesTotal();
}
}
return sizes;
}
size_t UidStatistics::elementsTotal(pid_t pid) {
size_t elements = 0;
PidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
PidStatistics *p = *it;
if ((pid == pid_all) || (pid == p->getPid())) {
elements += p->elementsTotal();
}
}
return elements;
}
LidStatistics::LidStatistics() {
Uids.clear();
}
LidStatistics::~LidStatistics() {
UidStatisticsCollection::iterator it;
for (it = begin(); it != end();) {
delete (*it);
it = Uids.erase(it);
}
}
void LidStatistics::add(unsigned short size, uid_t uid, pid_t pid) {
UidStatistics *u;
UidStatisticsCollection::iterator it;
UidStatisticsCollection::iterator last;
if (uid == (uid_t) -1) { // init
uid = (uid_t) AID_ROOT;
}
for (last = it = begin(); it != end(); last = it, ++it) {
u = *it;
if (uid == u->getUid()) {
u->add(size, pid);
if ((last != it) && ((*last)->sizesTotal() < u->sizesTotal())) {
Uids.erase(it);
Uids.insert(last, u);
}
return;
}
}
u = new UidStatistics(uid);
if ((last != it) && ((*last)->sizesTotal() < (size_t) size)) {
Uids.insert(last, u);
} else {
Uids.push_back(u);
}
u->add(size, pid);
}
void LidStatistics::subtract(unsigned short size, uid_t uid, pid_t pid) {
UidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
UidStatistics *u = *it;
if (uid == u->getUid()) {
u->subtract(size, pid);
return;
}
}
}
void LidStatistics::sort() {
for (bool pass = true; pass;) {
pass = false;
UidStatisticsCollection::iterator it = begin();
if (it != end()) {
UidStatisticsCollection::iterator lt = it;
UidStatistics *l = (*lt);
while (++it != end()) {
UidStatistics *n = (*it);
if (n->sizes() > l->sizes()) {
pass = true;
Uids.erase(it);
Uids.insert(lt, n);
it = lt;
n = l;
}
lt = it;
l = n;
}
}
}
}
size_t LidStatistics::sizes(uid_t uid, pid_t pid) {
size_t sizes = 0;
UidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
UidStatistics *u = *it;
if ((uid == uid_all) || (uid == u->getUid())) {
sizes += u->sizes(pid);
}
}
return sizes;
}
size_t LidStatistics::elements(uid_t uid, pid_t pid) {
size_t elements = 0;
UidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
UidStatistics *u = *it;
if ((uid == uid_all) || (uid == u->getUid())) {
elements += u->elements(pid);
}
}
return elements;
}
size_t LidStatistics::sizesTotal(uid_t uid, pid_t pid) {
size_t sizes = 0;
UidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
UidStatistics *u = *it;
if ((uid == uid_all) || (uid == u->getUid())) {
sizes += u->sizesTotal(pid);
}
}
return sizes;
}
size_t LidStatistics::elementsTotal(uid_t uid, pid_t pid) {
size_t elements = 0;
UidStatisticsCollection::iterator it;
for (it = begin(); it != end(); ++it) {
UidStatistics *u = *it;
if ((uid == uid_all) || (uid == u->getUid())) {
elements += u->elementsTotal(pid);
}
}
return elements;
}
LogStatistics::LogStatistics()
: start(CLOCK_MONOTONIC) {
log_id_for_each(i) {
mSizes[i] = 0;
mElements[i] = 0;
}
dgram_qlen_statistics = false;
for(unsigned short bucket = 0; dgram_qlen(bucket); ++bucket) {
mMinimum[bucket].tv_sec = mMinimum[bucket].tv_sec_max;
mMinimum[bucket].tv_nsec = mMinimum[bucket].tv_nsec_max;
}
}
// Each bucket below represents a dgram_qlen of log messages. By
// finding the minimum period of time from start to finish
// of each dgram_qlen, we can get a performance expectation for
// the user space logger. The net result is that the period
// of time divided by the dgram_qlen will give us the average time
// between log messages; at the point where the average time
// is greater than the throughput capability of the logger
// we will not longer require the benefits of the FIFO formed
// by max_dgram_qlen. We will also expect to see a very visible
// knee in the average time between log messages at this point,
// so we do not necessarily have to compare the rate against the
// measured performance (BM_log_maximum_retry) of the logger.
//
// for example (reformatted):
//
// Minimum time between log events per dgram_qlen:
// 1 2 3 5 10 20 30 50 100 200 300 400 500 600
// 5u2 12u 13u 15u 16u 27u 30u 36u 407u 3m1 3m3 3m9 3m9 5m5
//
// demonstrates a clear knee rising at 100, so this means that for this
// case max_dgram_qlen = 100 would be more than sufficient to handle the
// worst that the system could stuff into the logger. The
// BM_log_maximum_retry performance (derated by the log collection) on the
// same system was 33.2us so we would almost be fine with max_dgram_qlen = 50.
// BM_log_maxumum_retry with statistics off is roughly 20us, so
// max_dgram_qlen = 20 would work. We will be more than willing to have
// a large engineering margin so the rule of thumb that lead us to 100 is
// fine.
//
// bucket dgram_qlen are tuned for /proc/sys/net/unix/max_dgram_qlen = 300
const unsigned short LogStatistics::mBuckets[] = {
1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 400, 500, 600
};
unsigned short LogStatistics::dgram_qlen(unsigned short bucket) {
if (bucket >= sizeof(mBuckets) / sizeof(mBuckets[0])) {
return 0;
}
return mBuckets[bucket];
}
unsigned long long LogStatistics::minimum(unsigned short bucket) {
if (mMinimum[bucket].tv_sec == mMinimum[bucket].tv_sec_max) {
return 0;
}
return mMinimum[bucket].nsec();
}
void LogStatistics::recordDiff(log_time diff, unsigned short bucket) {
if ((diff.tv_sec || diff.tv_nsec) && (mMinimum[bucket] > diff)) {
mMinimum[bucket] = diff;
}
}
void LogStatistics::add(unsigned short size,
log_id_t log_id, uid_t uid, pid_t pid) {
mSizes[log_id] += size;
++mElements[log_id];
id(log_id).add(size, uid, pid);
}
void LogStatistics::subtract(unsigned short size,
log_id_t log_id, uid_t uid, pid_t pid) {
mSizes[log_id] -= size;
--mElements[log_id];
id(log_id).subtract(size, uid, pid);
}
size_t LogStatistics::sizes(log_id_t log_id, uid_t uid, pid_t pid) {
if (log_id != log_id_all) {
return id(log_id).sizes(uid, pid);
}
size_t sizes = 0;
log_id_for_each(i) {
sizes += id(i).sizes(uid, pid);
}
return sizes;
}
size_t LogStatistics::elements(log_id_t log_id, uid_t uid, pid_t pid) {
if (log_id != log_id_all) {
return id(log_id).elements(uid, pid);
}
size_t elements = 0;
log_id_for_each(i) {
elements += id(i).elements(uid, pid);
}
return elements;
}
size_t LogStatistics::sizesTotal(log_id_t log_id, uid_t uid, pid_t pid) {
if (log_id != log_id_all) {
return id(log_id).sizesTotal(uid, pid);
}
size_t sizes = 0;
log_id_for_each(i) {
sizes += id(i).sizesTotal(uid, pid);
}
return sizes;
}
size_t LogStatistics::elementsTotal(log_id_t log_id, uid_t uid, pid_t pid) {
if (log_id != log_id_all) {
return id(log_id).elementsTotal(uid, pid);
}
size_t elements = 0;
log_id_for_each(i) {
elements += id(i).elementsTotal(uid, pid);
}
return elements;
}
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 = 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 (dgram_qlen_statistics) {
const unsigned short spaces_time = 6;
const unsigned long long max_seconds = 100000;
spaces = 0;
string.append("\n\nMinimum time between log events per dgram_qlen:\n");
for(unsigned short i = 0; dgram_qlen(i); ++i) {
oldLength = string.length();
if (spaces < 0) {
spaces = 0;
}
string.appendFormat("%*s%u", spaces, "", dgram_qlen(i));
spaces += spaces_time + oldLength - string.length();
}
string.append("\n");
spaces = 0;
unsigned short n;
for(unsigned short i = 0; (n = dgram_qlen(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());
}
uid_t LogStatistics::pidToUid(pid_t pid) {
log_id_for_each(i) {
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) {
if ((*ip)->getPid() == pid) {
return u.getUid();
}
}
}
}
return getuid(); // associate this with the logger
}