1 /*
2  * Copyright (c) 2020, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "carwatchdogd"
18 #define DEBUG false  // STOPSHIP if true.
19 
20 #include "UidProcStatsCollector.h"
21 
22 #include <android-base/file.h>
23 #include <android-base/parseint.h>
24 #include <android-base/result.h>
25 #include <android-base/stringprintf.h>
26 #include <android-base/strings.h>
27 #include <log/log.h>
28 
29 #include <dirent.h>
30 
31 #include <string>
32 #include <string_view>
33 #include <unordered_map>
34 #include <vector>
35 
36 namespace android {
37 namespace automotive {
38 namespace watchdog {
39 
40 using ::android::base::EndsWith;
41 using ::android::base::Error;
42 using ::android::base::ParseInt;
43 using ::android::base::ParseUint;
44 using ::android::base::ReadFileToString;
45 using ::android::base::Result;
46 using ::android::base::Split;
47 using ::android::base::StartsWith;
48 using ::android::base::StringAppendF;
49 using ::android::base::Trim;
50 using ::android::meminfo::MemUsage;
51 using ::android::meminfo::ProcMemInfo;
52 
53 namespace {
54 
55 constexpr uint64_t kMaxUint64 = std::numeric_limits<uint64_t>::max();
56 
57 constexpr const char* kProcPidStatFileFormat = "/proc/%" PRIu32 "/stat";
58 constexpr const char* kProcPidStatusFileFormat = "/proc/%" PRIu32 "/status";
59 
60 enum ReadStatus {
61     // Default value is an error for backwards compatibility with the Result::ErrorCode.
62     READ_ERROR = 0,
63     // PIDs may disappear between scanning and reading directory/files. Use |READ_WARNING| in these
64     // instances to return the missing directory/file for logging purposes.
65     READ_WARNING = 1,
66     NUM_READ_STATUS = 2,
67 };
68 
addUint64(const uint64_t & l,const uint64_t & r)69 uint64_t addUint64(const uint64_t& l, const uint64_t& r) {
70     return (kMaxUint64 - l) > r ? (l + r) : kMaxUint64;
71 }
72 
73 /**
74  * /proc/PID/stat or /proc/PID/task/TID/stat format:
75  * <pid> <comm> <state> <ppid> <pgrp ID> <session ID> <tty_nr> <tpgid> <flags> <minor faults>
76  * <children minor faults> <major faults> <children major faults> <user mode time>
77  * <system mode time> <children user mode time> <children kernel mode time> <priority> <nice value>
78  * <num threads> <start time since boot> <virtual memory size> <resident set size> <rss soft limit>
79  * <start code addr> <end code addr> <start stack addr> <ESP value> <EIP> <bitmap of pending sigs>
80  * <bitmap of blocked sigs> <bitmap of ignored sigs> <waiting channel> <num pages swapped>
81  * <cumulative pages swapped> <exit signal> <processor #> <real-time prio> <agg block I/O delays>
82  * <guest time> <children guest time> <start data addr> <end data addr> <start break addr>
83  * <cmd line args start addr> <amd line args end addr> <env start addr> <env end addr> <exit code>
84  * 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...
85  */
parsePidStatLine(const std::string & line,PidStat * pidStat)86 bool parsePidStatLine(const std::string& line, PidStat* pidStat) {
87     std::vector<std::string> fields = Split(line, " ");
88 
89     /* Note: Regex parsing for the below logic increased the time taken to run the
90      * UidProcStatsCollectorTest#TestProcPidStatContentsFromDevice from 151.7ms to 1.3 seconds.
91      *
92      * Comm string is enclosed with ( ) brackets and may contain space(s). Thus calculate the
93      * commEndOffset based on the field that contains the closing bracket.
94      */
95     size_t commEndOffset = 0;
96     for (size_t i = 1; i < fields.size(); ++i) {
97         pidStat->comm += fields[i];
98         if (EndsWith(fields[i], ")")) {
99             commEndOffset = i - 1;
100             break;
101         }
102         pidStat->comm += " ";
103     }
104 
105     if (pidStat->comm.front() != '(' || pidStat->comm.back() != ')') {
106         ALOGD("Comm string `%s` not enclosed in brackets", pidStat->comm.c_str());
107         return false;
108     }
109     pidStat->comm.erase(pidStat->comm.begin());
110     pidStat->comm.erase(pidStat->comm.end() - 1);
111 
112     int64_t systemCpuTime = 0;
113     if (fields.size() < 22 + commEndOffset ||
114         !ParseUint(fields[11 + commEndOffset], &pidStat->majorFaults) ||
115         !ParseInt(fields[13 + commEndOffset], &pidStat->cpuTimeMillis) ||
116         !ParseInt(fields[14 + commEndOffset], &systemCpuTime) ||
117         !ParseInt(fields[21 + commEndOffset], &pidStat->startTimeMillis)) {
118         ALOGD("Invalid proc pid stat contents: \"%s\"", line.c_str());
119         return false;
120     }
121     pidStat->cpuTimeMillis += systemCpuTime;
122     pidStat->state = fields[2 + commEndOffset];
123     return true;
124 }
125 
readPidStatFile(const std::string & path,int32_t millisPerClockTick)126 Result<PidStat> readPidStatFile(const std::string& path, int32_t millisPerClockTick) {
127     std::string buffer;
128     if (!ReadFileToString(path, &buffer)) {
129         return Error(READ_WARNING) << "ReadFileToString failed for " << path;
130     }
131     std::vector<std::string> lines = Split(std::move(buffer), "\n");
132     if (lines.size() != 1 && (lines.size() != 2 || !lines[1].empty())) {
133         return Error(READ_ERROR) << path << " contains " << lines.size() << " lines != 1";
134     }
135     PidStat pidStat;
136     if (!parsePidStatLine(std::move(lines[0]), &pidStat)) {
137         return Error(READ_ERROR) << "Failed to parse the contents of " << path;
138     }
139     pidStat.startTimeMillis = pidStat.startTimeMillis * millisPerClockTick;
140     pidStat.cpuTimeMillis = pidStat.cpuTimeMillis * millisPerClockTick;
141     return pidStat;
142 }
143 
getLinesWithTags(std::string_view buffer,const std::vector<std::string> & tags)144 std::vector<std::string> getLinesWithTags(std::string_view buffer,
145                                           const std::vector<std::string>& tags) {
146     std::vector<std::string> result;
147     std::vector<std::string> notFoundTags(tags);
148     size_t base = 0;
149     std::string_view sub;
150     for (size_t found = 0; !notFoundTags.empty() && found != buffer.npos;) {
151         found = buffer.find_first_of('\n', base);
152         sub = buffer.substr(base, found - base);
153         bool hasTag = false;
154         for (auto it = notFoundTags.begin(); it != notFoundTags.end();) {
155             if (sub.find(*it) != sub.npos) {
156                 notFoundTags.erase(it);
157                 hasTag = true;
158             } else {
159                 ++it;
160             }
161         }
162         if (hasTag) {
163             result.push_back(std::string{sub});
164         }
165         base = found + 1;
166     }
167     return result;
168 }
169 
readKeyValueFile(const std::string & path,const std::string & delimiter,const std::vector<std::string> & tags)170 Result<std::unordered_map<std::string, std::string>> readKeyValueFile(
171         const std::string& path, const std::string& delimiter,
172         const std::vector<std::string>& tags) {
173     std::string buffer;
174     if (!ReadFileToString(path, &buffer)) {
175         return Error(READ_WARNING) << "ReadFileToString failed for " << path;
176     }
177     std::vector<std::string> lines = getLinesWithTags(buffer, tags);
178     std::unordered_map<std::string, std::string> contents;
179     for (size_t i = 0; i < lines.size(); ++i) {
180         if (lines[i].empty()) {
181             continue;
182         }
183         std::vector<std::string> elements = Split(lines[i], delimiter);
184         if (elements.size() < 2) {
185             return Error(READ_ERROR)
186                     << "Line \"" << lines[i] << "\" doesn't contain the delimiter \"" << delimiter
187                     << "\" in file " << path;
188         }
189         std::string key = elements[0];
190         std::string value = Trim(lines[i].substr(key.length() + delimiter.length()));
191         if (contents.find(key) != contents.end()) {
192             return Error(READ_ERROR)
193                     << "Duplicate " << key << " line: \"" << lines[i] << "\" in file " << path;
194         }
195         contents[key] = value;
196     }
197     return contents;
198 }
199 
200 /**
201  * Returns UID and TGID from the given pid status file.
202  *
203  * /proc/PID/status file format:
204  * Tgid:    <Thread group ID of the process>
205  * Uid:     <Read UID>   <Effective UID>   <Saved set UID>   <Filesystem UID>
206  *
207  * Note: Included only the fields that are parsed from the file.
208  */
readPidStatusFile(const std::string & path)209 Result<std::tuple<uid_t, pid_t>> readPidStatusFile(const std::string& path) {
210     auto result = readKeyValueFile(path, ":\t", {"Uid", "Tgid"});
211     if (!result.ok()) {
212         return Error(result.error().code()) << result.error();
213     }
214     auto contents = result.value();
215     if (contents.empty()) {
216         return Error(READ_ERROR) << "Empty file " << path;
217     }
218     int64_t uid = 0;
219     int64_t tgid = 0;
220     if (contents.find("Uid") == contents.end() ||
221         !ParseInt(Split(contents["Uid"], "\t")[0], &uid)) {
222         return Error(READ_ERROR) << "Failed to read 'UID' from file " << path;
223     }
224     if (contents.find("Tgid") == contents.end() || !ParseInt(contents["Tgid"], &tgid)) {
225         return Error(READ_ERROR) << "Failed to read 'Tgid' from file " << path;
226     }
227     return std::make_tuple(uid, tgid);
228 }
229 
230 /**
231  * Returns the total CPU cycles from the given time_in_state file.
232  *
233  * /proc/PID/task/TID/time_in_state file format:
234  * cpuX
235  * <CPU freq (kHz)> <time spent at freq (clock ticks)>
236  * <CPU freq (kHz)> <time spent at freq (clock ticks)>
237  * ...
238  * cpuY
239  * <CPU freq (kHz)> <time spent at freq (clock ticks)>
240  * <CPU freq (kHz)> <time spent at freq (clock ticks)>
241  * ...
242  *
243  * Note: Each 'cpuX' header refers to a particular CPU freq policy. A policy can contain multiple
244  * cores. Since we gather the time spent at a frequency at the thread level, their is no need
245  * aggregate the time across cores because threads only run in one core at a time.
246  */
readTimeInStateFile(const std::string & path)247 Result<uint64_t> readTimeInStateFile(const std::string& path) {
248     const auto mul = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
249         if (l == 0 || r == 0) {
250             return 0;
251         }
252         return (kMaxUint64 / r) > l ? (l * r) : kMaxUint64;
253     };
254 
255     std::string buffer;
256     if (!ReadFileToString(path, &buffer)) {
257         return Error(READ_WARNING) << "ReadFileToString failed for " << path;
258     }
259     std::string delimiter = " ";
260     uint64_t oneTenthCpuCycles = 0;
261     const uint64_t cyclesPerKHzClockTicks = 1000 / sysconf(_SC_CLK_TCK);
262     std::vector<std::string> lines = Split(buffer, "\n");
263     for (size_t i = 0; i < lines.size(); ++i) {
264         if (lines[i].empty() || StartsWith(lines[i], "cpu")) {
265             continue;
266         }
267         std::vector<std::string> elements = Split(lines[i], delimiter);
268         if (elements.size() < 2) {
269             return Error(READ_ERROR)
270                     << "Line \"" << lines[i] << "\" doesn't contain the delimiter \"" << delimiter
271                     << "\" in file " << path;
272         }
273         uint64_t freqKHz;
274         uint64_t clockTicks;
275         if (!ParseUint(elements[0], &freqKHz) || !ParseUint(Trim(elements[1]), &clockTicks)) {
276             return Error(READ_ERROR)
277                     << "Line \"" << lines[i] << "\" has invalid format in file " << path;
278         }
279         oneTenthCpuCycles = addUint64(oneTenthCpuCycles, mul(freqKHz, clockTicks));
280     }
281     // The frequency is in kHz and the time is in clock ticks (10ms). In order to obtain cycles,
282     // one has to scale the frequency by 1000 to obtain Hz and the time by 1/sysconf(_SC_CLK_TCK)
283     // to obtain seconds which results in scaling the result by |cyclesPerKHzClockTicks|.
284     return mul(oneTenthCpuCycles, cyclesPerKHzClockTicks);
285 }
286 
287 /**
288  * Returns the RSS and Shared pages from the given /proc/PID/statm file.
289  *
290  * /proc/PID/statm format:
291  * <Total program size> <Resident pages> <Shared pages> <Text pages> 0 <Data pages> 0
292  * Example: 2969783 1481 938 530 0 5067 0
293  */
readPidStatmFile(const std::string & path)294 Result<std::tuple<uint64_t, uint64_t>> readPidStatmFile(const std::string& path) {
295     std::string buffer;
296     if (!ReadFileToString(path, &buffer)) {
297         return Error(READ_WARNING) << "ReadFileToString failed for " << path;
298     }
299     std::vector<std::string> lines = Split(std::move(buffer), "\n");
300     if (lines.size() != 1 && (lines.size() != 2 || !lines[1].empty())) {
301         return Error(READ_ERROR) << path << " contains " << lines.size() << " lines != 1";
302     }
303     std::vector<std::string> fields = Split(std::move(lines[0]), " ");
304     if (fields.size() < 6) {
305         return Error(READ_ERROR) << path << " contains insufficient entries";
306     }
307     uint64_t rssPages = 0;
308     uint64_t sharedPages = 0;
309     if (!ParseUint(fields[1], &rssPages) || !ParseUint(fields[2], &sharedPages)) {
310         return Error(READ_ERROR) << "Failed to parse fields from " << path;
311     }
312     return std::make_tuple(rssPages, sharedPages);
313 }
314 
315 }  // namespace
316 
toString() const317 std::string ProcessStats::toString() const {
318     std::string buffer;
319     StringAppendF(&buffer,
320                   "{comm: %s, startTimeMillis: %" PRIu64 ", cpuTimeMillis: %" PRIu64
321                   ", totalCpuCycles: %" PRIu64 ", totalMajorFaults: %" PRIu64
322                   ", totalTasksCount: %d, ioBlockedTasksCount: %d, cpuCyclesByTid: {",
323                   comm.c_str(), startTimeMillis, cpuTimeMillis, totalCpuCycles, totalMajorFaults,
324                   totalTasksCount, ioBlockedTasksCount);
325     for (const auto& [tid, cpuCycles] : cpuCyclesByTid) {
326         StringAppendF(&buffer, "{tid: %d, cpuCycles: %" PRIu64 "},", tid, cpuCycles);
327     }
328     buffer.erase(buffer.length() - 1);
329     StringAppendF(&buffer,
330                   "}, rssKb: %" PRIu64 ", pssKb: %" PRIu64 ", ussKb: %" PRIu64
331                   ", swapPsskb: %" PRIu64 "} ",
332                   rssKb, pssKb, ussKb, swapPssKb);
333     return buffer;
334 }
335 
toString() const336 std::string UidProcStats::toString() const {
337     std::string buffer;
338     StringAppendF(&buffer,
339                   "UidProcStats{cpuTimeMillis: %" PRIu64 ", cpuCycles: %" PRIu64
340                   ", totalMajorFaults: %" PRIu64 ", totalTasksCount: %d, ioBlockedTasksCount: %d"
341                   ", totalRssKb: %" PRIu64 ", totalPssKb: %" PRIu64 ", processStatsByPid: {",
342                   cpuTimeMillis, cpuCycles, totalMajorFaults, totalTasksCount, ioBlockedTasksCount,
343                   totalRssKb, totalPssKb);
344     for (const auto& [pid, processStats] : processStatsByPid) {
345         StringAppendF(&buffer, "{pid: %" PRIi32 ", processStats: %s},", pid,
346                       processStats.toString().c_str());
347     }
348     buffer.erase(buffer.length() - 1);
349     StringAppendF(&buffer, "}}");
350     return buffer;
351 }
352 
UidProcStatsCollector(const std::string & path,bool isSmapsRollupSupported)353 UidProcStatsCollector::UidProcStatsCollector(const std::string& path, bool isSmapsRollupSupported) :
354       mIsMemoryProfilingEnabled(::android::car::feature::car_watchdog_memory_profiling()),
355       mMillisPerClockTick(1000 / sysconf(_SC_CLK_TCK)),
356       mPath(path),
357       mLatestStats({}),
358       mDeltaStats({}) {
359     mIsSmapsRollupSupported = isSmapsRollupSupported;
360     mPageSizeKb =
361             sysconf(_SC_PAGESIZE) > 1024 ? static_cast<size_t>(sysconf(_SC_PAGESIZE) / 1024) : 1;
362 }
363 
init()364 void UidProcStatsCollector::init() {
365     // Note: Verify proc file access outside the constructor. Otherwise, the unittests of
366     // dependent classes would call the constructor before mocking and get killed due to
367     // sepolicy violation.
368     std::string pidStatPath = StringPrintf((mPath + kStatFileFormat).c_str(), PID_FOR_INIT);
369     bool isPidStatPathAccessible = access(pidStatPath.c_str(), R_OK) == 0;
370 
371     std::string tidStatPath = StringPrintf((mPath + kTaskDirFormat + kStatFileFormat).c_str(),
372                                            PID_FOR_INIT, PID_FOR_INIT);
373     bool isTidStatPathAccessible = access(tidStatPath.c_str(), R_OK) == 0;
374 
375     std::string pidStatusPath = StringPrintf((mPath + kStatusFileFormat).c_str(), PID_FOR_INIT);
376     bool isPidStatusPathAccessible = access(pidStatusPath.c_str(), R_OK) == 0;
377 
378     std::string tidTimeInStatePath =
379             StringPrintf((mPath + kTaskDirFormat + kTimeInStateFileFormat).c_str(), PID_FOR_INIT,
380                          PID_FOR_INIT);
381     bool isTidTimeInStatePathAccessible = access(tidTimeInStatePath.c_str(), R_OK) == 0;
382 
383     bool isStatmPathAccessible;
384     std::string statmPath = StringPrintf((mPath + kStatmFileFormat).c_str(), PID_FOR_INIT);
385     if (mIsMemoryProfilingEnabled) {
386         isStatmPathAccessible = access(statmPath.c_str(), R_OK) == 0;
387     }
388 
389     Mutex::Autolock lock(mMutex);
390     mIsEnabled = isPidStatPathAccessible && isTidStatPathAccessible && isPidStatusPathAccessible;
391     if (mIsMemoryProfilingEnabled) {
392         mIsEnabled &= isStatmPathAccessible || mIsSmapsRollupSupported;
393     }
394 
395     if (isTidTimeInStatePathAccessible) {
396         auto tidCpuCycles = readTimeInStateFile(tidTimeInStatePath);
397         mIsTimeInStateEnabled = tidCpuCycles.ok() && *tidCpuCycles > 0;
398     }
399 
400     if (!mIsTimeInStateEnabled) {
401         ALOGW("Time in state collection is not enabled. Missing time in state file at path: %s",
402               tidTimeInStatePath.c_str());
403     }
404 
405     if (!mIsEnabled) {
406         std::string inaccessiblePaths;
407         if (!isPidStatPathAccessible) {
408             StringAppendF(&inaccessiblePaths, "%s, ", pidStatPath.c_str());
409         }
410         if (!isTidStatPathAccessible) {
411             StringAppendF(&inaccessiblePaths, "%s, ", pidStatPath.c_str());
412         }
413         if (!isPidStatusPathAccessible) {
414             StringAppendF(&inaccessiblePaths, "%s, ", pidStatusPath.c_str());
415         }
416         if (mIsMemoryProfilingEnabled && !isStatmPathAccessible) {
417             StringAppendF(&inaccessiblePaths, "%s, ", statmPath.c_str());
418         }
419         ALOGE("Disabling UidProcStatsCollector because access to the following files are not "
420               "available: '%s'",
421               inaccessiblePaths.substr(0, inaccessiblePaths.length() - 2).c_str());
422     }
423 }
424 
collect()425 Result<void> UidProcStatsCollector::collect() {
426     if (!mIsEnabled) {
427         return Error() << "Can not access PID stat files under " << kProcDirPath;
428     }
429 
430     Mutex::Autolock lock(mMutex);
431     auto uidProcStatsByUid = readUidProcStatsLocked();
432     if (!uidProcStatsByUid.ok()) {
433         return Error() << uidProcStatsByUid.error();
434     }
435 
436     mDeltaStats.clear();
437     for (const auto& [uid, currUidStats] : *uidProcStatsByUid) {
438         if (const auto& it = mLatestStats.find(uid); it == mLatestStats.end()) {
439             mDeltaStats[uid] = currUidStats;
440             continue;
441         }
442         const auto& prevUidStats = mLatestStats[uid];
443         UidProcStats deltaUidStats = {
444                 .totalTasksCount = currUidStats.totalTasksCount,
445                 .ioBlockedTasksCount = currUidStats.ioBlockedTasksCount,
446                 .totalRssKb = currUidStats.totalRssKb,
447                 .totalPssKb = currUidStats.totalPssKb,
448         };
449         // Generate the delta stats since the previous collection. Delta stats are generated by
450         // calculating the difference between the |prevUidStats| and the |currUidStats|.
451         for (const auto& [pid, processStats] : currUidStats.processStatsByPid) {
452             ProcessStats deltaProcessStats = processStats;
453             if (const auto& it = prevUidStats.processStatsByPid.find(pid);
454                 it != prevUidStats.processStatsByPid.end() &&
455                 it->second.startTimeMillis == deltaProcessStats.startTimeMillis) {
456                 auto prevProcessStats = it->second;
457                 if (prevProcessStats.cpuTimeMillis <= deltaProcessStats.cpuTimeMillis) {
458                     deltaProcessStats.cpuTimeMillis -= prevProcessStats.cpuTimeMillis;
459                 }
460                 if (prevProcessStats.totalMajorFaults <= deltaProcessStats.totalMajorFaults) {
461                     deltaProcessStats.totalMajorFaults -= prevProcessStats.totalMajorFaults;
462                 }
463                 // Generate the process delta CPU cycles by iterating through the thread-level CPU
464                 // cycles and calculating the sum of the deltas of each thread.
465                 deltaProcessStats.totalCpuCycles = 0;
466                 for (const auto& [tid, threadCpuCycles] : processStats.cpuCyclesByTid) {
467                     uint64_t deltaThreadCpuCycles = threadCpuCycles;
468                     if (const auto& cIt = prevProcessStats.cpuCyclesByTid.find(tid);
469                         cIt != prevProcessStats.cpuCyclesByTid.end() &&
470                         cIt->second <= deltaThreadCpuCycles) {
471                         deltaThreadCpuCycles -= cIt->second;
472                     }
473                     deltaProcessStats.cpuCyclesByTid[tid] = deltaThreadCpuCycles;
474                     deltaProcessStats.totalCpuCycles =
475                             addUint64(deltaProcessStats.totalCpuCycles, deltaThreadCpuCycles);
476                 }
477             }
478             deltaUidStats.cpuTimeMillis += deltaProcessStats.cpuTimeMillis;
479             deltaUidStats.cpuCycles =
480                     addUint64(deltaUidStats.cpuCycles, deltaProcessStats.totalCpuCycles);
481             deltaUidStats.totalMajorFaults += deltaProcessStats.totalMajorFaults;
482             deltaUidStats.processStatsByPid[pid] = deltaProcessStats;
483         }
484         mDeltaStats[uid] = std::move(deltaUidStats);
485     }
486     mLatestStats = std::move(*uidProcStatsByUid);
487     return {};
488 }
489 
readUidProcStatsLocked() const490 Result<std::unordered_map<uid_t, UidProcStats>> UidProcStatsCollector::readUidProcStatsLocked()
491         const {
492     std::unordered_map<uid_t, UidProcStats> uidProcStatsByUid;
493     auto procDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(mPath.c_str()), closedir);
494     if (!procDirp) {
495         return Error() << "Failed to open " << mPath << " directory";
496     }
497     for (dirent* pidDir = nullptr; (pidDir = readdir(procDirp.get())) != nullptr;) {
498         pid_t pid = 0;
499         if (pidDir->d_type != DT_DIR || !ParseInt(pidDir->d_name, &pid)) {
500             continue;
501         }
502         auto result = readProcessStatsLocked(pid);
503         if (!result.ok()) {
504             if (result.error().code() != READ_WARNING) {
505                 return Error() << result.error();
506             }
507             if (DEBUG) {
508                 ALOGD("%s", result.error().message().c_str());
509             }
510             continue;
511         }
512         uid_t uid = std::get<uid_t>(*result);
513         ProcessStats processStats = std::get<ProcessStats>(*result);
514         if (uidProcStatsByUid.find(uid) == uidProcStatsByUid.end()) {
515             uidProcStatsByUid[uid] = {};
516         }
517         UidProcStats* uidProcStats = &uidProcStatsByUid[uid];
518         uidProcStats->cpuTimeMillis += processStats.cpuTimeMillis;
519         uidProcStats->cpuCycles = addUint64(uidProcStats->cpuCycles, processStats.totalCpuCycles);
520         uidProcStats->totalMajorFaults += processStats.totalMajorFaults;
521         uidProcStats->totalTasksCount += processStats.totalTasksCount;
522         uidProcStats->ioBlockedTasksCount += processStats.ioBlockedTasksCount;
523         uidProcStats->totalRssKb += processStats.rssKb;
524         uidProcStats->totalPssKb += processStats.pssKb;
525         uidProcStats->processStatsByPid[pid] = std::move(processStats);
526     }
527     return uidProcStatsByUid;
528 }
529 
readProcessStatsLocked(pid_t pid) const530 Result<std::tuple<uid_t, ProcessStats>> UidProcStatsCollector::readProcessStatsLocked(
531         pid_t pid) const {
532     // 1. Read top-level pid stats.
533     std::string path = StringPrintf((mPath + kStatFileFormat).c_str(), pid);
534     auto pidStat = readPidStatFile(path, mMillisPerClockTick);
535     if (!pidStat.ok()) {
536         return Error(pidStat.error().code())
537                 << "Failed to read top-level per-process stat file '%s': %s"
538                 << pidStat.error().message().c_str();
539     }
540 
541     // 2. Read aggregated process status.
542     pid_t tgid = -1;
543     uid_t uid = -1;
544     path = StringPrintf((mPath + kStatusFileFormat).c_str(), pid);
545     if (auto result = readPidStatusFile(path); !result.ok()) {
546         if (result.error().code() != READ_WARNING) {
547             return Error() << "Failed to read pid status for pid " << pid << ": "
548                            << result.error().message().c_str();
549         }
550         for (const auto& [curUid, uidProcStats] : mLatestStats) {
551             if (const auto it = uidProcStats.processStatsByPid.find(pid);
552                 it != uidProcStats.processStatsByPid.end() &&
553                 it->second.startTimeMillis == pidStat->startTimeMillis) {
554                 tgid = pid;
555                 uid = curUid;
556                 break;
557             }
558         }
559     } else {
560         uid = std::get<0>(*result);
561         tgid = std::get<1>(*result);
562     }
563 
564     if (uid == static_cast<uid_t>(-1) || tgid != pid) {
565         return Error(READ_WARNING)
566                 << "Skipping PID '" << pid << "' because either Tgid != PID or invalid UID";
567     }
568 
569     ProcessStats processStats = {
570             .comm = std::move(pidStat->comm),
571             .startTimeMillis = pidStat->startTimeMillis,
572             .cpuTimeMillis = pidStat->cpuTimeMillis,
573             .totalCpuCycles = 0,
574             /* Top-level process stats has the aggregated major page faults count and this should be
575              * persistent across thread creation/termination. Thus use the value from this field.
576              */
577             .totalMajorFaults = pidStat->majorFaults,
578             .totalTasksCount = 1,
579             .ioBlockedTasksCount = pidStat->state == "D" ? 1 : 0,
580             .cpuCyclesByTid = {},
581     };
582 
583     // 3. Read memory usage summary.
584     if (mIsMemoryProfilingEnabled && !readSmapsRollup(pid, &processStats)) {
585         path = StringPrintf((mPath + kStatmFileFormat).c_str(), pid);
586         if (auto result = readPidStatmFile(path); !result.ok()) {
587             if (result.error().code() != READ_WARNING) {
588                 return Error() << result.error();
589             }
590             if (DEBUG) {
591                 ALOGD("%s", result.error().message().c_str());
592             }
593         } else {
594             processStats.rssKb = std::get<0>(*result) * mPageSizeKb;
595             // RSS pages - Shared pages = USS pages.
596             uint64_t ussKb = processStats.rssKb - (std::get<1>(*result) * mPageSizeKb);
597             // Check for overflow and correct the result.
598             processStats.ussKb = ussKb < processStats.rssKb ? ussKb : 0;
599         }
600     }
601 
602     // 4. Read per-thread stats.
603     std::string taskDir = StringPrintf((mPath + kTaskDirFormat).c_str(), pid);
604     bool didReadMainThread = false;
605     auto taskDirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(taskDir.c_str()), closedir);
606     for (dirent* tidDir = nullptr;
607          taskDirp != nullptr && (tidDir = readdir(taskDirp.get())) != nullptr;) {
608         pid_t tid = 0;
609         if (tidDir->d_type != DT_DIR || !ParseInt(tidDir->d_name, &tid)) {
610             continue;
611         }
612 
613         if (tid != pid) {
614             path = StringPrintf((taskDir + kStatFileFormat).c_str(), tid);
615             auto tidStat = readPidStatFile(path, mMillisPerClockTick);
616             if (!tidStat.ok()) {
617                 if (tidStat.error().code() != READ_WARNING) {
618                     return Error() << "Failed to read per-thread stat file: "
619                                    << tidStat.error().message().c_str();
620                 }
621                 /* Maybe the thread terminated before reading the file so skip this thread and
622                  * continue with scanning the next thread's stat.
623                  */
624                 continue;
625             }
626 
627             processStats.ioBlockedTasksCount += tidStat->state == "D" ? 1 : 0;
628             processStats.totalTasksCount += 1;
629         }
630 
631         if (!mIsTimeInStateEnabled) {
632             continue;
633         }
634 
635         // 5. Read time-in-state stats only when the corresponding file is accessible.
636         path = StringPrintf((taskDir + kTimeInStateFileFormat).c_str(), tid);
637         auto tidCpuCycles = readTimeInStateFile(path);
638         if (!tidCpuCycles.ok() || *tidCpuCycles <= 0) {
639             if (!tidCpuCycles.ok() && tidCpuCycles.error().code() != READ_WARNING) {
640                 return Error() << "Failed to read per-thread time_in_state file: "
641                                << tidCpuCycles.error().message().c_str();
642             }
643             // time_in_state file might not be supported by the Kernel (when the Kernel configs
644             // CPU_FREQ_STAT or CPU_FREQ_TIMES are not be enabled or the governor doesn't report the
645             // CPU transition states to the Kernel CPU frequency node). Or non-positive CPU cycles
646             // calculated. Or maybe the thread terminated before reading the file so skip this
647             // thread and continue with scanning the next thread's stat.
648             continue;
649         }
650 
651         processStats.totalCpuCycles = addUint64(processStats.totalCpuCycles, *tidCpuCycles);
652         processStats.cpuCyclesByTid[tid] = *tidCpuCycles;
653     }
654     return std::make_tuple(uid, processStats);
655 }
656 
readStatFileForPid(pid_t pid)657 Result<PidStat> UidProcStatsCollector::readStatFileForPid(pid_t pid) {
658     std::string path = StringPrintf(kProcPidStatFileFormat, pid);
659     return readPidStatFile(path, 1000 / sysconf(_SC_CLK_TCK));
660 }
661 
readPidStatusFileForPid(pid_t pid)662 Result<std::tuple<uid_t, pid_t>> UidProcStatsCollector::readPidStatusFileForPid(pid_t pid) {
663     std::string path = StringPrintf(kProcPidStatusFileFormat, pid);
664     return readPidStatusFile(path);
665 }
666 
readSmapsRollup(pid_t pid,ProcessStats * processStatsOut) const667 bool UidProcStatsCollector::readSmapsRollup(pid_t pid, ProcessStats* processStatsOut) const {
668     if (!mIsSmapsRollupSupported) {
669         return false;
670     }
671     MemUsage memUsage;
672     std::string path = StringPrintf((mPath + kSmapsRollupFileFormat).c_str(), pid);
673     if (!SmapsOrRollupFromFile(path, &memUsage)) {
674         return false;
675     }
676     processStatsOut->pssKb = memUsage.pss;
677     processStatsOut->rssKb = memUsage.rss;
678     processStatsOut->ussKb = memUsage.uss;
679     processStatsOut->swapPssKb = memUsage.swap_pss;
680     return memUsage.pss > 0 && memUsage.rss > 0 && memUsage.uss > 0;
681 }
682 
683 }  // namespace watchdog
684 }  // namespace automotive
685 }  // namespace android
686