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