| /* | 
 |  * 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 <pwd.h> | 
 | #include <stdio.h> | 
 | #include <string.h> | 
 | #include <sys/types.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <log/logger.h> | 
 |  | 
 | #include "LogStatistics.h" | 
 |  | 
 | LogStatistics::LogStatistics() : enable(false) { | 
 |     log_id_for_each(id) { | 
 |         mSizes[id] = 0; | 
 |         mElements[id] = 0; | 
 |         mDroppedElements[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 (fast<strcmp>(buffer, "<pre-initialized>")) { | 
 |                     retval = strdup(buffer); | 
 |                 } | 
 |             } | 
 |             close(fd); | 
 |         } | 
 |     } | 
 |     return retval; | 
 | } | 
 |  | 
 | } | 
 |  | 
 | void LogStatistics::add(LogBufferElement *element) { | 
 |     log_id_t log_id = element->getLogId(); | 
 |     unsigned short size = element->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(element->getUid(), element); | 
 |     if (element->getUid() == AID_SYSTEM) { | 
 |         pidSystemTable[log_id].add(element->getPid(), element); | 
 |     } | 
 |  | 
 |     if (!enable) { | 
 |         return; | 
 |     } | 
 |  | 
 |     pidTable.add(element->getPid(), element); | 
 |     tidTable.add(element->getTid(), element); | 
 |  | 
 |     uint32_t tag = element->getTag(); | 
 |     if (tag) { | 
 |         if (log_id == LOG_ID_SECURITY) { | 
 |             securityTagTable.add(tag, element); | 
 |         } else { | 
 |             tagTable.add(tag, element); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | void LogStatistics::subtract(LogBufferElement *element) { | 
 |     log_id_t log_id = element->getLogId(); | 
 |     unsigned short size = element->getMsgLen(); | 
 |     mSizes[log_id] -= size; | 
 |     --mElements[log_id]; | 
 |     if (element->getDropped()) { | 
 |         --mDroppedElements[log_id]; | 
 |     } | 
 |  | 
 |     if (log_id == LOG_ID_KERNEL) { | 
 |         return; | 
 |     } | 
 |  | 
 |     uidTable[log_id].subtract(element->getUid(), element); | 
 |     if (element->getUid() == AID_SYSTEM) { | 
 |         pidSystemTable[log_id].subtract(element->getPid(), element); | 
 |     } | 
 |  | 
 |     if (!enable) { | 
 |         return; | 
 |     } | 
 |  | 
 |     pidTable.subtract(element->getPid(), element); | 
 |     tidTable.subtract(element->getTid(), element); | 
 |  | 
 |     uint32_t tag = element->getTag(); | 
 |     if (tag) { | 
 |         if (log_id == LOG_ID_SECURITY) { | 
 |             securityTagTable.subtract(tag, element); | 
 |         } else { | 
 |             tagTable.subtract(tag, element); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | // Atomically set an entry to drop | 
 | // entry->setDropped(1) must follow this call, caller should do this explicitly. | 
 | void LogStatistics::drop(LogBufferElement *element) { | 
 |     log_id_t log_id = element->getLogId(); | 
 |     unsigned short size = element->getMsgLen(); | 
 |     mSizes[log_id] -= size; | 
 |     ++mDroppedElements[log_id]; | 
 |  | 
 |     uidTable[log_id].drop(element->getUid(), element); | 
 |     if (element->getUid() == AID_SYSTEM) { | 
 |         pidSystemTable[log_id].drop(element->getPid(), element); | 
 |     } | 
 |  | 
 |     if (!enable) { | 
 |         return; | 
 |     } | 
 |  | 
 |     pidTable.drop(element->getPid(), element); | 
 |     tidTable.drop(element->getTid(), element); | 
 |  | 
 |     uint32_t tag = element->getTag(); | 
 |     if (tag) { | 
 |         if (log_id == LOG_ID_SECURITY) { | 
 |             securityTagTable.drop(tag, element); | 
 |         } else { | 
 |             tagTable.drop(tag, element); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | // caller must own and free character string | 
 | const char *LogStatistics::uidToName(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; | 
 |     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.getUid() == uid) { | 
 |             const char *nameTmp = entry.getName(); | 
 |  | 
 |             if (nameTmp) { | 
 |                 if (!name) { | 
 |                     name = strdup(nameTmp); | 
 |                 } else if (fast<strcmp>(name, nameTmp)) { | 
 |                     free(const_cast<char *>(name)); | 
 |                     name = NULL; | 
 |                     break; | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     // No one | 
 |     return name; | 
 | } | 
 |  | 
 | 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" : "")); | 
 | } | 
 |  | 
 | std::string UidEntry::format(const LogStatistics &stat, log_id_t id) const { | 
 |     uid_t uid = getUid(); | 
 |     std::string name = android::base::StringPrintf("%u", uid); | 
 |     const char *nameTmp = stat.uidToName(uid); | 
 |     if (nameTmp) { | 
 |         name += android::base::StringPrintf( | 
 |             "%*s%s", (int)std::max(6 - name.length(), (size_t)1), | 
 |             "", nameTmp); | 
 |         free(const_cast<char *>(nameTmp)); | 
 |     } | 
 |  | 
 |     std::string size = android::base::StringPrintf("%zu", getSizes()); | 
 |  | 
 |     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.getDropped(); | 
 |         } | 
 |         size_t sizes = stat.sizes(id); | 
 |         size_t totalSize = stat.sizesTotal(id); | 
 |         size_t totalElements = stat.elementsTotal(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 = getDropped(); | 
 |         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 = EntryBaseConstants::pruned_len | 
 |                            - 2 - pruned.length() - change.length(); | 
 |             if ((spaces <= 0) && pruned.length()) { | 
 |                 spaces = 1; | 
 |             } | 
 |             if ((spaces > 0) && (pruned.length() != 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::unique_ptr<const PidEntry *[]> sorted | 
 |         = stat.pidSystemTable[id].sort(uid, (pid_t)0, maximum_sorted_entries); | 
 |  | 
 |     if (!sorted.get()) { | 
 |         return output; | 
 |     } | 
 |     std::string byPid; | 
 |     size_t index; | 
 |     bool hasDropped = false; | 
 |     for (index = 0; index < maximum_sorted_entries; ++index) { | 
 |         const PidEntry *entry = sorted[index]; | 
 |         if (!entry) { | 
 |             break; | 
 |         } | 
 |         if (entry->getSizes() <= (getSizes() / 100)) { | 
 |             break; | 
 |         } | 
 |         if (entry->getDropped()) { | 
 |             hasDropped = true; | 
 |         } | 
 |         byPid += entry->format(stat, id); | 
 |     } | 
 |     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 /* id */) const { | 
 |     uid_t uid = getUid(); | 
 |     pid_t pid = getPid(); | 
 |     std::string name = android::base::StringPrintf("%5u/%u", pid, uid); | 
 |     const char *nameTmp = getName(); | 
 |     if (nameTmp) { | 
 |         name += android::base::StringPrintf( | 
 |             "%*s%s", (int)std::max(12 - name.length(), (size_t)1), | 
 |             "", nameTmp); | 
 |     } else if ((nameTmp = stat.uidToName(uid))) { | 
 |         name += android::base::StringPrintf( | 
 |             "%*s%s", (int)std::max(12 - name.length(), (size_t)1), | 
 |             "", nameTmp); | 
 |         free(const_cast<char *>(nameTmp)); | 
 |     } | 
 |  | 
 |     std::string size = android::base::StringPrintf("%zu", | 
 |                                                    getSizes()); | 
 |  | 
 |     std::string pruned = ""; | 
 |     size_t dropped = getDropped(); | 
 |     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 /* id */) const { | 
 |     uid_t uid = getUid(); | 
 |     std::string name = android::base::StringPrintf("%5u/%u", | 
 |                                                    getTid(), uid); | 
 |     const char *nameTmp = getName(); | 
 |     if (nameTmp) { | 
 |         name += android::base::StringPrintf( | 
 |             "%*s%s", (int)std::max(12 - name.length(), (size_t)1), | 
 |             "", nameTmp); | 
 |     } else if ((nameTmp = stat.uidToName(uid))) { | 
 |         // if we do not have a PID name, lets punt to try UID name? | 
 |         name += android::base::StringPrintf( | 
 |             "%*s%s", (int)std::max(12 - name.length(), (size_t)1), | 
 |             "", nameTmp); | 
 |         free(const_cast<char *>(nameTmp)); | 
 |         // 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", | 
 |                                                    getSizes()); | 
 |  | 
 |     std::string pruned = ""; | 
 |     size_t dropped = getDropped(); | 
 |     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 & /* stat */, log_id_t /* id */) const { | 
 |     std::string name; | 
 |     uid_t uid = getUid(); | 
 |     if (uid == (uid_t)-1) { | 
 |         name = android::base::StringPrintf("%7u", | 
 |                                            getKey()); | 
 |     } else { | 
 |         name = android::base::StringPrintf("%7u/%u", | 
 |                                            getKey(), uid); | 
 |     } | 
 |     const char *nameTmp = getName(); | 
 |     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 = getDropped(); | 
 |     if (dropped) { | 
 |         pruned = android::base::StringPrintf("%zu", dropped); | 
 |     } | 
 |  | 
 |     return formatLine(name, size, pruned); | 
 | } | 
 |  | 
 | std::string LogStatistics::format(uid_t uid, pid_t pid, | 
 |                                   unsigned int logMask) const { | 
 |     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 += "\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 += "\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 | 
 |  | 
 |     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 += uidTable[id].format(*this, uid, pid, name, id); | 
 |     } | 
 |  | 
 |     if (enable) { | 
 |         name = ((uid == AID_ROOT) && !pid) | 
 |             ? "Chattiest PIDs:" | 
 |             : "Logging for this PID:"; | 
 |         output += pidTable.format(*this, uid, pid, name); | 
 |         name = "Chattiest TIDs"; | 
 |         if (pid) { | 
 |             name += android::base::StringPrintf(" for PID %d", pid); | 
 |         } | 
 |         name += ":"; | 
 |         output += tidTable.format(*this, 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 += tagTable.format(*this, 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 += securityTagTable.format(*this, uid, pid, name, LOG_ID_SECURITY); | 
 |     } | 
 |  | 
 |     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 | 
 | const char *LogStatistics::pidToName(pid_t pid) const { | 
 |     // An inconvenient truth ... getName() can alter the object | 
 |     pidTable_t &writablePidTable = const_cast<pidTable_t &>(pidTable); | 
 |     const char *name = writablePidTable.add(pid)->second.getName(); | 
 |     if (!name) { | 
 |         return NULL; | 
 |     } | 
 |     return strdup(name); | 
 | } |