1 /*
2 * Copyright (c) 2021, 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 "ProcDiskStats.h"
20
21 #include <android-base/file.h>
22 #include <android-base/parseint.h>
23 #include <log/log.h>
24
25 #include <inttypes.h>
26
27 #include <algorithm>
28 #include <limits>
29
30 namespace android {
31 namespace automotive {
32 namespace watchdog {
33
34 using ::android::base::Error;
35 using ::android::base::Join;
36 using ::android::base::ParseInt;
37 using ::android::base::ParseUint;
38 using ::android::base::ReadFileToString;
39 using ::android::base::Result;
40 using ::android::base::Split;
41 using ::android::base::StartsWith;
42 using ::android::base::StringPrintf;
43 using ::android::base::StringReplace;
44 using ::android::base::Trim;
45
46 namespace {
47
48 /*
49 * Format of a line in /proc/diskstats.
50 * <major #> <minor #> <device name> <# of reads completed> <# of reads merged> <# of sectors read>\
51 * <# of milliseconds spent reading> <# of writes completed> <# of writes merged> \
52 * <# of sectors written> <# of milliseconds spent writing> <# of I/Os currently in progress> \
53 * <# of milliseconds spent doing I/Os> <weighted # of milliseconds spent doing I/Os> \
54 * <# of discards completed> <# of discards merged> <# of sectors discarded> \
55 * <# of milliseconds spent discarding> <# of flush requests completed> \
56 * <# of milliseconds spent flushing>
57 *
58 * All fields except the duration fields after the device name field are reported as unsigned long
59 * values. Duration fields are reported as unsigned int values. The reported values may overflow and
60 * the application should deal with it.
61 */
parseDiskStatsLine(const std::string & line)62 Result<DiskStats> parseDiskStatsLine(const std::string& line) {
63 std::vector<std::string> fields = Split(Trim(line), " ");
64 for (auto it = fields.begin(); it != fields.end();) {
65 if (it->empty() || std::all_of(it->begin(), it->end(), isspace)) {
66 it = fields.erase(it);
67 continue;
68 }
69 ++it;
70 }
71 uint64_t sectorsRead = 0;
72 uint64_t sectorsWritten = 0;
73 DiskStats diskStats;
74 if (fields.size() < 14 || !ParseInt(fields[0], &diskStats.major) ||
75 !ParseInt(fields[1], &diskStats.minor) ||
76 !ParseUint(fields[3], &diskStats.numReadsCompleted) ||
77 !ParseUint(fields[4], &diskStats.numReadsMerged) || !ParseUint(fields[5], §orsRead) ||
78 !ParseUint(fields[6], &diskStats.readTimeInMs) ||
79 !ParseUint(fields[7], &diskStats.numWritesCompleted) ||
80 !ParseUint(fields[8], &diskStats.numWritesMerged) ||
81 !ParseUint(fields[9], §orsWritten) ||
82 !ParseUint(fields[10], &diskStats.writeTimeInMs) ||
83 !ParseUint(fields[12], &diskStats.totalIoTimeInMs) ||
84 !ParseUint(fields[13], &diskStats.weightedTotalIoTimeInMs)) {
85 return Error() << "Failed to parse from line fields: '" << Join(fields, "', '") << "'";
86 }
87 diskStats.deviceName = fields[2];
88 // Kernel sector size is 512 bytes. Therefore, 2 sectors == 1 KiB.
89 diskStats.numKibRead = sectorsRead / 2;
90 diskStats.numKibWritten = sectorsWritten / 2;
91 if (fields.size() >= 20 &&
92 (!ParseUint(fields[18], &diskStats.numFlushCompleted) ||
93 !ParseUint(fields[19], &diskStats.flushTimeInMs))) {
94 return Error() << "Failed to parse flush stats from line fields: '" << Join(fields, "', '")
95 << "'";
96 }
97 return diskStats;
98 }
99
readDiskStatsFile(const std::string & path)100 Result<ProcDiskStats::PerPartitionDiskStats> readDiskStatsFile(const std::string& path) {
101 std::string buffer;
102 if (!ReadFileToString(path, &buffer)) {
103 return Error() << "ReadFileToString failed";
104 }
105 std::vector<std::string> lines = Split(std::move(buffer), "\n");
106 if (lines.empty()) {
107 return Error() << "File is empty";
108 }
109 ProcDiskStats::PerPartitionDiskStats perPartitionDiskStats;
110 for (const auto& line : lines) {
111 if (line.empty()) {
112 continue;
113 }
114 if (auto diskStats = parseDiskStatsLine(line); !diskStats.ok()) {
115 return diskStats.error();
116 } else if (recordStatsForDevice(diskStats->deviceName)) {
117 perPartitionDiskStats.emplace(std::move(*diskStats));
118 }
119 }
120 if (perPartitionDiskStats.empty()) {
121 return Error() << "No valid partition disk stats available";
122 }
123 return perPartitionDiskStats;
124 }
125
diffPerPartitionDiskStats(const ProcDiskStats::PerPartitionDiskStats & minuend,const ProcDiskStats::PerPartitionDiskStats & subtrahend)126 ProcDiskStats::PerPartitionDiskStats diffPerPartitionDiskStats(
127 const ProcDiskStats::PerPartitionDiskStats& minuend,
128 const ProcDiskStats::PerPartitionDiskStats& subtrahend) {
129 ProcDiskStats::PerPartitionDiskStats diff;
130 for (const auto& minuendStats : minuend) {
131 if (auto subtrahendStats = subtrahend.find(minuendStats);
132 subtrahendStats != subtrahend.end()) {
133 auto diffStats = minuendStats;
134 diffStats -= *subtrahendStats;
135 diff.emplace(std::move(diffStats));
136 } else {
137 diff.emplace(minuendStats);
138 }
139 }
140 return diff;
141 }
142
aggregateSystemWideDiskStats(const ProcDiskStats::PerPartitionDiskStats && perPartitionDiskStats)143 DiskStats aggregateSystemWideDiskStats(
144 const ProcDiskStats::PerPartitionDiskStats&& perPartitionDiskStats) {
145 DiskStats systemWideStats;
146 for (const auto& stats : perPartitionDiskStats) {
147 systemWideStats += stats;
148 }
149 return systemWideStats;
150 }
151
152 } // namespace
153
operator -=(const DiskStats & rhs)154 DiskStats& DiskStats::operator-=(const DiskStats& rhs) {
155 auto diff = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
156 // Disk stats may overflow so handling it here.
157 return l >= r ? (l - r) : ((std::numeric_limits<uint64_t>::max() - r) + l);
158 };
159 numReadsCompleted = diff(numReadsCompleted, rhs.numReadsCompleted);
160 numReadsMerged = diff(numReadsMerged, rhs.numReadsMerged);
161 numKibRead = diff(numKibRead, rhs.numKibRead);
162 readTimeInMs = diff(readTimeInMs, rhs.readTimeInMs);
163 numWritesCompleted = diff(numWritesCompleted, rhs.numWritesCompleted);
164 numWritesMerged = diff(numWritesMerged, rhs.numWritesMerged);
165 numKibWritten = diff(numKibWritten, rhs.numKibWritten);
166 writeTimeInMs = diff(writeTimeInMs, rhs.writeTimeInMs);
167 totalIoTimeInMs = diff(totalIoTimeInMs, rhs.totalIoTimeInMs);
168 weightedTotalIoTimeInMs = diff(weightedTotalIoTimeInMs, rhs.weightedTotalIoTimeInMs);
169 numFlushCompleted = diff(numFlushCompleted, rhs.numFlushCompleted);
170 flushTimeInMs = diff(flushTimeInMs, rhs.flushTimeInMs);
171 return *this;
172 }
173
operator +=(const DiskStats & rhs)174 DiskStats& DiskStats::operator+=(const DiskStats& rhs) {
175 auto sum = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
176 return (std::numeric_limits<uint64_t>::max() - l) > r
177 ? (l + r)
178 : std::numeric_limits<uint64_t>::max();
179 };
180 numReadsCompleted = sum(numReadsCompleted, rhs.numReadsCompleted);
181 numReadsMerged = sum(numReadsMerged, rhs.numReadsMerged);
182 numKibRead = sum(numKibRead, rhs.numKibRead);
183 readTimeInMs = sum(readTimeInMs, rhs.readTimeInMs);
184 numWritesCompleted = sum(numWritesCompleted, rhs.numWritesCompleted);
185 numWritesMerged = sum(numWritesMerged, rhs.numWritesMerged);
186 numKibWritten = sum(numKibWritten, rhs.numKibWritten);
187 writeTimeInMs = sum(writeTimeInMs, rhs.writeTimeInMs);
188 totalIoTimeInMs = sum(totalIoTimeInMs, rhs.totalIoTimeInMs);
189 weightedTotalIoTimeInMs = sum(weightedTotalIoTimeInMs, rhs.weightedTotalIoTimeInMs);
190 numFlushCompleted = sum(numFlushCompleted, rhs.numFlushCompleted);
191 flushTimeInMs = sum(flushTimeInMs, rhs.flushTimeInMs);
192 return *this;
193 }
194
operator ()(const DiskStats & stats) const195 size_t DiskStats::HashByPartition::operator()(const DiskStats& stats) const {
196 return std::hash<std::string>{}(
197 StringPrintf("%d.%d.%s", stats.major, stats.minor, stats.deviceName.c_str()));
198 }
199
operator ()(const DiskStats & lhs,const DiskStats & rhs) const200 bool DiskStats::EqualByPartition::operator()(const DiskStats& lhs, const DiskStats& rhs) const {
201 return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.deviceName == rhs.deviceName;
202 }
203
collect()204 Result<void> ProcDiskStats::collect() {
205 if (!kEnabled) {
206 return Error() << "Failed to access " << kPath;
207 }
208
209 Mutex::Autolock lock(mMutex);
210 if (auto latestPerPartitionDiskStats = readDiskStatsFile(kPath);
211 !latestPerPartitionDiskStats.ok()) {
212 return Error() << "Failed to read per-partition disk stats from '" << kPath
213 << "': " << latestPerPartitionDiskStats.error();
214 } else {
215 mDeltaSystemWideDiskStats = aggregateSystemWideDiskStats(
216 diffPerPartitionDiskStats(*latestPerPartitionDiskStats,
217 mLatestPerPartitionDiskStats));
218 mLatestPerPartitionDiskStats = *latestPerPartitionDiskStats;
219 }
220 return {};
221 }
222
223 } // namespace watchdog
224 } // namespace automotive
225 } // namespace android
226