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