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 "ProcDiskStatsCollector.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], &sectorsRead) ||
78         !ParseUint(fields[6], &diskStats.readTimeInMillis) ||
79         !ParseUint(fields[7], &diskStats.numWritesCompleted) ||
80         !ParseUint(fields[8], &diskStats.numWritesMerged) ||
81         !ParseUint(fields[9], &sectorsWritten) ||
82         !ParseUint(fields[10], &diskStats.writeTimeInMillis) ||
83         !ParseUint(fields[12], &diskStats.totalIoTimeInMillis) ||
84         !ParseUint(fields[13], &diskStats.weightedTotalIoTimeInMillis)) {
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.flushTimeInMillis))) {
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<ProcDiskStatsCollector::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     ProcDiskStatsCollector::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 ProcDiskStatsCollector::PerPartitionDiskStats & minuend,const ProcDiskStatsCollector::PerPartitionDiskStats & subtrahend)126 ProcDiskStatsCollector::PerPartitionDiskStats diffPerPartitionDiskStats(
127         const ProcDiskStatsCollector::PerPartitionDiskStats& minuend,
128         const ProcDiskStatsCollector::PerPartitionDiskStats& subtrahend) {
129     ProcDiskStatsCollector::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 ProcDiskStatsCollector::PerPartitionDiskStats && perPartitionDiskStats)143 DiskStats aggregateSystemWideDiskStats(
144         const ProcDiskStatsCollector::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     readTimeInMillis = diff(readTimeInMillis, rhs.readTimeInMillis);
163     numWritesCompleted = diff(numWritesCompleted, rhs.numWritesCompleted);
164     numWritesMerged = diff(numWritesMerged, rhs.numWritesMerged);
165     numKibWritten = diff(numKibWritten, rhs.numKibWritten);
166     writeTimeInMillis = diff(writeTimeInMillis, rhs.writeTimeInMillis);
167     totalIoTimeInMillis = diff(totalIoTimeInMillis, rhs.totalIoTimeInMillis);
168     weightedTotalIoTimeInMillis =
169             diff(weightedTotalIoTimeInMillis, rhs.weightedTotalIoTimeInMillis);
170     numFlushCompleted = diff(numFlushCompleted, rhs.numFlushCompleted);
171     flushTimeInMillis = diff(flushTimeInMillis, rhs.flushTimeInMillis);
172     return *this;
173 }
174 
operator +=(const DiskStats & rhs)175 DiskStats& DiskStats::operator+=(const DiskStats& rhs) {
176     auto sum = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
177         return (std::numeric_limits<uint64_t>::max() - l) > r
178                 ? (l + r)
179                 : std::numeric_limits<uint64_t>::max();
180     };
181     numReadsCompleted = sum(numReadsCompleted, rhs.numReadsCompleted);
182     numReadsMerged = sum(numReadsMerged, rhs.numReadsMerged);
183     numKibRead = sum(numKibRead, rhs.numKibRead);
184     readTimeInMillis = sum(readTimeInMillis, rhs.readTimeInMillis);
185     numWritesCompleted = sum(numWritesCompleted, rhs.numWritesCompleted);
186     numWritesMerged = sum(numWritesMerged, rhs.numWritesMerged);
187     numKibWritten = sum(numKibWritten, rhs.numKibWritten);
188     writeTimeInMillis = sum(writeTimeInMillis, rhs.writeTimeInMillis);
189     totalIoTimeInMillis = sum(totalIoTimeInMillis, rhs.totalIoTimeInMillis);
190     weightedTotalIoTimeInMillis = sum(weightedTotalIoTimeInMillis, rhs.weightedTotalIoTimeInMillis);
191     numFlushCompleted = sum(numFlushCompleted, rhs.numFlushCompleted);
192     flushTimeInMillis = sum(flushTimeInMillis, rhs.flushTimeInMillis);
193     return *this;
194 }
195 
operator ()(const DiskStats & stats) const196 size_t DiskStats::HashByPartition::operator()(const DiskStats& stats) const {
197     return std::hash<std::string>{}(
198             StringPrintf("%d.%d.%s", stats.major, stats.minor, stats.deviceName.c_str()));
199 }
200 
operator ()(const DiskStats & lhs,const DiskStats & rhs) const201 bool DiskStats::EqualByPartition::operator()(const DiskStats& lhs, const DiskStats& rhs) const {
202     return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.deviceName == rhs.deviceName;
203 }
204 
collect()205 Result<void> ProcDiskStatsCollector::collect() {
206     if (!mEnabled) {
207         return Error() << "Failed to access " << kPath;
208     }
209 
210     Mutex::Autolock lock(mMutex);
211     if (auto latestPerPartitionDiskStats = readDiskStatsFile(kPath);
212         !latestPerPartitionDiskStats.ok()) {
213         return Error() << "Failed to read per-partition disk stats from '" << kPath
214                        << "': " << latestPerPartitionDiskStats.error();
215     } else {
216         mDeltaSystemWideDiskStats = aggregateSystemWideDiskStats(
217                 diffPerPartitionDiskStats(*latestPerPartitionDiskStats,
218                                           mLatestPerPartitionDiskStats));
219         mLatestPerPartitionDiskStats = *latestPerPartitionDiskStats;
220     }
221     return {};
222 }
223 
224 }  // namespace watchdog
225 }  // namespace automotive
226 }  // namespace android
227