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], &sectorsRead) ||
78         !ParseUint(fields[6], &diskStats.readTimeInMs) ||
79         !ParseUint(fields[7], &diskStats.numWritesCompleted) ||
80         !ParseUint(fields[8], &diskStats.numWritesMerged) ||
81         !ParseUint(fields[9], &sectorsWritten) ||
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