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
47 if (last_sample.duration == 0) {
48 LOG(VERBOSE) << "Power rail " << power_rail.data() << ": samples are under collecting";
49 return true;
50 } else if (curr_sample.duration == last_sample.duration) {
51 LOG(VERBOSE) << "Power rail " << power_rail.data()
52 << ": has not collected min 2 samples yet";
53 return true;
54 } else if (curr_sample.duration < last_sample.duration ||
55 curr_sample.energy_counter < last_sample.energy_counter) {
56 LOG(ERROR) << "Power rail " << power_rail.data()
57 << " is invalid: last_sample=" << last_sample.energy_counter
58 << "(T=" << last_sample.duration << ")"
59 << ", curr_sample=" << curr_sample.energy_counter
60 << "(T=" << curr_sample.duration << ")";
61 return false;
62 }
63 const auto duration = curr_sample.duration - last_sample.duration;
64 const auto deltaEnergy = curr_sample.energy_counter - last_sample.energy_counter;
65 *avg_power = static_cast<float>(deltaEnergy) / static_cast<float>(duration);
66 LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << *avg_power
67 << ", duration = " << duration << ", deltaEnergy = " << deltaEnergy;
68 return true;
69 }
70 } // namespace
71
registerPowerRailsToWatch(const Json::Value & config,std::unordered_map<std::string,std::vector<std::string>> * power_rail_switch_map)72 bool PowerFiles::registerPowerRailsToWatch(
73 const Json::Value &config,
74 std::unordered_map<std::string, std::vector<std::string>> *power_rail_switch_map) {
75 if (!ParsePowerRailInfo(config, &power_rail_info_map_, power_rail_switch_map)) {
76 LOG(ERROR) << "Failed to parse power rail info config";
77 return false;
78 }
79
80 if (!power_rail_info_map_.size()) {
81 LOG(INFO) << " No power rail info config found";
82 return true;
83 }
84
85 if (!findEnergySourceToWatch()) {
86 LOG(ERROR) << "Cannot find energy source";
87 return false;
88 }
89
90 if (!energy_info_map_.size() && !updateEnergyValues()) {
91 LOG(ERROR) << "Faield to update energy info";
92 return false;
93 }
94
95 for (const auto &power_rail_info_pair : power_rail_info_map_) {
96 std::vector<std::queue<PowerSample>> power_history;
97 if (!power_rail_info_pair.second.power_sample_count ||
98 power_rail_info_pair.second.power_sample_delay == std::chrono::milliseconds::max()) {
99 continue;
100 }
101
102 if (power_rail_info_pair.second.virtual_power_rail_info != nullptr &&
103 power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size()) {
104 for (size_t i = 0;
105 i < power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size();
106 ++i) {
107 std::string power_rail =
108 power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails[i];
109 if (!energy_info_map_.count(power_rail)) {
110 LOG(ERROR) << " Could not find energy source " << power_rail;
111 return false;
112 }
113
114 const auto curr_sample = energy_info_map_.at(power_rail);
115 power_history.emplace_back(std::queue<PowerSample>());
116 for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
117 power_history[i].emplace(curr_sample);
118 }
119 }
120 } else {
121 if (energy_info_map_.count(power_rail_info_pair.first)) {
122 const auto curr_sample = energy_info_map_.at(power_rail_info_pair.first);
123 power_history.emplace_back(std::queue<PowerSample>());
124 for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
125 power_history[0].emplace(curr_sample);
126 }
127 } else {
128 LOG(ERROR) << "Could not find energy source " << power_rail_info_pair.first;
129 return false;
130 }
131 }
132
133 if (power_history.size()) {
134 power_status_map_[power_rail_info_pair.first] = {
135 .last_update_time = boot_clock::time_point::min(),
136 .power_history = power_history,
137 .last_updated_avg_power = NAN,
138 .enabled = true,
139 };
140 } else {
141 LOG(ERROR) << "power history size is zero";
142 return false;
143 }
144 LOG(INFO) << "Successfully to register power rail " << power_rail_info_pair.first;
145 }
146
147 power_status_log_ = {.prev_log_time = boot_clock::now(),
148 .prev_energy_info_map = energy_info_map_};
149 return true;
150 }
151
findEnergySourceToWatch(void)152 bool PowerFiles::findEnergySourceToWatch(void) {
153 std::string devicePath;
154
155 if (energy_path_set_.size()) {
156 return true;
157 }
158
159 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(kIioRootDir.data()), closedir);
160 if (!dir) {
161 PLOG(ERROR) << "Error opening directory" << kIioRootDir;
162 return false;
163 }
164
165 // Find any iio:devices that support energy_value
166 while (struct dirent *ent = readdir(dir.get())) {
167 std::string devTypeDir = ent->d_name;
168 if (devTypeDir.find(kDeviceType) != std::string::npos) {
169 devicePath = StringPrintf("%s/%s", kIioRootDir.data(), devTypeDir.data());
170 std::string deviceEnergyContent;
171
172 if (!ReadFileToString(StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()),
173 &deviceEnergyContent)) {
174 } else if (deviceEnergyContent.size()) {
175 energy_path_set_.emplace(
176 StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()));
177 }
178 }
179 }
180
181 if (!energy_path_set_.size()) {
182 return false;
183 }
184
185 return true;
186 }
187
updateEnergyValues(void)188 bool PowerFiles::updateEnergyValues(void) {
189 std::string deviceEnergyContent;
190 std::string deviceEnergyContents;
191 std::string line;
192
193 ATRACE_CALL();
194 for (const auto &path : energy_path_set_) {
195 if (!::android::base::ReadFileToString(path, &deviceEnergyContent)) {
196 LOG(ERROR) << "Failed to read energy content from " << path;
197 return false;
198 } else {
199 deviceEnergyContents.append(deviceEnergyContent);
200 }
201 }
202
203 std::istringstream energyData(deviceEnergyContents);
204
205 while (std::getline(energyData, line)) {
206 /* Read rail energy */
207 uint64_t energy_counter = 0;
208 uint64_t duration = 0;
209
210 /* Format example: CH3(T=358356)[S2M_VDD_CPUCL2], 761330 */
211 auto start_pos = line.find("T=");
212 auto end_pos = line.find(')');
213 if (start_pos != std::string::npos) {
214 duration =
215 strtoul(line.substr(start_pos + 2, end_pos - start_pos - 2).c_str(), NULL, 10);
216 } else {
217 continue;
218 }
219
220 start_pos = line.find(")[");
221 end_pos = line.find(']');
222 std::string railName;
223 if (start_pos != std::string::npos) {
224 railName = line.substr(start_pos + 2, end_pos - start_pos - 2);
225 } else {
226 continue;
227 }
228
229 start_pos = line.find("],");
230 if (start_pos != std::string::npos) {
231 energy_counter = strtoul(line.substr(start_pos + 2).c_str(), NULL, 10);
232 } else {
233 continue;
234 }
235
236 energy_info_map_[railName] = {
237 .energy_counter = energy_counter,
238 .duration = duration,
239 };
240 }
241
242 return true;
243 }
244
updateAveragePower(std::string_view power_rail,std::queue<PowerSample> * power_history)245 float PowerFiles::updateAveragePower(std::string_view power_rail,
246 std::queue<PowerSample> *power_history) {
247 float avg_power = NAN;
248 if (!energy_info_map_.count(power_rail.data())) {
249 LOG(ERROR) << " Could not find power rail " << power_rail.data();
250 return avg_power;
251 }
252 const auto last_sample = power_history->front();
253 const auto curr_sample = energy_info_map_.at(power_rail.data());
254 if (calculateAvgPower(power_rail, last_sample, curr_sample, &avg_power)) {
255 power_history->pop();
256 power_history->push(curr_sample);
257 }
258 return avg_power;
259 }
260
updatePowerRail(std::string_view power_rail)261 float PowerFiles::updatePowerRail(std::string_view power_rail) {
262 float avg_power = NAN;
263
264 if (!power_rail_info_map_.count(power_rail.data())) {
265 return avg_power;
266 }
267
268 if (!power_status_map_.count(power_rail.data())) {
269 return avg_power;
270 }
271
272 const auto &power_rail_info = power_rail_info_map_.at(power_rail.data());
273 auto &power_status = power_status_map_.at(power_rail.data());
274
275 boot_clock::time_point now = boot_clock::now();
276 auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
277 now - power_status.last_update_time);
278
279 if (power_status.last_update_time != boot_clock::time_point::min() &&
280 time_elapsed_ms < power_rail_info.power_sample_delay) {
281 return power_status.last_updated_avg_power;
282 }
283
284 if (!energy_info_map_.size() && !updateEnergyValues()) {
285 LOG(ERROR) << "Failed to update energy values";
286 return avg_power;
287 }
288
289 if (power_rail_info.virtual_power_rail_info == nullptr) {
290 avg_power = updateAveragePower(power_rail, &power_status.power_history[0]);
291 } else {
292 const auto offset = power_rail_info.virtual_power_rail_info->offset;
293 float avg_power_val = 0.0;
294 for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size();
295 i++) {
296 float coefficient = power_rail_info.virtual_power_rail_info->coefficients[i];
297 float avg_power_number = updateAveragePower(
298 power_rail_info.virtual_power_rail_info->linked_power_rails[i],
299 &power_status.power_history[i]);
300
301 switch (power_rail_info.virtual_power_rail_info->formula) {
302 case FormulaOption::COUNT_THRESHOLD:
303 if ((coefficient < 0 && avg_power_number < -coefficient) ||
304 (coefficient >= 0 && avg_power_number >= coefficient))
305 avg_power_val += 1;
306 break;
307 case FormulaOption::WEIGHTED_AVG:
308 avg_power_val += avg_power_number * coefficient;
309 break;
310 case FormulaOption::MAXIMUM:
311 if (i == 0)
312 avg_power_val = std::numeric_limits<float>::lowest();
313 if (avg_power_number * coefficient > avg_power_val)
314 avg_power_val = avg_power_number * coefficient;
315 break;
316 case FormulaOption::MINIMUM:
317 if (i == 0)
318 avg_power_val = std::numeric_limits<float>::max();
319 if (avg_power_number * coefficient < avg_power_val)
320 avg_power_val = avg_power_number * coefficient;
321 break;
322 default:
323 break;
324 }
325 }
326 if (avg_power_val >= 0) {
327 avg_power_val = avg_power_val + offset;
328 }
329
330 avg_power = avg_power_val;
331 }
332
333 if (avg_power < 0) {
334 avg_power = NAN;
335 }
336
337 power_status.last_updated_avg_power = avg_power;
338 power_status.last_update_time = now;
339 return avg_power;
340 }
341
refreshPowerStatus(void)342 bool PowerFiles::refreshPowerStatus(void) {
343 if (!updateEnergyValues()) {
344 LOG(ERROR) << "Failed to update energy values";
345 return false;
346 }
347
348 for (const auto &[power_rail, power_status] : power_status_map_) {
349 if (power_status.enabled) {
350 updatePowerRail(power_rail);
351 }
352 }
353 return true;
354 }
355
powerSamplingSwitch(std::string_view power_rail,const bool enabled)356 void PowerFiles::powerSamplingSwitch(std::string_view power_rail, const bool enabled) {
357 if (!power_rail_info_map_.contains(power_rail.data())) {
358 LOG(ERROR) << "Unable to clear status for invalid power rail: " << power_rail.data();
359 return;
360 }
361 auto &power_status = power_status_map_.at(power_rail.data());
362 power_status.enabled = enabled;
363
364 if (!enabled) {
365 PowerSample power_sample = {.energy_counter = 0, .duration = 0};
366
367 for (size_t i = 0; i < power_status.power_history.size(); i++) {
368 for (size_t j = 0; j < power_status.power_history[i].size(); j++) {
369 power_status.power_history[i].pop();
370 power_status.power_history[i].push(power_sample);
371 }
372 }
373 power_status.last_updated_avg_power = NAN;
374 }
375 }
376
logPowerStatus(const boot_clock::time_point & now)377 void PowerFiles::logPowerStatus(const boot_clock::time_point &now) {
378 // calculate energy and print
379 uint8_t power_rail_log_cnt = 0;
380 uint64_t max_duration = 0;
381 float tot_power = 0.0;
382 std::string out;
383 for (const auto &energy_info_pair : energy_info_map_) {
384 const auto &rail = energy_info_pair.first;
385 if (!power_status_log_.prev_energy_info_map.count(rail)) {
386 continue;
387 }
388 const auto &last_sample = power_status_log_.prev_energy_info_map.at(rail);
389 const auto &curr_sample = energy_info_pair.second;
390 float avg_power = NAN;
391 if (calculateAvgPower(rail, last_sample, curr_sample, &avg_power) &&
392 !std::isnan(avg_power)) {
393 // start of new line
394 if (power_rail_log_cnt % kMaxPowerLogPerLine == 0) {
395 if (power_rail_log_cnt != 0) {
396 out.append("\n");
397 }
398 out.append("Power rails ");
399 }
400 out.append(StringPrintf("[%s: %0.2f mW] ", rail.c_str(), avg_power));
401 power_rail_log_cnt++;
402 tot_power += avg_power;
403 max_duration = std::max(max_duration, curr_sample.duration - last_sample.duration);
404 }
405 }
406
407 if (power_rail_log_cnt) {
408 LOG(INFO) << StringPrintf("Power rails total power: %0.2f mW for %" PRId64 " ms", tot_power,
409 max_duration);
410 LOG(INFO) << out;
411 }
412 power_status_log_ = {.prev_log_time = now, .prev_energy_info_map = energy_info_map_};
413 }
414
415 } // namespace implementation
416 } // namespace thermal
417 } // namespace hardware
418 } // namespace android
419 } // namespace aidl
420