| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/traced/probes/power/android_power_data_source.h" |
| |
| #include <dlfcn.h> |
| |
| #include <vector> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/base/optional.h" |
| #include "perfetto/base/scoped_file.h" |
| #include "perfetto/base/task_runner.h" |
| #include "perfetto/base/time.h" |
| #include "perfetto/tracing/core/data_source_config.h" |
| #include "perfetto/tracing/core/trace_packet.h" |
| #include "perfetto/tracing/core/trace_writer.h" |
| #include "src/android_internal/health_hal.h" |
| #include "src/android_internal/power_stats_hal.h" |
| |
| #include "perfetto/config/power/android_power_config.pbzero.h" |
| #include "perfetto/trace/power/battery_counters.pbzero.h" |
| #include "perfetto/trace/power/power_rails.pbzero.h" |
| #include "perfetto/trace/trace_packet.pbzero.h" |
| |
| namespace perfetto { |
| |
| namespace { |
| constexpr uint32_t kMinPollRateMs = 250; |
| constexpr size_t kMaxNumRails = 32; |
| } // namespace |
| |
| // Dynamically loads / unloads the libperfetto_android_internal.so library which |
| // allows to proxy calls to android hwbinder in in-tree builds. |
| struct AndroidPowerDataSource::DynamicLibLoader { |
| using ScopedDlHandle = base::ScopedResource<void*, dlclose, nullptr>; |
| |
| DynamicLibLoader() { |
| static const char kLibName[] = "libperfetto_android_internal.so"; |
| handle_.reset(dlopen(kLibName, RTLD_NOW)); |
| if (!handle_) { |
| PERFETTO_PLOG("dlopen(%s) failed", kLibName); |
| return; |
| } |
| void* fn = dlsym(*handle_, "GetBatteryCounter"); |
| if (!fn) { |
| PERFETTO_PLOG("dlsym(GetBatteryCounter) failed"); |
| return; |
| } |
| get_battery_counter_ = reinterpret_cast<decltype(get_battery_counter_)>(fn); |
| |
| fn = dlsym(*handle_, "GetAvailableRails"); |
| if (!fn) { |
| PERFETTO_PLOG("dlsym(GetAvailableRails) failed"); |
| return; |
| } |
| get_available_rails_ = reinterpret_cast<decltype(get_available_rails_)>(fn); |
| |
| fn = dlsym(*handle_, "GetRailEnergyData"); |
| if (!fn) { |
| PERFETTO_PLOG("dlsym(GetRailEnergyData) failed"); |
| return; |
| } |
| get_rail_energy_data_ = |
| reinterpret_cast<decltype(get_rail_energy_data_)>(fn); |
| } |
| |
| base::Optional<int64_t> GetCounter(android_internal::BatteryCounter counter) { |
| if (!get_battery_counter_) |
| return base::nullopt; |
| int64_t value = 0; |
| if (get_battery_counter_(counter, &value)) |
| return base::make_optional(value); |
| return base::nullopt; |
| } |
| |
| std::vector<android_internal::RailDescriptor> GetRailDescriptors() { |
| if (!get_available_rails_) |
| return std::vector<android_internal::RailDescriptor>(); |
| |
| std::vector<android_internal::RailDescriptor> rail_descriptors( |
| kMaxNumRails); |
| size_t num_rails = rail_descriptors.size(); |
| get_available_rails_(&rail_descriptors[0], &num_rails); |
| rail_descriptors.resize(num_rails); |
| return rail_descriptors; |
| } |
| |
| std::vector<android_internal::RailEnergyData> GetRailEnergyData() { |
| if (!get_rail_energy_data_) |
| return std::vector<android_internal::RailEnergyData>(); |
| |
| std::vector<android_internal::RailEnergyData> energy_data(kMaxNumRails); |
| size_t num_rails = energy_data.size(); |
| get_rail_energy_data_(&energy_data[0], &num_rails); |
| energy_data.resize(num_rails); |
| return energy_data; |
| } |
| |
| bool is_loaded() const { return !!handle_; } |
| |
| private: |
| decltype(&android_internal::GetBatteryCounter) get_battery_counter_ = nullptr; |
| decltype(&android_internal::GetAvailableRails) get_available_rails_ = nullptr; |
| decltype(&android_internal::GetRailEnergyData) get_rail_energy_data_ = |
| nullptr; |
| ScopedDlHandle handle_; |
| }; |
| |
| AndroidPowerDataSource::AndroidPowerDataSource( |
| DataSourceConfig cfg, |
| base::TaskRunner* task_runner, |
| TracingSessionID session_id, |
| std::unique_ptr<TraceWriter> writer) |
| : ProbesDataSource(session_id, kTypeId), |
| task_runner_(task_runner), |
| rail_descriptors_logged_(false), |
| writer_(std::move(writer)), |
| weak_factory_(this) { |
| using protos::pbzero::AndroidPowerConfig; |
| AndroidPowerConfig::Decoder pcfg(cfg.android_power_config_raw()); |
| poll_rate_ms_ = pcfg.battery_poll_ms(); |
| rails_collection_enabled_ = pcfg.collect_power_rails(); |
| |
| if (poll_rate_ms_ < kMinPollRateMs) { |
| PERFETTO_ELOG("Battery poll interval of %" PRIu32 |
| " ms is too low. Capping to %" PRIu32 " ms", |
| poll_rate_ms_, kMinPollRateMs); |
| poll_rate_ms_ = kMinPollRateMs; |
| } |
| for (auto counter = pcfg.battery_counters(); counter; ++counter) { |
| auto hal_id = android_internal::BatteryCounter::kUnspecified; |
| switch (counter->as_int32()) { |
| case AndroidPowerConfig::BATTERY_COUNTER_UNSPECIFIED: |
| break; |
| case AndroidPowerConfig::BATTERY_COUNTER_CHARGE: |
| hal_id = android_internal::BatteryCounter::kCharge; |
| break; |
| case AndroidPowerConfig::BATTERY_COUNTER_CAPACITY_PERCENT: |
| hal_id = android_internal::BatteryCounter::kCapacityPercent; |
| break; |
| case AndroidPowerConfig::BATTERY_COUNTER_CURRENT: |
| hal_id = android_internal::BatteryCounter::kCurrent; |
| break; |
| case AndroidPowerConfig::BATTERY_COUNTER_CURRENT_AVG: |
| hal_id = android_internal::BatteryCounter::kCurrentAvg; |
| break; |
| } |
| PERFETTO_CHECK(static_cast<size_t>(hal_id) < counters_enabled_.size()); |
| counters_enabled_.set(static_cast<size_t>(hal_id)); |
| } |
| } |
| |
| AndroidPowerDataSource::~AndroidPowerDataSource() = default; |
| |
| void AndroidPowerDataSource::Start() { |
| lib_.reset(new DynamicLibLoader()); |
| if (!lib_->is_loaded()) |
| return; |
| Tick(); |
| } |
| |
| void AndroidPowerDataSource::Tick() { |
| // Post next task. |
| auto now_ms = base::GetWallTimeMs().count(); |
| auto weak_this = weak_factory_.GetWeakPtr(); |
| task_runner_->PostDelayedTask( |
| [weak_this] { |
| if (weak_this) |
| weak_this->Tick(); |
| }, |
| poll_rate_ms_ - (now_ms % poll_rate_ms_)); |
| |
| WriteBatteryCounters(); |
| WritePowerRailsData(); |
| } |
| |
| void AndroidPowerDataSource::WriteBatteryCounters() { |
| if (counters_enabled_.none()) |
| return; |
| |
| auto packet = writer_->NewTracePacket(); |
| packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count())); |
| auto* counters_proto = packet->set_battery(); |
| |
| for (size_t i = 0; i < counters_enabled_.size(); i++) { |
| if (!counters_enabled_.test(i)) |
| continue; |
| auto counter = static_cast<android_internal::BatteryCounter>(i); |
| auto value = lib_->GetCounter(counter); |
| if (!value.has_value()) |
| continue; |
| |
| switch (counter) { |
| case android_internal::BatteryCounter::kUnspecified: |
| PERFETTO_DFATAL("Unspecified counter"); |
| break; |
| |
| case android_internal::BatteryCounter::kCharge: |
| counters_proto->set_charge_counter_uah(*value); |
| break; |
| |
| case android_internal::BatteryCounter::kCapacityPercent: |
| counters_proto->set_capacity_percent(static_cast<float>(*value)); |
| break; |
| |
| case android_internal::BatteryCounter::kCurrent: |
| counters_proto->set_current_ua(*value); |
| break; |
| |
| case android_internal::BatteryCounter::kCurrentAvg: |
| counters_proto->set_current_avg_ua(*value); |
| break; |
| } |
| } |
| } |
| |
| void AndroidPowerDataSource::WritePowerRailsData() { |
| if (!rails_collection_enabled_) |
| return; |
| |
| auto packet = writer_->NewTracePacket(); |
| packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count())); |
| auto* rails_proto = packet->set_power_rails(); |
| |
| if (!rail_descriptors_logged_) { |
| // We only add the rail descriptors to the first package, to avoid logging |
| // all rail names etc. on each one. |
| rail_descriptors_logged_ = true; |
| auto rail_descriptors = lib_->GetRailDescriptors(); |
| if (rail_descriptors.size() == 0) { |
| // No rails to collect data for. Don't try again in the next iteration. |
| rails_collection_enabled_ = false; |
| return; |
| } |
| |
| for (const auto& rail_descriptor : rail_descriptors) { |
| auto* descriptor = rails_proto->add_rail_descriptor(); |
| descriptor->set_index(rail_descriptor.index); |
| descriptor->set_rail_name(rail_descriptor.rail_name); |
| descriptor->set_subsys_name(rail_descriptor.subsys_name); |
| descriptor->set_sampling_rate(rail_descriptor.sampling_rate); |
| } |
| } |
| |
| for (const auto& energy_data : lib_->GetRailEnergyData()) { |
| auto* data = rails_proto->add_energy_data(); |
| data->set_index(energy_data.index); |
| data->set_timestamp_ms(energy_data.timestamp); |
| data->set_energy(energy_data.energy); |
| } |
| } |
| |
| void AndroidPowerDataSource::Flush(FlushRequestID, |
| std::function<void()> callback) { |
| writer_->Flush(callback); |
| } |
| |
| } // namespace perfetto |