1 /*
2  * Copyright (C) 2020 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: BatteryCapacityFG"
18 
19 #include <log/log.h>
20 #include <time.h>
21 #include <utils/Timers.h>
22 #include <cmath>
23 
24 #include <android-base/file.h>
25 
26 #include <pixelstats/BatteryCapacityReporter.h>
27 #include <pixelstats/StatsHelper.h>
28 
29 #include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
30 
31 namespace android {
32 namespace hardware {
33 namespace google {
34 namespace pixel {
35 
36 using aidl::android::frameworks::stats::IStats;
37 using aidl::android::frameworks::stats::VendorAtom;
38 using aidl::android::frameworks::stats::VendorAtomValue;
39 using android::base::ReadFileToString;
40 using android::hardware::google::pixel::PixelAtoms::BatteryCapacityFG;
41 
42 #define ONE_HOUR_SECS (60 * 60)
43 
BatteryCapacityReporter()44 BatteryCapacityReporter::BatteryCapacityReporter() {
45     // Remove the need for a translation function/table, while removing the dependency on the
46     // generated <pixelatoms.pb.h> in BatteryCapacityReporter.h.
47     static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_UNKNOWN) ==
48                   static_cast<int>(BatteryCapacityFG::LOG_REASON_UNKNOWN));
49     static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_CONNECTED) ==
50                   static_cast<int>(BatteryCapacityFG::LOG_REASON_CONNECTED));
51     static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_DISCONNECTED) ==
52                   static_cast<int>(BatteryCapacityFG::LOG_REASON_DISCONNECTED));
53     static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_FULL_CHARGE) ==
54                   static_cast<int>(BatteryCapacityFG::LOG_REASON_FULL_CHARGE));
55     static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_PERCENT_SKIP) ==
56                   static_cast<int>(BatteryCapacityFG::LOG_REASON_PERCENT_SKIP));
57     static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_DIVERGING_FG) ==
58                   static_cast<int>(BatteryCapacityFG::LOG_REASON_DIVERGING_FG));
59 }
60 
checkAndReport(const std::shared_ptr<IStats> & stats_client,const std::string & path)61 void BatteryCapacityReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client,
62                                              const std::string &path) {
63     if (parse(path)) {
64         if (checkLogEvent()) {
65             reportEvent(stats_client);
66         }
67     }
68 }
69 
getTimeSecs(void)70 int64_t BatteryCapacityReporter::getTimeSecs(void) {
71     return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
72 }
73 
parse(const std::string & path)74 bool BatteryCapacityReporter::parse(const std::string &path) {
75     std::string batterySSOCContents;
76     if (!ReadFileToString(path, &batterySSOCContents)) {
77         ALOGE("Unable to read ssoc_details path: %s - %s", path.c_str(), strerror(errno));
78         return false;
79     }
80 
81     // Parse file. Example format:
82     // soc: l=97% gdf=97.72 uic=97.72 rl=97.72
83     // curve:[15.00 15.00][97.87 97.87][100.00 100.00]
84     // status: ct=1 rl=0 s=1
85     if (sscanf(batterySSOCContents.c_str(),
86                "soc: %*s gdf=%f %*s rl=%f\n"
87                "curve:[%*f %*f][%f %f][%*f %*f]\n"
88                "status: %*s %*s s=%d",
89                &gdf_, &ssoc_, &gdf_curve_, &ssoc_curve_, &status_) != 5) {
90         ALOGE("Unable to parse ssoc_details [%s] from file %s to int.", batterySSOCContents.c_str(),
91               path.c_str());
92         return false;
93     }
94 
95     return true;
96 }
97 
shouldReportEvent(void)98 bool BatteryCapacityReporter::shouldReportEvent(void) {
99     const int64_t current_time = getTimeSecs();
100     if (current_time == 0) {
101         ALOGE("Current boot time is zero!");
102         return false;
103     }
104 
105     /* Perform cleanup of events that are older than 1 hour */
106     for (int i = 0; i < MAX_LOG_EVENTS_PER_HOUR; i++) {
107         if (log_event_time_secs_[i] != 0 && /* Non-empty */
108             log_event_time_secs_[i] + ONE_HOUR_SECS < current_time) {
109             log_event_time_secs_[i] = 0;
110             num_events_in_last_hour_--;
111         }
112     }
113 
114     /* Log event if there hasn't been many events in the past hour */
115     if (num_events_in_last_hour_ < MAX_LOG_EVENTS_PER_HOUR) {
116         for (int i = 0; i < MAX_LOG_EVENTS_PER_HOUR; i++) {
117             if (log_event_time_secs_[i] == 0) { /* Empty */
118                 log_event_time_secs_[i] = current_time;
119                 num_events_in_last_hour_++;
120                 return true;
121             }
122         }
123     } else {
124         ALOGD("Too many log events in past hour; event ignored.");
125     }
126 
127     return false;
128 }
129 
130 /**
131  * @return true if a log should be reported, else false
132  */
checkLogEvent(void)133 bool BatteryCapacityReporter::checkLogEvent(void) {
134     LogReason log_reason = LOG_REASON_UNKNOWN;
135     if (status_previous_ != status_) {
136         // Handle nominal events
137 
138         if (status_ == SOC_STATUS_CONNECTED) {
139             log_reason = LOG_REASON_CONNECTED;
140 
141         } else if (status_ == SOC_STATUS_DISCONNECTED) {
142             log_reason = LOG_REASON_DISCONNECTED;
143 
144         } else if (status_ == SOC_STATUS_FULL) {
145             log_reason = LOG_REASON_FULL_CHARGE;
146         }
147 
148         status_previous_ = status_;
149 
150     } else {
151         // Handle abnormal events
152         const float diff = fabsf(ssoc_ - gdf_);
153 
154         if (fabsf(ssoc_ - ssoc_previous_) >= 2.0f) {
155             log_reason = LOG_REASON_PERCENT_SKIP;
156 
157             // Every +- 1% when above a 4% SOC difference (w/ timer)
158         } else if (static_cast<int>(round(ssoc_gdf_diff_previous_)) !=
159                            static_cast<int>(round(diff)) &&
160                    diff >= 4.0f) {
161             log_reason = LOG_REASON_DIVERGING_FG;
162 
163             ssoc_gdf_diff_previous_ = diff;
164         }
165     }
166     ssoc_previous_ = ssoc_;
167     log_reason_ = log_reason;
168 
169     if (log_reason != LOG_REASON_UNKNOWN) {
170         /* Found new log event! */
171         /* Check if we should actually report the event */
172         return shouldReportEvent();
173     }
174 
175     return false;
176 }
177 
reportEvent(const std::shared_ptr<IStats> & stats_client)178 void BatteryCapacityReporter::reportEvent(const std::shared_ptr<IStats> &stats_client) {
179     // Load values array
180     std::vector<VendorAtomValue> values(5);
181     VendorAtomValue tmp;
182     tmp.set<VendorAtomValue::intValue>(log_reason_);
183     values[BatteryCapacityFG::kCapacityLogReasonFieldNumber - kVendorAtomOffset] = tmp;
184     tmp.set<VendorAtomValue::floatValue>(gdf_);
185     values[BatteryCapacityFG::kCapacityGdfFieldNumber - kVendorAtomOffset] = tmp;
186     tmp.set<VendorAtomValue::floatValue>(ssoc_);
187     values[BatteryCapacityFG::kCapacitySsocFieldNumber - kVendorAtomOffset] = tmp;
188     tmp.set<VendorAtomValue::floatValue>(gdf_curve_);
189     values[BatteryCapacityFG::kCapacityGdfCurveFieldNumber - kVendorAtomOffset] = tmp;
190     tmp.set<VendorAtomValue::floatValue>(ssoc_curve_);
191     values[BatteryCapacityFG::kCapacitySsocCurveFieldNumber - kVendorAtomOffset] = tmp;
192 
193     // Send vendor atom to IStats HAL
194     VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
195                         .atomId = PixelAtoms::Atom::kFgCapacity,
196                         .values = std::move(values)};
197     const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
198     if (!ret.isOk())
199         ALOGE("Unable to report to IStats service");
200 }
201 
202 }  // namespace pixel
203 }  // namespace google
204 }  // namespace hardware
205 }  // namespace android
206