1 /*
2 * Copyright (C) 2018 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 #include <pixelstats/SysfsCollector.h>
18
19 #define LOG_TAG "pixelstats-vendor"
20
21 #include <android-base/file.h>
22 #include <android-base/parseint.h>
23 #include <android-base/strings.h>
24 #include <android/frameworks/stats/1.0/IStats.h>
25 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
26 #include <utils/Log.h>
27 #include <utils/StrongPointer.h>
28 #include <utils/Timers.h>
29
30 #include <sys/timerfd.h>
31 #include <string>
32
33 namespace android {
34 namespace hardware {
35 namespace google {
36 namespace pixel {
37
38 using android::sp;
39 using android::base::ReadFileToString;
40 using android::frameworks::stats::V1_0::ChargeCycles;
41 using android::frameworks::stats::V1_0::HardwareFailed;
42 using android::frameworks::stats::V1_0::IStats;
43 using android::frameworks::stats::V1_0::SlowIo;
44 using android::frameworks::stats::V1_0::SpeakerImpedance;
45 using android::frameworks::stats::V1_0::SpeechDspStat;
46 using android::frameworks::stats::V1_0::VendorAtom;
47 using android::hardware::google::pixel::PixelAtoms::BatteryCapacity;
48
SysfsCollector(const struct SysfsPaths & sysfs_paths)49 SysfsCollector::SysfsCollector(const struct SysfsPaths &sysfs_paths)
50 : kSlowioReadCntPath(sysfs_paths.SlowioReadCntPath),
51 kSlowioWriteCntPath(sysfs_paths.SlowioWriteCntPath),
52 kSlowioUnmapCntPath(sysfs_paths.SlowioUnmapCntPath),
53 kSlowioSyncCntPath(sysfs_paths.SlowioSyncCntPath),
54 kCycleCountBinsPath(sysfs_paths.CycleCountBinsPath),
55 kImpedancePath(sysfs_paths.ImpedancePath),
56 kCodecPath(sysfs_paths.CodecPath),
57 kCodec1Path(sysfs_paths.Codec1Path),
58 kSpeechDspPath(sysfs_paths.SpeechDspPath),
59 kBatteryCapacityCC(sysfs_paths.BatteryCapacityCC),
60 kBatteryCapacityVFSOC(sysfs_paths.BatteryCapacityVFSOC) {}
61
ReadFileToInt(const std::string & path,int * val)62 bool SysfsCollector::ReadFileToInt(const std::string &path, int *val) {
63 return ReadFileToInt(path.c_str(), val);
64 }
65
ReadFileToInt(const char * const path,int * val)66 bool SysfsCollector::ReadFileToInt(const char *const path, int *val) {
67 std::string file_contents;
68
69 if (!ReadFileToString(path, &file_contents)) {
70 ALOGE("Unable to read %s - %s", path, strerror(errno));
71 return false;
72 } else if (sscanf(file_contents.c_str(), "%d", val) != 1) {
73 ALOGE("Unable to convert %s to int - %s", path, strerror(errno));
74 return false;
75 }
76 return true;
77 }
78
79 /**
80 * Read the contents of kCycleCountBinsPath and report them via IStats HAL.
81 * The contents are expected to be N buckets total, the nth of which indicates the
82 * number of times battery %-full has been increased with the n/N% full bucket.
83 */
logBatteryChargeCycles()84 void SysfsCollector::logBatteryChargeCycles() {
85 std::string file_contents;
86 int val;
87 std::vector<int> charge_cycles;
88 if (kCycleCountBinsPath == nullptr || strlen(kCycleCountBinsPath) == 0) {
89 ALOGV("Battery charge cycle path not specified");
90 return;
91 }
92 if (!ReadFileToString(kCycleCountBinsPath, &file_contents)) {
93 ALOGE("Unable to read battery charge cycles %s - %s", kCycleCountBinsPath, strerror(errno));
94 return;
95 }
96
97 std::stringstream stream(file_contents);
98 while (stream >> val) {
99 charge_cycles.push_back(val);
100 }
101 ChargeCycles cycles;
102 cycles.cycleBucket = charge_cycles;
103
104 std::replace(file_contents.begin(), file_contents.end(), ' ', ',');
105 stats_->reportChargeCycles(cycles);
106 }
107
108 /**
109 * Check the codec for failures over the past 24hr.
110 */
logCodecFailed()111 void SysfsCollector::logCodecFailed() {
112 std::string file_contents;
113 if (kCodecPath == nullptr || strlen(kCodecPath) == 0) {
114 ALOGV("Audio codec path not specified");
115 return;
116 }
117 if (!ReadFileToString(kCodecPath, &file_contents)) {
118 ALOGE("Unable to read codec state %s - %s", kCodecPath, strerror(errno));
119 return;
120 }
121 if (file_contents == "0") {
122 return;
123 } else {
124 HardwareFailed failed = {.hardwareType = HardwareFailed::HardwareType::CODEC,
125 .hardwareLocation = 0,
126 .errorCode = HardwareFailed::HardwareErrorCode::COMPLETE};
127 stats_->reportHardwareFailed(failed);
128 }
129 }
130
131 /**
132 * Check the codec1 for failures over the past 24hr.
133 */
logCodec1Failed()134 void SysfsCollector::logCodec1Failed() {
135 std::string file_contents;
136 if (kCodec1Path == nullptr || strlen(kCodec1Path) == 0) {
137 ALOGV("Audio codec1 path not specified");
138 return;
139 }
140 if (!ReadFileToString(kCodec1Path, &file_contents)) {
141 ALOGE("Unable to read codec1 state %s - %s", kCodec1Path, strerror(errno));
142 return;
143 }
144 if (file_contents == "0") {
145 return;
146 } else {
147 ALOGE("%s report hardware fail", kCodec1Path);
148 HardwareFailed failed = {.hardwareType = HardwareFailed::HardwareType::CODEC,
149 .hardwareLocation = 1,
150 .errorCode = HardwareFailed::HardwareErrorCode::COMPLETE};
151 stats_->reportHardwareFailed(failed);
152 }
153 }
154
reportSlowIoFromFile(const char * path,const SlowIo::IoOperation & operation_s)155 void SysfsCollector::reportSlowIoFromFile(const char *path,
156 const SlowIo::IoOperation &operation_s) {
157 std::string file_contents;
158 if (path == nullptr || strlen(path) == 0) {
159 ALOGV("slow_io path not specified");
160 return;
161 }
162 if (!ReadFileToString(path, &file_contents)) {
163 ALOGE("Unable to read slowio %s - %s", path, strerror(errno));
164 return;
165 } else {
166 int32_t slow_io_count = 0;
167 if (sscanf(file_contents.c_str(), "%d", &slow_io_count) != 1) {
168 ALOGE("Unable to parse %s from file %s to int.", file_contents.c_str(), path);
169 } else if (slow_io_count > 0) {
170 SlowIo slowio = {.operation = operation_s, .count = slow_io_count};
171 stats_->reportSlowIo(slowio);
172 }
173 // Clear the stats
174 if (!android::base::WriteStringToFile("0", path, true)) {
175 ALOGE("Unable to clear SlowIO entry %s - %s", path, strerror(errno));
176 }
177 }
178 }
179
180 /**
181 * Check for slow IO operations.
182 */
logSlowIO()183 void SysfsCollector::logSlowIO() {
184 reportSlowIoFromFile(kSlowioReadCntPath, SlowIo::IoOperation::READ);
185 reportSlowIoFromFile(kSlowioWriteCntPath, SlowIo::IoOperation::WRITE);
186 reportSlowIoFromFile(kSlowioUnmapCntPath, SlowIo::IoOperation::UNMAP);
187 reportSlowIoFromFile(kSlowioSyncCntPath, SlowIo::IoOperation::SYNC);
188 }
189
190 /**
191 * Report the last-detected impedance of left & right speakers.
192 */
logSpeakerImpedance()193 void SysfsCollector::logSpeakerImpedance() {
194 std::string file_contents;
195 if (kImpedancePath == nullptr || strlen(kImpedancePath) == 0) {
196 ALOGV("Audio impedance path not specified");
197 return;
198 }
199 if (!ReadFileToString(kImpedancePath, &file_contents)) {
200 ALOGE("Unable to read impedance path %s", kImpedancePath);
201 return;
202 }
203
204 float left, right;
205 if (sscanf(file_contents.c_str(), "%g,%g", &left, &right) != 2) {
206 ALOGE("Unable to parse speaker impedance %s", file_contents.c_str());
207 return;
208 }
209 SpeakerImpedance left_obj = {.speakerLocation = 0,
210 .milliOhms = static_cast<int32_t>(left * 1000)};
211 SpeakerImpedance right_obj = {.speakerLocation = 1,
212 .milliOhms = static_cast<int32_t>(right * 1000)};
213 stats_->reportSpeakerImpedance(left_obj);
214 stats_->reportSpeakerImpedance(right_obj);
215 }
216
217 /**
218 * Report the Speech DSP state.
219 */
logSpeechDspStat()220 void SysfsCollector::logSpeechDspStat() {
221 std::string file_contents;
222 if (kSpeechDspPath == nullptr || strlen(kSpeechDspPath) == 0) {
223 ALOGV("Speech DSP path not specified");
224 return;
225 }
226 if (!ReadFileToString(kSpeechDspPath, &file_contents)) {
227 ALOGE("Unable to read speech dsp path %s", kSpeechDspPath);
228 return;
229 }
230
231 int32_t uptime = 0, downtime = 0, crashcount = 0, recovercount = 0;
232 if (sscanf(file_contents.c_str(), "%d,%d,%d,%d", &uptime, &downtime, &crashcount,
233 &recovercount) != 4) {
234 ALOGE("Unable to parse speech dsp stat %s", file_contents.c_str());
235 return;
236 }
237
238 ALOGD("SpeechDSP uptime %d downtime %d crashcount %d recovercount %d", uptime, downtime,
239 crashcount, recovercount);
240 SpeechDspStat dspstat = {.totalUptimeMillis = uptime,
241 .totalDowntimeMillis = downtime,
242 .totalCrashCount = crashcount,
243 .totalRecoverCount = recovercount};
244
245 stats_->reportSpeechDspStat(dspstat);
246 }
247
logBatteryCapacity()248 void SysfsCollector::logBatteryCapacity() {
249 std::string file_contents;
250 if (kBatteryCapacityCC == nullptr || strlen(kBatteryCapacityCC) == 0) {
251 ALOGV("Battery Capacity CC path not specified");
252 return;
253 }
254 if (kBatteryCapacityVFSOC == nullptr || strlen(kBatteryCapacityVFSOC) == 0) {
255 ALOGV("Battery Capacity VFSOC path not specified");
256 return;
257 }
258 int delta_cc_sum, delta_vfsoc_sum;
259 if (!ReadFileToInt(kBatteryCapacityCC, &delta_cc_sum) ||
260 !ReadFileToInt(kBatteryCapacityVFSOC, &delta_vfsoc_sum))
261 return;
262
263 // Load values array
264 std::vector<VendorAtom::Value> values(2);
265 VendorAtom::Value tmp;
266 tmp.intValue(delta_cc_sum);
267 values[BatteryCapacity::kDeltaCcSumFieldNumber - kVendorAtomOffset] = tmp;
268 tmp.intValue(delta_vfsoc_sum);
269 values[BatteryCapacity::kDeltaVfsocSumFieldNumber - kVendorAtomOffset] = tmp;
270
271 // Send vendor atom to IStats HAL
272 VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
273 .atomId = PixelAtoms::Ids::BATTERY_CAPACITY,
274 .values = values};
275 Return<void> ret = stats_->reportVendorAtom(event);
276 if (!ret.isOk())
277 ALOGE("Unable to report ChargeStats to Stats service");
278 }
279
logAll()280 void SysfsCollector::logAll() {
281 stats_ = IStats::tryGetService();
282 if (!stats_) {
283 ALOGE("Unable to connect to Stats service");
284 return;
285 }
286
287 logBatteryChargeCycles();
288 logCodecFailed();
289 logCodec1Failed();
290 logSlowIO();
291 logSpeakerImpedance();
292 logSpeechDspStat();
293 logBatteryCapacity();
294
295 stats_.clear();
296 }
297
298 /**
299 * Loop forever collecting stats from sysfs nodes and reporting them via
300 * IStats.
301 */
collect(void)302 void SysfsCollector::collect(void) {
303 int timerfd = timerfd_create(CLOCK_BOOTTIME, 0);
304 if (timerfd < 0) {
305 ALOGE("Unable to create timerfd - %s", strerror(errno));
306 return;
307 }
308
309 // Sleep for 30 seconds on launch to allow codec driver to load.
310 sleep(30);
311
312 // Collect first set of stats on boot.
313 logAll();
314
315 // Collect stats every 24hrs after.
316 struct itimerspec period;
317 const int kSecondsPerDay = 60 * 60 * 24;
318 period.it_interval.tv_sec = kSecondsPerDay;
319 period.it_interval.tv_nsec = 0;
320 period.it_value.tv_sec = kSecondsPerDay;
321 period.it_value.tv_nsec = 0;
322
323 if (timerfd_settime(timerfd, 0, &period, NULL)) {
324 ALOGE("Unable to set 24hr timer - %s", strerror(errno));
325 return;
326 }
327
328 while (1) {
329 int readval;
330 do {
331 char buf[8];
332 errno = 0;
333 readval = read(timerfd, buf, sizeof(buf));
334 } while (readval < 0 && errno == EINTR);
335 if (readval < 0) {
336 ALOGE("Timerfd error - %s\n", strerror(errno));
337 return;
338 }
339 logAll();
340 }
341 }
342
343 } // namespace pixel
344 } // namespace google
345 } // namespace hardware
346 } // namespace android
347