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 "pixelstats-uevent: ChargeStatsReporter"
18 
19 #include <android-base/file.h>
20 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
21 #include <log/log.h>
22 #include <pixelstats/ChargeStatsReporter.h>
23 #include <pixelstats/StatsHelper.h>
24 #include <time.h>
25 #include <utils/Timers.h>
26 
27 #include <cmath>
28 
29 namespace android {
30 namespace hardware {
31 namespace google {
32 namespace pixel {
33 
34 using aidl::android::frameworks::stats::IStats;
35 using aidl::android::frameworks::stats::VendorAtom;
36 using aidl::android::frameworks::stats::VendorAtomValue;
37 using android::base::ReadFileToString;
38 using android::base::WriteStringToFile;
39 using android::hardware::google::pixel::PixelAtoms::ChargeStats;
40 using android::hardware::google::pixel::PixelAtoms::VoltageTierStats;
41 
42 #define DURATION_FILTER_SECS 15
43 #define CHG_STATS_FMT0 "%d,%d,%d, %d,%d,%d,%d"
44 #define CHG_STATS_FMT1 "%d,%d,%d, %d,%d,%d,%d %d" /* AACR */
45 #define CHG_STATS_FMT2 "%d,%d,%d, %d,%d,%d,%d %d %d,%d" /* AACR + CSI */
46 
ChargeStatsReporter()47 ChargeStatsReporter::ChargeStatsReporter() {}
48 
getTimeSecs(void)49 int64_t ChargeStatsReporter::getTimeSecs(void) {
50     return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
51 }
52 
ReportChargeStats(const std::shared_ptr<IStats> & stats_client,const std::string line,const std::string wline_at,const std::string wline_ac,const std::string pca_line)53 void ChargeStatsReporter::ReportChargeStats(const std::shared_ptr<IStats> &stats_client,
54                                             const std::string line, const std::string wline_at,
55                                             const std::string wline_ac,
56                                             const std::string pca_line) {
57     int charge_stats_fields[] = {
58             ChargeStats::kAdapterTypeFieldNumber,
59             ChargeStats::kAdapterVoltageFieldNumber,
60             ChargeStats::kAdapterAmperageFieldNumber,
61             ChargeStats::kSsocInFieldNumber,
62             ChargeStats::kVoltageInFieldNumber,
63             ChargeStats::kSsocOutFieldNumber,
64             ChargeStats::kVoltageOutFieldNumber,
65             ChargeStats::kChargeCapacityFieldNumber,
66             ChargeStats::kCsiAggregateStatusFieldNumber,
67             ChargeStats::kCsiAggregateTypeFieldNumber,
68             ChargeStats::kAdapterCapabilities0FieldNumber,
69             ChargeStats::kAdapterCapabilities1FieldNumber,
70             ChargeStats::kAdapterCapabilities2FieldNumber,
71             ChargeStats::kAdapterCapabilities3FieldNumber,
72             ChargeStats::kAdapterCapabilities4FieldNumber,
73             ChargeStats::kReceiverState0FieldNumber,
74             ChargeStats::kReceiverState1FieldNumber,
75     };
76     const int32_t chg_fields_size = std::size(charge_stats_fields);
77     static_assert(chg_fields_size == 17, "Unexpected charge stats fields size");
78     const int32_t wlc_fields_size = 7;
79     std::vector<VendorAtomValue> values(chg_fields_size);
80     VendorAtomValue val;
81     int32_t i = 0, tmp[chg_fields_size] = {0}, fields_size = (chg_fields_size - wlc_fields_size);
82     int32_t pca_ac[2] = {0}, pca_rs[5] = {0};
83     std::string pdo_line, file_contents;
84     std::istringstream ss;
85 
86     ALOGD("processing %s", line.c_str());
87     if (sscanf(line.c_str(), CHG_STATS_FMT2, &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5],
88                &tmp[6], &tmp[7], &tmp[8], &tmp[9]) == 10) {
89         /*
90          * Charging Speed Indicator (CSI) the sum of the reasons that limit the charging speed in
91          * this charging session.
92          */
93     } else if (sscanf(line.c_str(), CHG_STATS_FMT1, &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
94                &tmp[5], &tmp[6], &tmp[7]) == 8) {
95         /*
96          * Age Adjusted Charge Rate (AACR) logs an additional battery capacity in order to determine
97          * the charge curve needed to minimize battery cycle life degradation, while also minimizing
98          * impact to the user.
99          */
100     } else if (sscanf(line.c_str(), CHG_STATS_FMT0, &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
101                       &tmp[5], &tmp[6]) != 7) {
102         ALOGE("Couldn't process %s", line.c_str());
103         return;
104     }
105 
106     if (!wline_at.empty()) {
107         int32_t ssoc_tmp = 0;
108         ALOGD("wlc: processing %s", wline_at.c_str());
109         if (sscanf(wline_at.c_str(), "A:%d", &ssoc_tmp) != 1) {
110             ALOGE("Couldn't process %s", wline_at.c_str());
111         } else {
112             tmp[0] = wireless_charge_stats_.TranslateSysModeToAtomValue(ssoc_tmp);
113             ALOGD("wlc: processing %s", wline_ac.c_str());
114             if (sscanf(wline_ac.c_str(), "D:%x,%x,%x,%x,%x, %x,%x", &tmp[10], &tmp[11], &tmp[12],
115                        &tmp[13], &tmp[14], &tmp[15], &tmp[16]) != 7)
116                 ALOGE("Couldn't process %s", wline_ac.c_str());
117             else
118                 fields_size = chg_fields_size; /* include wlc stats */
119         }
120     }
121 
122     if (!pca_line.empty()) {
123         ALOGD("pca: processing %s", pca_line.c_str());
124         if (sscanf(pca_line.c_str(), "D:%x,%x %x,%x,%x,%x,%x", &pca_ac[0], &pca_ac[1], &pca_rs[0],
125                    &pca_rs[1], &pca_rs[2], &pca_rs[3], &pca_rs[4]) != 7) {
126             ALOGE("Couldn't process %s", pca_line.c_str());
127         } else {
128             fields_size = chg_fields_size; /* include pca stats */
129             tmp[12] = pca_rs[2];
130             tmp[13] = pca_rs[3];
131             tmp[14] = pca_rs[4];
132             tmp[16] = pca_rs[1];
133             if (wline_at.empty()) {
134                 /* force adapter type to PPS when pca log is available, but not wlc */
135                 tmp[0] = PixelAtoms::ChargeStats::ADAPTER_TYPE_USB_PD_PPS;
136                 tmp[10] = pca_ac[0];
137                 tmp[11] = pca_ac[1];
138                 tmp[15] = pca_rs[0];
139             }
140         }
141     }
142 
143     if (ReadFileToString(kGChargerMetricsPath.c_str(), &file_contents)) {
144         ss.str(file_contents);
145         while (std::getline(ss, pdo_line)) {
146             if (sscanf(pdo_line.c_str(), "D:%x,%x,%x,%x,%x,%x,%x", &pca_ac[0], &pca_ac[1], &pca_rs[0],
147                    &pca_rs[1], &pca_rs[2], &pca_rs[3], &pca_rs[4]) != 7) {
148                 continue;
149             } else {
150                 ALOGD("processed %s, apdo:%d, pdo:%d", pdo_line.c_str(), pca_ac[1], pca_rs[4]);
151                 tmp[15] = pca_ac[1]; /* APDO */
152                 tmp[16] = pca_rs[4]; /* PDO */
153                 break;
154             }
155         }
156     }
157 
158     for (i = 0; i < fields_size; i++) {
159         val.set<VendorAtomValue::intValue>(tmp[i]);
160         values[charge_stats_fields[i] - kVendorAtomOffset] = val;
161     }
162 
163     VendorAtom event = {.reverseDomainName = "",
164                         .atomId = PixelAtoms::Atom::kChargeStats,
165                         .values = std::move(values)};
166     const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
167     if (!ret.isOk())
168         ALOGE("Unable to report ChargeStats to Stats service");
169 }
170 
ReportVoltageTierStats(const std::shared_ptr<IStats> & stats_client,const char * line,const bool has_wireless=false,const std::string & wfile_contents="")171 void ChargeStatsReporter::ReportVoltageTierStats(const std::shared_ptr<IStats> &stats_client,
172                                                  const char *line, const bool has_wireless = false,
173                                                  const std::string &wfile_contents = "") {
174     int voltage_tier_stats_fields[] = {
175             VoltageTierStats::kVoltageTierFieldNumber,
176             VoltageTierStats::kSocInFieldNumber, /* retrieved via ssoc_tmp */
177             VoltageTierStats::kCcInFieldNumber,
178             VoltageTierStats::kTempInFieldNumber,
179             VoltageTierStats::kTimeFastSecsFieldNumber,
180             VoltageTierStats::kTimeTaperSecsFieldNumber,
181             VoltageTierStats::kTimeOtherSecsFieldNumber,
182             VoltageTierStats::kTempMinFieldNumber,
183             VoltageTierStats::kTempAvgFieldNumber,
184             VoltageTierStats::kTempMaxFieldNumber,
185             VoltageTierStats::kIbattMinFieldNumber,
186             VoltageTierStats::kIbattAvgFieldNumber,
187             VoltageTierStats::kIbattMaxFieldNumber,
188             VoltageTierStats::kIclMinFieldNumber,
189             VoltageTierStats::kIclAvgFieldNumber,
190             VoltageTierStats::kIclMaxFieldNumber,
191             VoltageTierStats::kMinAdapterPowerOutFieldNumber,
192             VoltageTierStats::kTimeAvgAdapterPowerOutFieldNumber,
193             VoltageTierStats::kMaxAdapterPowerOutFieldNumber,
194             VoltageTierStats::kChargingOperatingPointFieldNumber};
195 
196     const int32_t vtier_fields_size = std::size(voltage_tier_stats_fields);
197     static_assert(vtier_fields_size == 20, "Unexpected voltage tier stats fields size");
198     const int32_t wlc_fields_size = 4;
199     std::vector<VendorAtomValue> values(vtier_fields_size);
200     VendorAtomValue val;
201     float ssoc_tmp;
202     int32_t i = 0, tmp[vtier_fields_size - 1] = {0}, /* ssoc_tmp is not saved in this array */
203             fields_size = (vtier_fields_size - wlc_fields_size);
204 
205     if (sscanf(line, "%d, %f,%d,%d, %d,%d,%d, %d,%d,%d, %d,%d,%d, %d,%d,%d", &tmp[0], &ssoc_tmp,
206                &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5], &tmp[6], &tmp[7], &tmp[8], &tmp[9],
207                &tmp[10], &tmp[11], &tmp[12], &tmp[13], &tmp[14]) != 16) {
208         /* If format isn't as expected, then ignore line on purpose */
209         return;
210     }
211 
212     if (has_wireless) {
213         wireless_charge_stats_.CalculateWirelessChargeStats(static_cast<int>(ssoc_tmp),
214                                                             wfile_contents);
215         tmp[15] = wireless_charge_stats_.pout_min_;
216         tmp[16] = wireless_charge_stats_.pout_avg_;
217         tmp[17] = wireless_charge_stats_.pout_max_;
218         tmp[18] = wireless_charge_stats_.of_freq_;
219         fields_size = vtier_fields_size; /* include wlc stats */
220     }
221 
222     ALOGD("VoltageTierStats: processed %s", line);
223     val.set<VendorAtomValue::intValue>(tmp[0]);
224     values[voltage_tier_stats_fields[0] - kVendorAtomOffset] = val;
225     val.set<VendorAtomValue::floatValue>(ssoc_tmp);
226     values[voltage_tier_stats_fields[1] - kVendorAtomOffset] = val;
227     for (i = 2; i < fields_size; i++) {
228         val.set<VendorAtomValue::intValue>(tmp[i - 1]);
229         values[voltage_tier_stats_fields[i] - kVendorAtomOffset] = val;
230     }
231 
232     VendorAtom event = {.reverseDomainName = "",
233                         .atomId = PixelAtoms::Atom::kVoltageTierStats,
234                         .values = std::move(values)};
235     const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
236     if (!ret.isOk())
237         ALOGE("Unable to report VoltageTierStats to Stats service");
238 }
239 
240 /**
241  * b/223664185
242  * Adds a rolling window filter to charge stats. If the time has expired, there will be a new log
243  * event.
244  *
245  * This helps ensure that we throttle stats even if there is an intermittent disconnect, while still
246  * retaining some stats on the disconnect.
247  */
shouldReportEvent(void)248 bool ChargeStatsReporter::shouldReportEvent(void) {
249     const int64_t current_time = getTimeSecs();
250     if (current_time == 0) {
251         ALOGE("Current boot time is zero!");
252         return false;
253     }
254 
255     if (log_event_time_secs_ == 0 || log_event_time_secs_ + DURATION_FILTER_SECS < current_time) {
256         log_event_time_secs_ = current_time;
257         return true;
258     }
259 
260     return false;
261 }
262 
checkAndReport(const std::shared_ptr<IStats> & stats_client,const std::string & path)263 void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client,
264                                          const std::string &path) {
265     std::string file_contents, line, wfile_contents, wline_at, wline_ac, pca_file_contents,
266             pca_line, thermal_file_contents, gcharger_file_contents, gdbatt_file_contents;
267     std::istringstream ss;
268     bool has_wireless, has_pca, has_thermal, has_gcharger, has_dual_batt;
269 
270     if (!ReadFileToString(path.c_str(), &file_contents)) {
271         ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno));
272         return;
273     }
274 
275     ss.str(file_contents);
276 
277     if (!std::getline(ss, line)) {
278         ALOGE("Unable to read first line");
279         return;
280     }
281 
282     if (!WriteStringToFile("0", path.c_str())) {
283         ALOGE("Couldn't clear %s - %s", path.c_str(), strerror(errno));
284     }
285 
286     if (!shouldReportEvent()) {
287         ALOGW("Too many log events; event ignored.");
288         return;
289     }
290 
291     has_pca = pca_charge_stats_.CheckPcaContentsAndAck(&pca_file_contents);
292     if (has_pca) {
293         std::istringstream pca_ss;
294 
295         pca_ss.str(pca_file_contents);
296         std::getline(pca_ss, pca_line);
297     }
298 
299     has_wireless = wireless_charge_stats_.CheckWirelessContentsAndAck(&wfile_contents);
300     if (has_wireless) {
301         std::istringstream wss;
302 
303         /* there are two lines in the head, A: ...(Adapter Type) and D: ...(Adapter Capabilities) */
304         wss.str(wfile_contents);
305         std::getline(wss, wline_at);
306         std::getline(wss, wline_ac);
307 
308         /* reset initial tier soc */
309         wireless_charge_stats_.tier_soc_ = 0;
310     }
311 
312     ReportChargeStats(stats_client, line, wline_at, wline_ac, pca_line);
313 
314     while (std::getline(ss, line)) {
315         ReportVoltageTierStats(stats_client, line.c_str(), has_wireless, wfile_contents);
316     }
317 
318     has_thermal = checkContentsAndAck(&thermal_file_contents, kThermalChargeMetricsPath);
319     if (has_thermal) {
320         std::istringstream wss;
321         wss.str(thermal_file_contents);
322         while (std::getline(wss, line)) {
323             ReportVoltageTierStats(stats_client, line.c_str());
324         }
325     }
326 
327     has_gcharger = checkContentsAndAck(&gcharger_file_contents, kGChargerMetricsPath);
328     if (has_gcharger) {
329         std::istringstream wss;
330         wss.str(gcharger_file_contents);
331         while (std::getline(wss, line)) {
332             ReportVoltageTierStats(stats_client, line.c_str());
333         }
334     }
335 
336     has_dual_batt = checkContentsAndAck(&gdbatt_file_contents, kGDualBattMetricsPath);
337     if (has_dual_batt) {
338         std::istringstream wss;
339         wss.str(gdbatt_file_contents);
340         while (std::getline(wss, line)) {
341             ReportVoltageTierStats(stats_client, line.c_str());
342         }
343     }
344 }
345 
checkContentsAndAck(std::string * file_contents,const std::string & path)346 bool ChargeStatsReporter::checkContentsAndAck(std::string *file_contents, const std::string &path) {
347     if (!ReadFileToString(path.c_str(), file_contents)) {
348         return false;
349     }
350 
351     if (!WriteStringToFile("0", path.c_str())) {
352         ALOGE("Couldn't clear %s - %s", path.c_str(), strerror(errno));
353         return false;
354     }
355     return true;
356 }
357 
358 }  // namespace pixel
359 }  // namespace google
360 }  // namespace hardware
361 }  // namespace android
362