1 /*
2  * Copyright (c) 2022, 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 
19 #include "UidCpuStatsCollector.h"
20 
21 #include <android-base/file.h>
22 #include <android-base/parseint.h>
23 #include <android-base/strings.h>
24 
25 #include <inttypes.h>
26 
27 #include <string>
28 #include <unordered_map>
29 #include <vector>
30 
31 namespace android {
32 namespace automotive {
33 namespace watchdog {
34 
35 namespace {
36 
37 using ::android::base::EndsWith;
38 using ::android::base::Error;
39 using ::android::base::ParseInt;
40 using ::android::base::ReadFileToString;
41 using ::android::base::Result;
42 using ::android::base::Split;
43 
44 enum class ReadError : int {
45     ERR_INVALID_FILE,
46     ERR_FILE_OPEN_READ,
47     NUM_ERRORS,
48 };
49 
50 /**
51  * Returns a map of CPU time in milliseconds spent by each UID since system boot up.
52  *
53  * /proc/uid_cputime/show_uid_stat file format:
54  * <uid>: <user_time_micro_seconds> <system_time_micro_seconds>
55  */
readUidCpuTimeFile(const std::string & path)56 Result<std::unordered_map<uid_t, int64_t>> readUidCpuTimeFile(const std::string& path) {
57     std::string buffer;
58     if (!ReadFileToString(path, &buffer)) {
59         return Error(static_cast<int>(ReadError::ERR_FILE_OPEN_READ))
60                 << "ReadFileToString failed for " << path;
61     }
62     std::unordered_map<uid_t, int64_t> cpuTimeMillisByUid;
63     std::vector<std::string> lines = Split(buffer, "\n");
64     for (size_t i = 0; i < lines.size(); i++) {
65         if (lines[i].empty()) {
66             continue;
67         }
68         const std::string delimiter = " ";
69         std::vector<std::string> elements = Split(lines[i], delimiter);
70         if (elements.size() < 3) {
71             return Error(static_cast<int>(ReadError::ERR_INVALID_FILE))
72                     << "Line \"" << lines[i] << "\" doesn't contain the delimiter \"" << delimiter
73                     << "\" in file " << path;
74         }
75         if (EndsWith(elements[0], ":")) {
76             elements[0].pop_back();
77         }
78         int64_t uid = -1;
79         int64_t userCpuTimeUs = 0;
80         int64_t systemCpuTimeUs = 0;
81         if (!ParseInt(elements[0], &uid) || !ParseInt(elements[1], &userCpuTimeUs) ||
82             !ParseInt(elements[2], &systemCpuTimeUs)) {
83             return Error(static_cast<int>(ReadError::ERR_INVALID_FILE))
84                     << "Failed to parse line from file: " << path << ", error: line " << lines[i]
85                     << " has invalid format";
86         }
87         if (cpuTimeMillisByUid.find(uid) != cpuTimeMillisByUid.end()) {
88             return Error(static_cast<int>(ReadError::ERR_INVALID_FILE))
89                     << "Duplicate " << uid << " line: \"" << lines[i] << "\" in file " << path;
90         }
91         // Store CPU time as milliseconds
92         cpuTimeMillisByUid[uid] = userCpuTimeUs / 1000 + systemCpuTimeUs / 1000;
93     }
94     if (cpuTimeMillisByUid.empty()) {
95         return Error(static_cast<int>(ReadError::ERR_INVALID_FILE)) << "Empty file: " << path;
96     }
97     return cpuTimeMillisByUid;
98 }
99 
100 }  // namespace
101 
collect()102 Result<void> UidCpuStatsCollector::collect() {
103     Mutex::Autolock lock(mMutex);
104     if (!mEnabled) {
105         return Error() << "Can not access: " << mPath;
106     }
107     auto cpuTimeMillisByUid = readUidCpuTimeFile(mPath);
108     if (!cpuTimeMillisByUid.ok()) {
109         return Error(cpuTimeMillisByUid.error().code())
110                 << "Failed to read top-level per UID CPU time file " << mPath << ": "
111                 << cpuTimeMillisByUid.error().message();
112     }
113 
114     mDeltaStats.clear();
115     for (const auto& [uid, cpuTime] : *cpuTimeMillisByUid) {
116         if (cpuTime == 0) {
117             continue;
118         }
119         int64_t deltaCpuTime = cpuTime;
120         if (const auto& it = mLatestStats.find(uid);
121             it != mLatestStats.end() && it->second <= deltaCpuTime) {
122             deltaCpuTime -= it->second;
123             if (deltaCpuTime == 0) {
124                 continue;
125             }
126         }
127         mDeltaStats[uid] = deltaCpuTime;
128     }
129     mLatestStats = std::move(*cpuTimeMillisByUid);
130     return {};
131 }
132 
133 }  // namespace watchdog
134 }  // namespace automotive
135 }  // namespace android
136