| /* |
| * 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 "LogStatistics.h" |
| |
| #include <ctype.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <pwd.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <list> |
| |
| #include <private/android_logger.h> |
| |
| #include "LogBufferElement.h" |
| |
| static const uint64_t hourSec = 60 * 60; |
| static const uint64_t monthSec = 31 * 24 * hourSec; |
| |
| std::atomic<size_t> LogStatistics::SizesTotal; |
| |
| static std::string TagNameKey(const LogStatisticsElement& element) { |
| if (IsBinary(element.log_id)) { |
| uint32_t tag = element.tag; |
| if (tag) { |
| const char* cp = android::tagToName(tag); |
| if (cp) { |
| return std::string(cp); |
| } |
| } |
| return android::base::StringPrintf("[%" PRIu32 "]", tag); |
| } |
| const char* msg = element.msg; |
| if (!msg) { |
| return "chatty"; |
| } |
| ++msg; |
| uint16_t len = element.msg_len; |
| len = (len <= 1) ? 0 : strnlen(msg, len - 1); |
| if (!len) { |
| return "<NULL>"; |
| } |
| return std::string(msg, len); |
| } |
| |
| LogStatistics::LogStatistics(bool enable_statistics) : enable(enable_statistics) { |
| log_time now(CLOCK_REALTIME); |
| log_id_for_each(id) { |
| mSizes[id] = 0; |
| mElements[id] = 0; |
| mDroppedElements[id] = 0; |
| mSizesTotal[id] = 0; |
| mElementsTotal[id] = 0; |
| mOldest[id] = now; |
| mNewest[id] = now; |
| mNewestDropped[id] = now; |
| } |
| } |
| |
| namespace android { |
| |
| size_t sizesTotal() { |
| return LogStatistics::sizesTotal(); |
| } |
| |
| // caller must own and free character string |
| char* pidToName(pid_t pid) { |
| char* retval = nullptr; |
| 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 | O_CLOEXEC); |
| if (fd >= 0) { |
| ssize_t ret = read(fd, buffer, sizeof(buffer)); |
| if (ret > 0) { |
| buffer[sizeof(buffer) - 1] = '\0'; |
| // frameworks intermediate state |
| if (fastcmp<strcmp>(buffer, "<pre-initialized>")) { |
| retval = strdup(buffer); |
| } |
| } |
| close(fd); |
| } |
| } |
| return retval; |
| } |
| } |
| |
| void LogStatistics::AddTotal(log_id_t log_id, uint16_t size) { |
| auto lock = std::lock_guard{lock_}; |
| |
| mSizesTotal[log_id] += size; |
| SizesTotal += size; |
| ++mElementsTotal[log_id]; |
| } |
| |
| void LogStatistics::Add(const LogStatisticsElement& element) { |
| auto lock = std::lock_guard{lock_}; |
| log_id_t log_id = element.log_id; |
| uint16_t size = element.msg_len; |
| mSizes[log_id] += size; |
| ++mElements[log_id]; |
| |
| // When caller adding a chatty entry, they will have already |
| // called add() and subtract() for each entry as they are |
| // evaluated and trimmed, thus recording size and number of |
| // elements, but we must recognize the manufactured dropped |
| // entry as not contributing to the lifetime totals. |
| if (element.dropped_count) { |
| ++mDroppedElements[log_id]; |
| } else { |
| mSizesTotal[log_id] += size; |
| SizesTotal += size; |
| ++mElementsTotal[log_id]; |
| } |
| |
| log_time stamp(element.realtime); |
| if (mNewest[log_id] < stamp) { |
| // A major time update invalidates the statistics :-( |
| log_time diff = stamp - mNewest[log_id]; |
| mNewest[log_id] = stamp; |
| |
| if (diff.tv_sec > hourSec) { |
| // approximate Do-Your-Best fixup |
| diff += mOldest[log_id]; |
| if ((diff > stamp) && ((diff - stamp).tv_sec < hourSec)) { |
| diff = stamp; |
| } |
| if (diff <= stamp) { |
| mOldest[log_id] = diff; |
| if (mNewestDropped[log_id] < diff) { |
| mNewestDropped[log_id] = diff; |
| } |
| } |
| } |
| } |
| |
| if (log_id == LOG_ID_KERNEL) { |
| return; |
| } |
| |
| uidTable[log_id].Add(element.uid, element); |
| if (element.uid == AID_SYSTEM) { |
| pidSystemTable[log_id].Add(element.pid, element); |
| } |
| |
| if (!enable) { |
| return; |
| } |
| |
| pidTable.Add(element.pid, element); |
| tidTable.Add(element.tid, element); |
| |
| uint32_t tag = element.tag; |
| if (tag) { |
| if (log_id == LOG_ID_SECURITY) { |
| securityTagTable.Add(tag, element); |
| } else { |
| tagTable.Add(tag, element); |
| } |
| } |
| |
| if (!element.dropped_count) { |
| tagNameTable.Add(TagNameKey(element), element); |
| } |
| } |
| |
| void LogStatistics::Subtract(const LogStatisticsElement& element) { |
| auto lock = std::lock_guard{lock_}; |
| log_id_t log_id = element.log_id; |
| uint16_t size = element.msg_len; |
| mSizes[log_id] -= size; |
| --mElements[log_id]; |
| if (element.dropped_count) { |
| --mDroppedElements[log_id]; |
| } |
| |
| if (mOldest[log_id] < element.realtime) { |
| mOldest[log_id] = element.realtime; |
| } |
| |
| if (log_id == LOG_ID_KERNEL) { |
| return; |
| } |
| |
| uidTable[log_id].Subtract(element.uid, element); |
| if (element.uid == AID_SYSTEM) { |
| pidSystemTable[log_id].Subtract(element.pid, element); |
| } |
| |
| if (!enable) { |
| return; |
| } |
| |
| pidTable.Subtract(element.pid, element); |
| tidTable.Subtract(element.tid, element); |
| |
| uint32_t tag = element.tag; |
| if (tag) { |
| if (log_id == LOG_ID_SECURITY) { |
| securityTagTable.Subtract(tag, element); |
| } else { |
| tagTable.Subtract(tag, element); |
| } |
| } |
| |
| if (!element.dropped_count) { |
| tagNameTable.Subtract(TagNameKey(element), element); |
| } |
| } |
| |
| // Atomically set an entry to drop |
| // entry->setDropped(1) must follow this call, caller should do this explicitly. |
| void LogStatistics::Drop(const LogStatisticsElement& element) { |
| auto lock = std::lock_guard{lock_}; |
| log_id_t log_id = element.log_id; |
| uint16_t size = element.msg_len; |
| mSizes[log_id] -= size; |
| ++mDroppedElements[log_id]; |
| |
| if (mNewestDropped[log_id] < element.realtime) { |
| mNewestDropped[log_id] = element.realtime; |
| } |
| |
| uidTable[log_id].Drop(element.uid, element); |
| if (element.uid == AID_SYSTEM) { |
| pidSystemTable[log_id].Drop(element.pid, element); |
| } |
| |
| if (!enable) { |
| return; |
| } |
| |
| pidTable.Drop(element.pid, element); |
| tidTable.Drop(element.tid, element); |
| |
| uint32_t tag = element.tag; |
| if (tag) { |
| if (log_id == LOG_ID_SECURITY) { |
| securityTagTable.Drop(tag, element); |
| } else { |
| tagTable.Drop(tag, element); |
| } |
| } |
| |
| tagNameTable.Subtract(TagNameKey(element), element); |
| } |
| |
| const char* LogStatistics::UidToName(uid_t uid) const { |
| auto lock = std::lock_guard{lock_}; |
| return UidToNameLocked(uid); |
| } |
| |
| // caller must own and free character string |
| const char* LogStatistics::UidToNameLocked(uid_t uid) const { |
| // Local hard coded favourites |
| if (uid == AID_LOGD) { |
| return strdup("auditd"); |
| } |
| |
| // Android system |
| if (uid < AID_APP) { |
| // in bionic, thread safe as long as we copy the results |
| struct passwd* pwd = getpwuid(uid); |
| if (pwd) { |
| return strdup(pwd->pw_name); |
| } |
| } |
| |
| // Parse /data/system/packages.list |
| uid_t userId = uid % AID_USER_OFFSET; |
| const 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; |
| } |
| |
| // Android application |
| if (uid >= AID_APP) { |
| struct passwd* pwd = getpwuid(uid); |
| if (pwd) { |
| return strdup(pwd->pw_name); |
| } |
| } |
| |
| // report uid -> pid(s) -> pidToName if unique |
| for (pidTable_t::const_iterator it = pidTable.begin(); it != pidTable.end(); |
| ++it) { |
| const PidEntry& entry = it->second; |
| |
| if (entry.uid() == uid) { |
| const char* nameTmp = entry.name(); |
| |
| if (nameTmp) { |
| if (!name) { |
| name = strdup(nameTmp); |
| } else if (fastcmp<strcmp>(name, nameTmp)) { |
| free(const_cast<char*>(name)); |
| name = nullptr; |
| break; |
| } |
| } |
| } |
| } |
| |
| // No one |
| return name; |
| } |
| |
| template <typename TKey, typename TEntry> |
| void LogStatistics::WorstTwoWithThreshold(const LogHashtable<TKey, TEntry>& table, size_t threshold, |
| int* worst, size_t* worst_sizes, |
| size_t* second_worst_sizes) const { |
| std::array<const TKey*, 2> max_keys; |
| std::array<const TEntry*, 2> max_entries; |
| table.MaxEntries(AID_ROOT, 0, max_keys, max_entries); |
| if (max_entries[0] == nullptr || max_entries[1] == nullptr) { |
| return; |
| } |
| *worst_sizes = max_entries[0]->getSizes(); |
| // b/24782000: Allow time horizon to extend roughly tenfold, assume average entry length is |
| // 100 characters. |
| if (*worst_sizes > threshold && *worst_sizes > (10 * max_entries[0]->dropped_count())) { |
| *worst = *max_keys[0]; |
| *second_worst_sizes = max_entries[1]->getSizes(); |
| if (*second_worst_sizes < threshold) { |
| *second_worst_sizes = threshold; |
| } |
| } |
| } |
| |
| void LogStatistics::WorstTwoUids(log_id id, size_t threshold, int* worst, size_t* worst_sizes, |
| size_t* second_worst_sizes) const { |
| auto lock = std::lock_guard{lock_}; |
| WorstTwoWithThreshold(uidTable[id], threshold, worst, worst_sizes, second_worst_sizes); |
| } |
| |
| void LogStatistics::WorstTwoTags(size_t threshold, int* worst, size_t* worst_sizes, |
| size_t* second_worst_sizes) const { |
| auto lock = std::lock_guard{lock_}; |
| WorstTwoWithThreshold(tagTable, threshold, worst, worst_sizes, second_worst_sizes); |
| } |
| |
| void LogStatistics::WorstTwoSystemPids(log_id id, size_t worst_uid_sizes, int* worst, |
| size_t* second_worst_sizes) const { |
| auto lock = std::lock_guard{lock_}; |
| std::array<const pid_t*, 2> max_keys; |
| std::array<const PidEntry*, 2> max_entries; |
| pidSystemTable[id].MaxEntries(AID_SYSTEM, 0, max_keys, max_entries); |
| if (max_entries[0] == nullptr || max_entries[1] == nullptr) { |
| return; |
| } |
| |
| *worst = *max_keys[0]; |
| *second_worst_sizes = worst_uid_sizes - max_entries[0]->getSizes() + max_entries[1]->getSizes(); |
| } |
| |
| // Prune at most 10% of the log entries or maxPrune, whichever is less. |
| bool LogStatistics::ShouldPrune(log_id id, unsigned long max_size, |
| unsigned long* prune_rows) const { |
| static constexpr size_t kMinPrune = 4; |
| static constexpr size_t kMaxPrune = 256; |
| |
| auto lock = std::lock_guard{lock_}; |
| size_t sizes = mSizes[id]; |
| if (sizes <= max_size) { |
| return false; |
| } |
| size_t size_over = sizes - ((max_size * 9) / 10); |
| size_t elements = mElements[id] - mDroppedElements[id]; |
| size_t min_elements = elements / 100; |
| if (min_elements < kMinPrune) { |
| min_elements = kMinPrune; |
| } |
| *prune_rows = elements * size_over / sizes; |
| if (*prune_rows < min_elements) { |
| *prune_rows = min_elements; |
| } |
| if (*prune_rows > kMaxPrune) { |
| *prune_rows = kMaxPrune; |
| } |
| |
| return true; |
| } |
| |
| std::string UidEntry::formatHeader(const std::string& name, log_id_t id) const { |
| bool isprune = worstUidEnabledForLogid(id); |
| return formatLine(android::base::StringPrintf(name.c_str(), |
| android_log_id_to_name(id)), |
| std::string("Size"), |
| std::string(isprune ? "+/- Pruned" : "")) + |
| formatLine(std::string("UID PACKAGE"), std::string("BYTES"), |
| std::string(isprune ? "NUM" : "")); |
| } |
| |
| // Helper to truncate name, if too long, and add name dressings |
| void LogStatistics::FormatTmp(const char* nameTmp, uid_t uid, std::string& name, std::string& size, |
| size_t nameLen) const { |
| const char* allocNameTmp = nullptr; |
| if (!nameTmp) nameTmp = allocNameTmp = UidToNameLocked(uid); |
| if (nameTmp) { |
| size_t lenSpace = std::max(nameLen - name.length(), (size_t)1); |
| size_t len = EntryBase::TOTAL_LEN - EntryBase::PRUNED_LEN - size.length() - name.length() - |
| lenSpace - 2; |
| size_t lenNameTmp = strlen(nameTmp); |
| while ((len < lenNameTmp) && (lenSpace > 1)) { |
| ++len; |
| --lenSpace; |
| } |
| name += android::base::StringPrintf("%*s", (int)lenSpace, ""); |
| if (len < lenNameTmp) { |
| name += "..."; |
| nameTmp += lenNameTmp - std::max(len - 3, (size_t)1); |
| } |
| name += nameTmp; |
| free(const_cast<char*>(allocNameTmp)); |
| } |
| } |
| |
| std::string UidEntry::format(const LogStatistics& stat, log_id_t id, uid_t uid) const |
| REQUIRES(stat.lock_) { |
| std::string name = android::base::StringPrintf("%u", uid); |
| std::string size = android::base::StringPrintf("%zu", getSizes()); |
| |
| stat.FormatTmp(nullptr, uid, name, size, 6); |
| |
| std::string pruned = ""; |
| if (worstUidEnabledForLogid(id)) { |
| size_t totalDropped = 0; |
| for (LogStatistics::uidTable_t::const_iterator it = |
| stat.uidTable[id].begin(); |
| it != stat.uidTable[id].end(); ++it) { |
| totalDropped += it->second.dropped_count(); |
| } |
| size_t sizes = stat.mSizes[id]; |
| size_t totalSize = stat.mSizesTotal[id]; |
| size_t totalElements = stat.mElementsTotal[id]; |
| float totalVirtualSize = |
| (float)sizes + (float)totalDropped * totalSize / totalElements; |
| size_t entrySize = getSizes(); |
| float virtualEntrySize = entrySize; |
| int realPermille = virtualEntrySize * 1000.0 / sizes; |
| size_t dropped = dropped_count(); |
| if (dropped) { |
| pruned = android::base::StringPrintf("%zu", dropped); |
| virtualEntrySize += (float)dropped * totalSize / totalElements; |
| } |
| int virtualPermille = virtualEntrySize * 1000.0 / totalVirtualSize; |
| int permille = |
| (realPermille - virtualPermille) * 1000L / (virtualPermille ?: 1); |
| if ((permille < -1) || (1 < permille)) { |
| std::string change; |
| const char* units = "%"; |
| const char* prefix = (permille > 0) ? "+" : ""; |
| |
| if (permille > 999) { |
| permille = (permille + 1000) / 100; // Now tenths fold |
| units = "X"; |
| prefix = ""; |
| } |
| if ((-99 < permille) && (permille < 99)) { |
| change = android::base::StringPrintf( |
| "%s%d.%u%s", prefix, permille / 10, |
| ((permille < 0) ? (-permille % 10) : (permille % 10)), |
| units); |
| } else { |
| change = android::base::StringPrintf( |
| "%s%d%s", prefix, (permille + 5) / 10, units); |
| } |
| ssize_t spaces = EntryBase::PRUNED_LEN - 2 - pruned.length() - change.length(); |
| if ((spaces <= 0) && pruned.length()) { |
| spaces = 1; |
| } |
| if (spaces > 0) { |
| change += android::base::StringPrintf("%*s", (int)spaces, ""); |
| } |
| pruned = change + pruned; |
| } |
| } |
| |
| std::string output = formatLine(name, size, pruned); |
| |
| if (uid != AID_SYSTEM) { |
| return output; |
| } |
| |
| static const size_t maximum_sorted_entries = 32; |
| std::array<const pid_t*, maximum_sorted_entries> sorted_pids; |
| std::array<const PidEntry*, maximum_sorted_entries> sorted_entries; |
| stat.pidSystemTable[id].MaxEntries(uid, 0, sorted_pids, sorted_entries); |
| |
| std::string byPid; |
| size_t index; |
| bool hasDropped = false; |
| for (index = 0; index < maximum_sorted_entries; ++index) { |
| const PidEntry* entry = sorted_entries[index]; |
| if (!entry) { |
| break; |
| } |
| if (entry->getSizes() <= (getSizes() / 100)) { |
| break; |
| } |
| if (entry->dropped_count()) { |
| hasDropped = true; |
| } |
| byPid += entry->format(stat, id, *sorted_pids[index]); |
| } |
| if (index > 1) { // print this only if interesting |
| std::string ditto("\" "); |
| output += formatLine(std::string(" PID/UID COMMAND LINE"), ditto, |
| hasDropped ? ditto : std::string("")); |
| output += byPid; |
| } |
| |
| return output; |
| } |
| |
| std::string PidEntry::formatHeader(const std::string& name, |
| log_id_t /* id */) const { |
| return formatLine(name, std::string("Size"), std::string("Pruned")) + |
| formatLine(std::string(" PID/UID COMMAND LINE"), |
| std::string("BYTES"), std::string("NUM")); |
| } |
| |
| std::string PidEntry::format(const LogStatistics& stat, log_id_t, pid_t pid) const |
| REQUIRES(stat.lock_) { |
| std::string name = android::base::StringPrintf("%5u/%u", pid, uid_); |
| std::string size = android::base::StringPrintf("%zu", getSizes()); |
| |
| stat.FormatTmp(name_, uid_, name, size, 12); |
| |
| std::string pruned = ""; |
| size_t dropped = dropped_count(); |
| if (dropped) { |
| pruned = android::base::StringPrintf("%zu", dropped); |
| } |
| |
| return formatLine(name, size, pruned); |
| } |
| |
| std::string TidEntry::formatHeader(const std::string& name, |
| log_id_t /* id */) const { |
| return formatLine(name, std::string("Size"), std::string("Pruned")) + |
| formatLine(std::string(" TID/UID COMM"), std::string("BYTES"), |
| std::string("NUM")); |
| } |
| |
| std::string TidEntry::format(const LogStatistics& stat, log_id_t, pid_t tid) const |
| REQUIRES(stat.lock_) { |
| std::string name = android::base::StringPrintf("%5u/%u", tid, uid_); |
| std::string size = android::base::StringPrintf("%zu", getSizes()); |
| |
| stat.FormatTmp(name_, uid_, name, size, 12); |
| |
| std::string pruned = ""; |
| size_t dropped = dropped_count(); |
| if (dropped) { |
| pruned = android::base::StringPrintf("%zu", dropped); |
| } |
| |
| return formatLine(name, size, pruned); |
| } |
| |
| std::string TagEntry::formatHeader(const std::string& name, log_id_t id) const { |
| bool isprune = worstUidEnabledForLogid(id); |
| return formatLine(name, std::string("Size"), |
| std::string(isprune ? "Prune" : "")) + |
| formatLine(std::string(" TAG/UID TAGNAME"), |
| std::string("BYTES"), std::string(isprune ? "NUM" : "")); |
| } |
| |
| std::string TagEntry::format(const LogStatistics&, log_id_t, uint32_t) const { |
| std::string name; |
| if (uid_ == (uid_t)-1) { |
| name = android::base::StringPrintf("%7u", key()); |
| } else { |
| name = android::base::StringPrintf("%7u/%u", key(), uid_); |
| } |
| const char* nameTmp = this->name(); |
| if (nameTmp) { |
| name += android::base::StringPrintf( |
| "%*s%s", (int)std::max(14 - name.length(), (size_t)1), "", nameTmp); |
| } |
| |
| std::string size = android::base::StringPrintf("%zu", getSizes()); |
| |
| std::string pruned = ""; |
| size_t dropped = dropped_count(); |
| if (dropped) { |
| pruned = android::base::StringPrintf("%zu", dropped); |
| } |
| |
| return formatLine(name, size, pruned); |
| } |
| |
| std::string TagNameEntry::formatHeader(const std::string& name, |
| log_id_t /* id */) const { |
| return formatLine(name, std::string("Size"), std::string("")) + |
| formatLine(std::string(" TID/PID/UID LOG_TAG NAME"), |
| std::string("BYTES"), std::string("")); |
| } |
| |
| std::string TagNameEntry::format(const LogStatistics&, log_id_t, |
| const std::string& key_name) const { |
| std::string name; |
| std::string pidstr; |
| if (pid_ != (pid_t)-1) { |
| pidstr = android::base::StringPrintf("%u", pid_); |
| if (tid_ != (pid_t)-1 && tid_ != pid_) pidstr = "/" + pidstr; |
| } |
| int len = 9 - pidstr.length(); |
| if (len < 0) len = 0; |
| if (tid_ == (pid_t)-1 || tid_ == pid_) { |
| name = android::base::StringPrintf("%*s", len, ""); |
| } else { |
| name = android::base::StringPrintf("%*u", len, tid_); |
| } |
| name += pidstr; |
| if (uid_ != (uid_t)-1) { |
| name += android::base::StringPrintf("/%u", uid_); |
| } |
| |
| std::string size = android::base::StringPrintf("%zu", getSizes()); |
| |
| const char* nameTmp = key_name.data(); |
| if (nameTmp) { |
| size_t lenSpace = std::max(16 - name.length(), (size_t)1); |
| size_t len = EntryBase::TOTAL_LEN - EntryBase::PRUNED_LEN - size.length() - name.length() - |
| lenSpace - 2; |
| size_t lenNameTmp = strlen(nameTmp); |
| while ((len < lenNameTmp) && (lenSpace > 1)) { |
| ++len; |
| --lenSpace; |
| } |
| name += android::base::StringPrintf("%*s", (int)lenSpace, ""); |
| if (len < lenNameTmp) { |
| name += "..."; |
| nameTmp += lenNameTmp - std::max(len - 3, (size_t)1); |
| } |
| name += nameTmp; |
| } |
| |
| std::string pruned = ""; |
| |
| return formatLine(name, size, pruned); |
| } |
| |
| static std::string formatMsec(uint64_t val) { |
| static const unsigned subsecDigits = 3; |
| static const uint64_t sec = MS_PER_SEC; |
| |
| static const uint64_t minute = 60 * sec; |
| static const uint64_t hour = 60 * minute; |
| static const uint64_t day = 24 * hour; |
| |
| std::string output; |
| if (val < sec) return output; |
| |
| if (val >= day) { |
| output = android::base::StringPrintf("%" PRIu64 "d ", val / day); |
| val = (val % day) + day; |
| } |
| if (val >= minute) { |
| if (val >= hour) { |
| output += android::base::StringPrintf("%" PRIu64 ":", |
| (val / hour) % (day / hour)); |
| } |
| output += android::base::StringPrintf( |
| (val >= hour) ? "%02" PRIu64 ":" : "%" PRIu64 ":", |
| (val / minute) % (hour / minute)); |
| } |
| output += |
| android::base::StringPrintf((val >= minute) ? "%02" PRIu64 : "%" PRIu64, |
| (val / sec) % (minute / sec)); |
| val %= sec; |
| unsigned digits = subsecDigits; |
| while (digits && ((val % 10) == 0)) { |
| val /= 10; |
| --digits; |
| } |
| if (digits) { |
| output += android::base::StringPrintf(".%0*" PRIu64, digits, val); |
| } |
| return output; |
| } |
| |
| template <typename TKey, typename TEntry> |
| std::string LogStatistics::FormatTable(const LogHashtable<TKey, TEntry>& table, uid_t uid, |
| pid_t pid, const std::string& name, log_id_t id) const |
| REQUIRES(lock_) { |
| static const size_t maximum_sorted_entries = 32; |
| std::string output; |
| std::array<const TKey*, maximum_sorted_entries> sorted_keys; |
| std::array<const TEntry*, maximum_sorted_entries> sorted_entries; |
| table.MaxEntries(uid, pid, sorted_keys, sorted_entries); |
| bool header_printed = false; |
| for (size_t index = 0; index < maximum_sorted_entries; ++index) { |
| const TEntry* entry = sorted_entries[index]; |
| if (!entry) { |
| break; |
| } |
| if (entry->getSizes() <= (sorted_entries[0]->getSizes() / 100)) { |
| break; |
| } |
| if (!header_printed) { |
| output += "\n\n"; |
| output += entry->formatHeader(name, id); |
| header_printed = true; |
| } |
| output += entry->format(*this, id, *sorted_keys[index]); |
| } |
| return output; |
| } |
| |
| std::string LogStatistics::Format(uid_t uid, pid_t pid, unsigned int logMask) const { |
| auto lock = std::lock_guard{lock_}; |
| |
| static const uint16_t spaces_total = 19; |
| |
| // Report on total logging, current and for all time |
| |
| std::string output = "size/num"; |
| size_t oldLength; |
| int16_t 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(); |
| } |
| if (spaces < 0) spaces = 0; |
| output += android::base::StringPrintf("%*sTotal", spaces, ""); |
| |
| static const char TotalStr[] = "\nTotal"; |
| spaces = 10 - strlen(TotalStr); |
| output += TotalStr; |
| |
| size_t totalSize = 0; |
| size_t totalEls = 0; |
| log_id_for_each(id) { |
| if (!(logMask & (1 << id))) continue; |
| oldLength = output.length(); |
| if (spaces < 0) spaces = 0; |
| size_t szs = mSizesTotal[id]; |
| totalSize += szs; |
| size_t els = mElementsTotal[id]; |
| totalEls += els; |
| output += |
| android::base::StringPrintf("%*s%zu/%zu", spaces, "", szs, els); |
| spaces += spaces_total + oldLength - output.length(); |
| } |
| if (spaces < 0) spaces = 0; |
| output += android::base::StringPrintf("%*s%zu/%zu", spaces, "", totalSize, |
| totalEls); |
| |
| static const char NowStr[] = "\nNow"; |
| spaces = 10 - strlen(NowStr); |
| output += NowStr; |
| |
| totalSize = 0; |
| totalEls = 0; |
| log_id_for_each(id) { |
| if (!(logMask & (1 << id))) continue; |
| |
| size_t els = mElements[id]; |
| if (els) { |
| oldLength = output.length(); |
| if (spaces < 0) spaces = 0; |
| size_t szs = mSizes[id]; |
| totalSize += szs; |
| totalEls += els; |
| output += |
| android::base::StringPrintf("%*s%zu/%zu", spaces, "", szs, els); |
| spaces -= output.length() - oldLength; |
| } |
| spaces += spaces_total; |
| } |
| if (spaces < 0) spaces = 0; |
| output += android::base::StringPrintf("%*s%zu/%zu", spaces, "", totalSize, |
| totalEls); |
| |
| static const char SpanStr[] = "\nLogspan"; |
| spaces = 10 - strlen(SpanStr); |
| output += SpanStr; |
| |
| // Total reports the greater of the individual maximum time span, or the |
| // validated minimum start and maximum end time span if it makes sense. |
| uint64_t minTime = UINT64_MAX; |
| uint64_t maxTime = 0; |
| uint64_t maxSpan = 0; |
| totalSize = 0; |
| |
| log_id_for_each(id) { |
| if (!(logMask & (1 << id))) continue; |
| |
| // validity checking |
| uint64_t oldest = mOldest[id].msec(); |
| uint64_t newest = mNewest[id].msec(); |
| if (newest <= oldest) { |
| spaces += spaces_total; |
| continue; |
| } |
| |
| uint64_t span = newest - oldest; |
| if (span > (monthSec * MS_PER_SEC)) { |
| spaces += spaces_total; |
| continue; |
| } |
| |
| // total span |
| if (minTime > oldest) minTime = oldest; |
| if (maxTime < newest) maxTime = newest; |
| if (span > maxSpan) maxSpan = span; |
| totalSize += span; |
| |
| uint64_t dropped = mNewestDropped[id].msec(); |
| if (dropped < oldest) dropped = oldest; |
| if (dropped > newest) dropped = newest; |
| |
| oldLength = output.length(); |
| output += android::base::StringPrintf("%*s%s", spaces, "", |
| formatMsec(span).c_str()); |
| unsigned permille = ((newest - dropped) * 1000 + (span / 2)) / span; |
| if ((permille > 1) && (permille < 999)) { |
| output += android::base::StringPrintf("(%u", permille / 10); |
| permille %= 10; |
| if (permille) { |
| output += android::base::StringPrintf(".%u", permille); |
| } |
| output += android::base::StringPrintf("%%)"); |
| } |
| spaces -= output.length() - oldLength; |
| spaces += spaces_total; |
| } |
| if ((maxTime > minTime) && ((maxTime -= minTime) < totalSize) && |
| (maxTime > maxSpan)) { |
| maxSpan = maxTime; |
| } |
| if (spaces < 0) spaces = 0; |
| output += android::base::StringPrintf("%*s%s", spaces, "", |
| formatMsec(maxSpan).c_str()); |
| |
| static const char OverheadStr[] = "\nOverhead"; |
| spaces = 10 - strlen(OverheadStr); |
| output += OverheadStr; |
| |
| totalSize = 0; |
| log_id_for_each(id) { |
| if (!(logMask & (1 << id))) continue; |
| |
| size_t els = mElements[id]; |
| if (els) { |
| oldLength = output.length(); |
| if (spaces < 0) spaces = 0; |
| // estimate the std::list overhead. |
| static const size_t overhead = |
| ((sizeof(LogBufferElement) + sizeof(uint64_t) - 1) & |
| -sizeof(uint64_t)) + |
| sizeof(std::list<LogBufferElement*>); |
| size_t szs = mSizes[id] + els * overhead; |
| totalSize += szs; |
| output += android::base::StringPrintf("%*s%zu", spaces, "", szs); |
| spaces -= output.length() - oldLength; |
| } |
| spaces += spaces_total; |
| } |
| totalSize += sizeOf(); |
| if (spaces < 0) spaces = 0; |
| output += android::base::StringPrintf("%*s%zu", spaces, "", totalSize); |
| |
| // Report on Chattiest |
| |
| std::string name; |
| |
| // Chattiest by application (UID) |
| log_id_for_each(id) { |
| if (!(logMask & (1 << id))) continue; |
| |
| name = (uid == AID_ROOT) ? "Chattiest UIDs in %s log buffer:" |
| : "Logging for your UID in %s log buffer:"; |
| output += FormatTable(uidTable[id], uid, pid, name, id); |
| } |
| |
| if (enable) { |
| name = ((uid == AID_ROOT) && !pid) ? "Chattiest PIDs:" |
| : "Logging for this PID:"; |
| output += FormatTable(pidTable, uid, pid, name); |
| name = "Chattiest TIDs"; |
| if (pid) name += android::base::StringPrintf(" for PID %d", pid); |
| name += ":"; |
| output += FormatTable(tidTable, uid, pid, name); |
| } |
| |
| if (enable && (logMask & (1 << LOG_ID_EVENTS))) { |
| name = "Chattiest events log buffer TAGs"; |
| if (pid) name += android::base::StringPrintf(" for PID %d", pid); |
| name += ":"; |
| output += FormatTable(tagTable, uid, pid, name, LOG_ID_EVENTS); |
| } |
| |
| if (enable && (logMask & (1 << LOG_ID_SECURITY))) { |
| name = "Chattiest security log buffer TAGs"; |
| if (pid) name += android::base::StringPrintf(" for PID %d", pid); |
| name += ":"; |
| output += FormatTable(securityTagTable, uid, pid, name, LOG_ID_SECURITY); |
| } |
| |
| if (enable) { |
| name = "Chattiest TAGs"; |
| if (pid) name += android::base::StringPrintf(" for PID %d", pid); |
| name += ":"; |
| output += FormatTable(tagNameTable, uid, pid, name); |
| } |
| |
| 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, "re"); |
| if (fp) { |
| while (fgets(buffer, sizeof(buffer), fp)) { |
| int uid = AID_LOGD; |
| char space = 0; |
| if ((sscanf(buffer, "Uid: %d%c", &uid, &space) == 2) && |
| isspace(space)) { |
| fclose(fp); |
| return uid; |
| } |
| } |
| fclose(fp); |
| } |
| return AID_LOGD; // associate this with the logger |
| } |
| } |
| |
| uid_t LogStatistics::PidToUid(pid_t pid) { |
| auto lock = std::lock_guard{lock_}; |
| return pidTable.Add(pid)->second.uid(); |
| } |
| |
| // caller must free character string |
| const char* LogStatistics::PidToName(pid_t pid) const { |
| auto lock = std::lock_guard{lock_}; |
| // An inconvenient truth ... getName() can alter the object |
| pidTable_t& writablePidTable = const_cast<pidTable_t&>(pidTable); |
| const char* name = writablePidTable.Add(pid)->second.name(); |
| if (!name) { |
| return nullptr; |
| } |
| return strdup(name); |
| } |