1
2 /*
3 * Copyright (C) 2022 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 #define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
19
20 #include "power_files.h"
21
22 #include <android-base/file.h>
23 #include <android-base/logging.h>
24 #include <android-base/stringprintf.h>
25 #include <android-base/strings.h>
26 #include <dirent.h>
27 #include <utils/Trace.h>
28
29 namespace aidl {
30 namespace android {
31 namespace hardware {
32 namespace thermal {
33 namespace implementation {
34
35 constexpr std::string_view kDeviceType("iio:device");
36 constexpr std::string_view kIioRootDir("/sys/bus/iio/devices");
37 constexpr std::string_view kEnergyValueNode("energy_value");
38
39 using ::android::base::ReadFileToString;
40 using ::android::base::StringPrintf;
41
42 namespace {
calculateAvgPower(std::string_view power_rail,const PowerSample & last_sample,const PowerSample & curr_sample,float * avg_power)43 bool calculateAvgPower(std::string_view power_rail, const PowerSample &last_sample,
44 const PowerSample &curr_sample, float *avg_power) {
45 *avg_power = NAN;
46 const auto duration = curr_sample.duration - last_sample.duration;
47 const auto deltaEnergy = curr_sample.energy_counter - last_sample.energy_counter;
48 if (!last_sample.duration) {
49 LOG(VERBOSE) << "Power rail " << power_rail.data()
50 << ": all power samples have not been collected yet";
51 } else if (duration <= 0 || deltaEnergy < 0) {
52 LOG(ERROR) << "Power rail " << power_rail.data() << " is invalid: duration = " << duration
53 << ", deltaEnergy = " << deltaEnergy;
54 return false;
55 } else {
56 *avg_power = static_cast<float>(deltaEnergy) / static_cast<float>(duration);
57 LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << *avg_power
58 << ", duration = " << duration << ", deltaEnergy = " << deltaEnergy;
59 }
60 return true;
61 }
62 } // namespace
63
registerPowerRailsToWatch(const Json::Value & config)64 bool PowerFiles::registerPowerRailsToWatch(const Json::Value &config) {
65 if (!ParsePowerRailInfo(config, &power_rail_info_map_)) {
66 LOG(ERROR) << "Failed to parse power rail info config";
67 return false;
68 }
69
70 if (!power_rail_info_map_.size()) {
71 LOG(INFO) << " No power rail info config found";
72 return true;
73 }
74
75 if (!findEnergySourceToWatch()) {
76 LOG(ERROR) << "Cannot find energy source";
77 return false;
78 }
79
80 if (!energy_info_map_.size() && !updateEnergyValues()) {
81 LOG(ERROR) << "Faield to update energy info";
82 return false;
83 }
84
85 for (const auto &power_rail_info_pair : power_rail_info_map_) {
86 std::vector<std::queue<PowerSample>> power_history;
87 if (!power_rail_info_pair.second.power_sample_count ||
88 power_rail_info_pair.second.power_sample_delay == std::chrono::milliseconds::max()) {
89 continue;
90 }
91
92 PowerSample power_sample = {
93 .energy_counter = 0,
94 .duration = 0,
95 };
96
97 if (power_rail_info_pair.second.virtual_power_rail_info != nullptr &&
98 power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size()) {
99 for (size_t i = 0;
100 i < power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size();
101 ++i) {
102 if (!energy_info_map_.count(power_rail_info_pair.second.virtual_power_rail_info
103 ->linked_power_rails[i])) {
104 LOG(ERROR) << " Could not find energy source "
105 << power_rail_info_pair.second.virtual_power_rail_info
106 ->linked_power_rails[i];
107 return false;
108 }
109 power_history.emplace_back(std::queue<PowerSample>());
110 for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
111 power_history[i].emplace(power_sample);
112 }
113 }
114 } else {
115 if (energy_info_map_.count(power_rail_info_pair.first)) {
116 power_history.emplace_back(std::queue<PowerSample>());
117 for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
118 power_history[0].emplace(power_sample);
119 }
120 } else {
121 LOG(ERROR) << "Could not find energy source " << power_rail_info_pair.first;
122 return false;
123 }
124 }
125
126 if (power_history.size()) {
127 power_status_map_[power_rail_info_pair.first] = {
128 .power_history = power_history,
129 .last_update_time = boot_clock::time_point::min(),
130 .last_updated_avg_power = NAN,
131 };
132 } else {
133 LOG(ERROR) << "power history size is zero";
134 return false;
135 }
136 LOG(INFO) << "Successfully to register power rail " << power_rail_info_pair.first;
137 }
138
139 power_status_log_ = {.prev_log_time = boot_clock::now(),
140 .prev_energy_info_map = energy_info_map_};
141 return true;
142 }
143
findEnergySourceToWatch(void)144 bool PowerFiles::findEnergySourceToWatch(void) {
145 std::string devicePath;
146
147 if (energy_path_set_.size()) {
148 return true;
149 }
150
151 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(kIioRootDir.data()), closedir);
152 if (!dir) {
153 PLOG(ERROR) << "Error opening directory" << kIioRootDir;
154 return false;
155 }
156
157 // Find any iio:devices that support energy_value
158 while (struct dirent *ent = readdir(dir.get())) {
159 std::string devTypeDir = ent->d_name;
160 if (devTypeDir.find(kDeviceType) != std::string::npos) {
161 devicePath = StringPrintf("%s/%s", kIioRootDir.data(), devTypeDir.data());
162 std::string deviceEnergyContent;
163
164 if (!ReadFileToString(StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()),
165 &deviceEnergyContent)) {
166 } else if (deviceEnergyContent.size()) {
167 energy_path_set_.emplace(
168 StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()));
169 }
170 }
171 }
172
173 if (!energy_path_set_.size()) {
174 return false;
175 }
176
177 return true;
178 }
179
updateEnergyValues(void)180 bool PowerFiles::updateEnergyValues(void) {
181 std::string deviceEnergyContent;
182 std::string deviceEnergyContents;
183 std::string line;
184
185 ATRACE_CALL();
186 for (const auto &path : energy_path_set_) {
187 if (!::android::base::ReadFileToString(path, &deviceEnergyContent)) {
188 LOG(ERROR) << "Failed to read energy content from " << path;
189 return false;
190 } else {
191 deviceEnergyContents.append(deviceEnergyContent);
192 }
193 }
194
195 std::istringstream energyData(deviceEnergyContents);
196
197 while (std::getline(energyData, line)) {
198 /* Read rail energy */
199 uint64_t energy_counter = 0;
200 uint64_t duration = 0;
201
202 /* Format example: CH3(T=358356)[S2M_VDD_CPUCL2], 761330 */
203 auto start_pos = line.find("T=");
204 auto end_pos = line.find(')');
205 if (start_pos != std::string::npos) {
206 duration =
207 strtoul(line.substr(start_pos + 2, end_pos - start_pos - 2).c_str(), NULL, 10);
208 } else {
209 continue;
210 }
211
212 start_pos = line.find(")[");
213 end_pos = line.find(']');
214 std::string railName;
215 if (start_pos != std::string::npos) {
216 railName = line.substr(start_pos + 2, end_pos - start_pos - 2);
217 } else {
218 continue;
219 }
220
221 start_pos = line.find("],");
222 if (start_pos != std::string::npos) {
223 energy_counter = strtoul(line.substr(start_pos + 2).c_str(), NULL, 10);
224 } else {
225 continue;
226 }
227
228 energy_info_map_[railName] = {
229 .energy_counter = energy_counter,
230 .duration = duration,
231 };
232 }
233
234 return true;
235 }
236
updateAveragePower(std::string_view power_rail,std::queue<PowerSample> * power_history)237 float PowerFiles::updateAveragePower(std::string_view power_rail,
238 std::queue<PowerSample> *power_history) {
239 float avg_power = NAN;
240 if (!energy_info_map_.count(power_rail.data())) {
241 LOG(ERROR) << " Could not find power rail " << power_rail.data();
242 return avg_power;
243 }
244 const auto last_sample = power_history->front();
245 const auto curr_sample = energy_info_map_.at(power_rail.data());
246 if (calculateAvgPower(power_rail, last_sample, curr_sample, &avg_power)) {
247 power_history->pop();
248 power_history->push(curr_sample);
249 }
250 return avg_power;
251 }
252
updatePowerRail(std::string_view power_rail)253 float PowerFiles::updatePowerRail(std::string_view power_rail) {
254 float avg_power = NAN;
255
256 if (!power_rail_info_map_.count(power_rail.data())) {
257 return avg_power;
258 }
259
260 if (!power_status_map_.count(power_rail.data())) {
261 return avg_power;
262 }
263
264 const auto &power_rail_info = power_rail_info_map_.at(power_rail.data());
265 auto &power_status = power_status_map_.at(power_rail.data());
266
267 boot_clock::time_point now = boot_clock::now();
268 auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
269 now - power_status.last_update_time);
270
271 if (power_status.last_update_time != boot_clock::time_point::min() &&
272 time_elapsed_ms < power_rail_info.power_sample_delay) {
273 return power_status.last_updated_avg_power;
274 }
275
276 if (!energy_info_map_.size() && !updateEnergyValues()) {
277 LOG(ERROR) << "Failed to update energy values";
278 return avg_power;
279 }
280
281 if (power_rail_info.virtual_power_rail_info == nullptr) {
282 avg_power = updateAveragePower(power_rail, &power_status.power_history[0]);
283 } else {
284 const auto offset = power_rail_info.virtual_power_rail_info->offset;
285 float avg_power_val = 0.0;
286 for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size();
287 i++) {
288 float coefficient = power_rail_info.virtual_power_rail_info->coefficients[i];
289 float avg_power_number = updateAveragePower(
290 power_rail_info.virtual_power_rail_info->linked_power_rails[i],
291 &power_status.power_history[i]);
292
293 switch (power_rail_info.virtual_power_rail_info->formula) {
294 case FormulaOption::COUNT_THRESHOLD:
295 if ((coefficient < 0 && avg_power_number < -coefficient) ||
296 (coefficient >= 0 && avg_power_number >= coefficient))
297 avg_power_val += 1;
298 break;
299 case FormulaOption::WEIGHTED_AVG:
300 avg_power_val += avg_power_number * coefficient;
301 break;
302 case FormulaOption::MAXIMUM:
303 if (i == 0)
304 avg_power_val = std::numeric_limits<float>::lowest();
305 if (avg_power_number * coefficient > avg_power_val)
306 avg_power_val = avg_power_number * coefficient;
307 break;
308 case FormulaOption::MINIMUM:
309 if (i == 0)
310 avg_power_val = std::numeric_limits<float>::max();
311 if (avg_power_number * coefficient < avg_power_val)
312 avg_power_val = avg_power_number * coefficient;
313 break;
314 default:
315 break;
316 }
317 }
318 if (avg_power_val >= 0) {
319 avg_power_val = avg_power_val + offset;
320 }
321
322 avg_power = avg_power_val;
323 }
324
325 if (avg_power < 0) {
326 avg_power = NAN;
327 }
328
329 power_status.last_updated_avg_power = avg_power;
330 power_status.last_update_time = now;
331 return avg_power;
332 }
333
refreshPowerStatus(void)334 bool PowerFiles::refreshPowerStatus(void) {
335 if (!updateEnergyValues()) {
336 LOG(ERROR) << "Failed to update energy values";
337 return false;
338 }
339
340 for (const auto &power_status_pair : power_status_map_) {
341 updatePowerRail(power_status_pair.first);
342 }
343 return true;
344 }
345
logPowerStatus(const boot_clock::time_point & now)346 void PowerFiles::logPowerStatus(const boot_clock::time_point &now) {
347 // calculate energy and print
348 uint8_t power_rail_log_cnt = 0;
349 uint64_t max_duration = 0;
350 float tot_power = 0.0;
351 std::string out;
352 for (const auto &energy_info_pair : energy_info_map_) {
353 const auto &rail = energy_info_pair.first;
354 if (!power_status_log_.prev_energy_info_map.count(rail)) {
355 continue;
356 }
357 const auto &last_sample = power_status_log_.prev_energy_info_map.at(rail);
358 const auto &curr_sample = energy_info_pair.second;
359 float avg_power = NAN;
360 if (calculateAvgPower(rail, last_sample, curr_sample, &avg_power) && avg_power != NAN) {
361 // start of new line
362 if (power_rail_log_cnt % kMaxPowerLogPerLine == 0) {
363 if (power_rail_log_cnt != 0) {
364 out.append("\n");
365 }
366 out.append("Power rails ");
367 }
368 out.append(StringPrintf("[%s: %0.2f mW] ", rail.c_str(), avg_power));
369 power_rail_log_cnt++;
370 tot_power += avg_power;
371 max_duration = std::max(max_duration, curr_sample.duration - last_sample.duration);
372 }
373 }
374
375 if (power_rail_log_cnt) {
376 LOG(INFO) << StringPrintf("Power rails total power: %0.2f mW for %" PRId64 " ms", tot_power,
377 max_duration);
378 LOG(INFO) << out;
379 }
380 power_status_log_ = {.prev_log_time = now, .prev_energy_info_map = energy_info_map_};
381 }
382
383 } // namespace implementation
384 } // namespace thermal
385 } // namespace hardware
386 } // namespace android
387 } // namespace aidl
388