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