/* * Copyright (c) 2020, 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. */ #define LOG_TAG "carwatchdogd" #define DEBUG false // STOPSHIP if true. #include "UidProcStatsCollector.h" #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace automotive { namespace watchdog { using ::android::base::EndsWith; using ::android::base::Error; using ::android::base::ParseInt; using ::android::base::ParseUint; using ::android::base::ReadFileToString; using ::android::base::Result; using ::android::base::Split; using ::android::base::StartsWith; using ::android::base::StringAppendF; using ::android::base::Trim; using ::android::meminfo::MemUsage; using ::android::meminfo::ProcMemInfo; namespace { constexpr uint64_t kMaxUint64 = std::numeric_limits::max(); constexpr const char* kProcPidStatFileFormat = "/proc/%" PRIu32 "/stat"; constexpr const char* kProcPidStatusFileFormat = "/proc/%" PRIu32 "/status"; enum ReadStatus { // Default value is an error for backwards compatibility with the Result::ErrorCode. READ_ERROR = 0, // PIDs may disappear between scanning and reading directory/files. Use |READ_WARNING| in these // instances to return the missing directory/file for logging purposes. READ_WARNING = 1, NUM_READ_STATUS = 2, }; uint64_t addUint64(const uint64_t& l, const uint64_t& r) { return (kMaxUint64 - l) > r ? (l + r) : kMaxUint64; } /** * /proc/PID/stat or /proc/PID/task/TID/stat format: * * * * * * * * * * Example line: 1 (init) S 0 0 0 0 0 0 0 0 220 0 0 0 0 0 0 0 2 0 0 ...etc... */ bool parsePidStatLine(const std::string& line, PidStat* pidStat) { std::vector fields = Split(line, " "); /* Note: Regex parsing for the below logic increased the time taken to run the * UidProcStatsCollectorTest#TestProcPidStatContentsFromDevice from 151.7ms to 1.3 seconds. * * Comm string is enclosed with ( ) brackets and may contain space(s). Thus calculate the * commEndOffset based on the field that contains the closing bracket. */ size_t commEndOffset = 0; for (size_t i = 1; i < fields.size(); ++i) { pidStat->comm += fields[i]; if (EndsWith(fields[i], ")")) { commEndOffset = i - 1; break; } pidStat->comm += " "; } if (pidStat->comm.front() != '(' || pidStat->comm.back() != ')') { ALOGD("Comm string `%s` not enclosed in brackets", pidStat->comm.c_str()); return false; } pidStat->comm.erase(pidStat->comm.begin()); pidStat->comm.erase(pidStat->comm.end() - 1); int64_t systemCpuTime = 0; if (fields.size() < 22 + commEndOffset || !ParseUint(fields[11 + commEndOffset], &pidStat->majorFaults) || !ParseInt(fields[13 + commEndOffset], &pidStat->cpuTimeMillis) || !ParseInt(fields[14 + commEndOffset], &systemCpuTime) || !ParseInt(fields[21 + commEndOffset], &pidStat->startTimeMillis)) { ALOGD("Invalid proc pid stat contents: \"%s\"", line.c_str()); return false; } pidStat->cpuTimeMillis += systemCpuTime; pidStat->state = fields[2 + commEndOffset]; return true; } Result readPidStatFile(const std::string& path, int32_t millisPerClockTick) { std::string buffer; if (!ReadFileToString(path, &buffer)) { return Error(READ_WARNING) << "ReadFileToString failed for " << path; } std::vector lines = Split(std::move(buffer), "\n"); if (lines.size() != 1 && (lines.size() != 2 || !lines[1].empty())) { return Error(READ_ERROR) << path << " contains " << lines.size() << " lines != 1"; } PidStat pidStat; if (!parsePidStatLine(std::move(lines[0]), &pidStat)) { return Error(READ_ERROR) << "Failed to parse the contents of " << path; } pidStat.startTimeMillis = pidStat.startTimeMillis * millisPerClockTick; pidStat.cpuTimeMillis = pidStat.cpuTimeMillis * millisPerClockTick; return pidStat; } std::vector getLinesWithTags(std::string_view buffer, const std::vector& tags) { std::vector result; std::vector notFoundTags(tags); size_t base = 0; std::string_view sub; for (size_t found = 0; !notFoundTags.empty() && found != buffer.npos;) { found = buffer.find_first_of('\n', base); sub = buffer.substr(base, found - base); bool hasTag = false; for (auto it = notFoundTags.begin(); it != notFoundTags.end();) { if (sub.find(*it) != sub.npos) { notFoundTags.erase(it); hasTag = true; } else { ++it; } } if (hasTag) { result.push_back(std::string{sub}); } base = found + 1; } return result; } Result> readKeyValueFile( const std::string& path, const std::string& delimiter, const std::vector& tags) { std::string buffer; if (!ReadFileToString(path, &buffer)) { return Error(READ_WARNING) << "ReadFileToString failed for " << path; } std::vector lines = getLinesWithTags(buffer, tags); std::unordered_map contents; for (size_t i = 0; i < lines.size(); ++i) { if (lines[i].empty()) { continue; } std::vector elements = Split(lines[i], delimiter); if (elements.size() < 2) { return Error(READ_ERROR) << "Line \"" << lines[i] << "\" doesn't contain the delimiter \"" << delimiter << "\" in file " << path; } std::string key = elements[0]; std::string value = Trim(lines[i].substr(key.length() + delimiter.length())); if (contents.find(key) != contents.end()) { return Error(READ_ERROR) << "Duplicate " << key << " line: \"" << lines[i] << "\" in file " << path; } contents[key] = value; } return contents; } /** * Returns UID and TGID from the given pid status file. * * /proc/PID/status file format: * Tgid: * Uid: * * Note: Included only the fields that are parsed from the file. */ Result> readPidStatusFile(const std::string& path) { auto result = readKeyValueFile(path, ":\t", {"Uid", "Tgid"}); if (!result.ok()) { return Error(result.error().code()) << result.error(); } auto contents = result.value(); if (contents.empty()) { return Error(READ_ERROR) << "Empty file " << path; } int64_t uid = 0; int64_t tgid = 0; if (contents.find("Uid") == contents.end() || !ParseInt(Split(contents["Uid"], "\t")[0], &uid)) { return Error(READ_ERROR) << "Failed to read 'UID' from file " << path; } if (contents.find("Tgid") == contents.end() || !ParseInt(contents["Tgid"], &tgid)) { return Error(READ_ERROR) << "Failed to read 'Tgid' from file " << path; } return std::make_tuple(uid, tgid); } /** * Returns the total CPU cycles from the given time_in_state file. * * /proc/PID/task/TID/time_in_state file format: * cpuX *