/* * Copyright 2023 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. */ #define LOG_TAG "stack_power_tel" #include "osi/include/stack_power_telemetry.h" #include #include #include #include #include #include #include #include #include "os/logging/log_adapter.h" #include "osi/include/properties.h" #include "stack/include/acl_api_types.h" #include "stack/include/bt_psm_types.h" #include "stack/include/btm_status.h" #include "types/raw_address.h" // TODO(b/369381361) Enfore -Wmissing-prototypes #pragma GCC diagnostic ignored "-Wmissing-prototypes" using namespace bluetooth; time_t get_current_time() { return time(0); } namespace { constexpr int64_t kTrafficLogTime = 120; // 120seconds constexpr uint8_t kLogEntriesSize{15}; constexpr std::string_view kLogPerChannelProperty = "bluetooth.powertelemetry.log_per_channel.enabled"; constexpr std::string_view kPowerTelemetryEnabledProperty = "bluetooth.powertelemetry.enabled"; bool power_telemerty_enabled_ = false; std::string GetTimeString(time_t tstamp) { char buffer[15]; tm* nTm = localtime(&tstamp); strftime(buffer, 15, "%m-%d %H:%M:%S", nTm); return std::string(buffer); } enum class State { kDisconnected = 0, kConnected = 1, }; enum class ChannelType { kUnknown = 0, kRfcomm = 1, kL2cap = 2, }; ChannelType PsmToChannelType(const uint16_t& psm) { switch (psm) { case BT_PSM_RFCOMM: return ChannelType::kRfcomm; break; } return ChannelType::kL2cap; } struct Duration { time_t begin = 0; time_t end = 0; }; struct DataTransfer { struct { int64_t bytes{0}; } rx, tx; }; struct LinkDetails { RawAddress bd_addr = RawAddress::kEmpty; uint16_t handle = 0; Duration duration; uint8_t tx_power_level = 0; }; struct ChannelDetails { RawAddress bd_addr = RawAddress::kEmpty; int32_t psm = 0; struct { uint16_t cid = 0; } src, dst; State state = State::kDisconnected; ChannelType channel_type = ChannelType::kUnknown; DataTransfer data_transfer; Duration duration; struct { time_t last_data_sent = 0; } rx, tx; }; struct AclPacketDetails { struct { uint32_t pkt_count = 0; int64_t byte_count = 0; } rx, tx; }; struct AdvDetails { Duration active; }; struct ScanDetails { int32_t count = 0; }; struct SniffData { RawAddress bd_addr = RawAddress::kEmpty; uint32_t sniff_count = 0, active_count = 0; time_t sniff_duration_ts = 0, active_duration_ts = 0; time_t last_mode_change_ts = get_current_time(); }; class LogDataContainer { public: struct Duration lifetime; std::map> channel_map; DataTransfer l2c_data, rfc_data; std::map sniff_activity_map; struct { std::map link_details_map; std::list link_details_list; } acl, sco; std::list adv_list; ScanDetails scan_details, inq_scan_details, le_scan_details; AclPacketDetails acl_pkt_ds, hci_cmd_evt_ds; }; } // namespace struct power_telemetry::PowerTelemetryImpl { PowerTelemetryImpl() { idx_containers = 0; traffic_logged_ts_ = get_current_time(); log_per_channel_ = osi_property_get_bool(std::string(kLogPerChannelProperty).c_str(), false); power_telemetry_enabled_property_ = osi_property_get_bool(std::string(kPowerTelemetryEnabledProperty).c_str(), true); // Enable this feature when both feature flag and sysprops turn on. power_telemerty_enabled_ = com::android::bluetooth::flags::bluetooth_power_telemetry() && power_telemetry_enabled_property_; } LogDataContainer& GetCurrentLogDataContainer() { return log_data_containers_[idx_containers]; } void maybe_log_data() { if ((get_current_time() - traffic_logged_ts_) >= kTrafficLogTime) { LogDataTransfer(); } } void LogDataTransfer(); void RecordLogDataContainer(); mutable std::mutex dumpsys_mutex_; LogDataContainer log_data_containers_[kLogEntriesSize]; std::atomic_int idx_containers; time_t traffic_logged_ts_ = 0; struct { struct { int64_t bytes_ = 0; } rx, tx; } l2c, rfc; struct { uint32_t pkt_ = 0; int64_t len_ = 0; } rx, tx; struct { uint16_t count_ = 0; } scan, inq_scan, ble_adv, ble_scan; struct { uint32_t count_ = 0; } cmd, event; bool scan_timer_started_ = false; bool log_per_channel_ = false; bool power_telemetry_enabled_property_ = false; }; void power_telemetry::PowerTelemetryImpl::LogDataTransfer() { if (!power_telemerty_enabled_) { return; } LogDataContainer& ldc = GetCurrentLogDataContainer(); if ((l2c.rx.bytes_ != 0) || (l2c.tx.bytes_ != 0)) { ldc.l2c_data = { .rx = { .bytes = l2c.rx.bytes_, }, .tx = { .bytes = l2c.tx.bytes_, }, }; l2c = {}; } if ((rfc.rx.bytes_ != 0) || (rfc.tx.bytes_ != 0)) { ldc.rfc_data = { .rx = { .bytes = rfc.rx.bytes_, }, .tx = { .bytes = rfc.tx.bytes_, }, }; rfc = {}; } if (scan.count_ != 0) { ldc.scan_details = { .count = scan.count_, }; scan.count_ = 0; } if (inq_scan.count_ != 0) { ldc.inq_scan_details = { .count = inq_scan.count_, }; inq_scan.count_ = 0; } if ((rx.pkt_ != 0) || (tx.pkt_ != 0)) { ldc.acl_pkt_ds = { .rx = { .pkt_count = rx.pkt_, .byte_count = rx.len_, }, .tx = { .pkt_count = tx.pkt_, .byte_count = tx.len_, }, }; rx.pkt_ = tx.pkt_ = rx.len_ = tx.len_ = 0; } if ((cmd.count_ != 0) || (event.count_ != 0)) { ldc.hci_cmd_evt_ds = { .rx = { .pkt_count = event.count_, }, .tx = { .pkt_count = cmd.count_, }, }; cmd.count_ = event.count_ = 0; } if (ble_scan.count_ != 0) { ldc.le_scan_details = { .count = ble_scan.count_, }; ble_scan.count_ = 0; } ldc.lifetime.begin = traffic_logged_ts_; ldc.lifetime.end = get_current_time(); traffic_logged_ts_ = get_current_time(); RecordLogDataContainer(); } void power_telemetry::PowerTelemetryImpl::RecordLogDataContainer() { if (!power_telemerty_enabled_) { return; } LogDataContainer& ldc = GetCurrentLogDataContainer(); log::info( "bt_power: scan: {}, inqScan: {}, aclTx: {}, aclRx: {}, hciCmd: {}, " "hciEvt: {}, bleScan: {}", ldc.scan_details.count, ldc.inq_scan_details.count, ldc.acl_pkt_ds.tx.pkt_count, ldc.acl_pkt_ds.rx.pkt_count, ldc.hci_cmd_evt_ds.tx.pkt_count, ldc.hci_cmd_evt_ds.rx.pkt_count, ldc.le_scan_details.count); idx_containers++; if (idx_containers >= kLogEntriesSize) { idx_containers = 0; } log_data_containers_[idx_containers] = LogDataContainer(); } power_telemetry::PowerTelemetry& power_telemetry::GetInstance() { static power_telemetry::PowerTelemetry power_telemetry; return power_telemetry; } power_telemetry::PowerTelemetry::PowerTelemetry() { pimpl_ = std::make_unique(); } void power_telemetry::PowerTelemetry::LogInqScanStarted() { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->inq_scan.count_++; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogInqScanStopped() { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogBleScan(uint16_t num_resps) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->ble_scan.count_ += num_resps; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogBleAdvStarted() { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); const time_t current_time = get_current_time(); LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); ldc.adv_list.emplace_back(AdvDetails{.active.begin = current_time}); } void power_telemetry::PowerTelemetry::LogBleAdvStopped() { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); const time_t current_time = get_current_time(); LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); if (ldc.adv_list.size() == 0) { log::warn("Empty advList. Skip LogBleAdvDetails."); return; } ldc.adv_list.back().active.end = current_time; } void power_telemetry::PowerTelemetry::LogTxPower(void* res) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); tBTM_TX_POWER_RESULT* result = (tBTM_TX_POWER_RESULT*)res; LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); if (result->status != tBTM_STATUS::BTM_SUCCESS) { return; } for (auto it : ldc.acl.link_details_map) { uint16_t handle = it.first; LinkDetails lds = it.second; if (lds.bd_addr == result->rem_bda) { lds.tx_power_level = result->tx_power; ldc.acl.link_details_map[handle] = lds; break; } } pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogLinkDetails(uint16_t handle, const RawAddress& bd_addr, bool is_connected, bool is_acl_link) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); std::map& link_map = is_acl_link ? ldc.acl.link_details_map : ldc.sco.link_details_map; std::list& link_list = is_acl_link ? ldc.acl.link_details_list : ldc.sco.link_details_list; if (is_connected == false && link_map.count(handle) != 0) { LinkDetails link_details = link_map[handle]; link_details.duration.end = get_current_time(); link_list.push_back(link_details); link_map.erase(handle); } else if (is_connected == true) { link_map[handle] = { .bd_addr = bd_addr, .handle = handle, .duration.begin = get_current_time(), .tx_power_level = 0, }; if (is_acl_link) { SniffData sniff_data; if (ldc.sniff_activity_map.count(handle) != 0) { ldc.sniff_activity_map.erase(handle); } sniff_data.bd_addr = bd_addr; sniff_data.active_count = 1; sniff_data.last_mode_change_ts = get_current_time(); ldc.sniff_activity_map[handle] = sniff_data; } } pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogHciCmdDetail() { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->cmd.count_++; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogHciEvtDetail() { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->event.count_++; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogSniffStarted(uint16_t handle, const RawAddress& bd_addr) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); const time_t current_timestamp = get_current_time(); SniffData sniff_data; LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); if (ldc.sniff_activity_map.count(handle) == 0) { sniff_data.bd_addr = bd_addr; } else { sniff_data = ldc.sniff_activity_map[handle]; } sniff_data.sniff_count++; sniff_data.active_duration_ts += current_timestamp - sniff_data.last_mode_change_ts; sniff_data.last_mode_change_ts = get_current_time(); ldc.sniff_activity_map[handle] = sniff_data; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogSniffStopped(uint16_t handle, const RawAddress& bd_addr) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); const time_t current_timestamp = get_current_time(); SniffData sniff_data; LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); if (ldc.sniff_activity_map.count(handle) == 0) { sniff_data.bd_addr = bd_addr; } else { sniff_data = ldc.sniff_activity_map[handle]; } sniff_data.active_count++; sniff_data.sniff_duration_ts += current_timestamp - sniff_data.last_mode_change_ts; sniff_data.last_mode_change_ts = get_current_time(); ldc.sniff_activity_map[handle] = sniff_data; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogScanStarted() { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->scan.count_++; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogTxAclPktData(uint16_t len) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->tx.pkt_++; pimpl_->tx.len_ += len; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogRxAclPktData(uint16_t len) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->rx.pkt_++; pimpl_->rx.len_ += len; pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogChannelConnected(uint16_t psm, int32_t src_id, int32_t dst_id, const RawAddress& bd_addr) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); std::list channel_details_list; LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); const ChannelType channel_type = PsmToChannelType(psm); ChannelDetails channel_details = { .bd_addr = bd_addr, .psm = psm, .src.cid = static_cast(src_id), .dst.cid = static_cast(dst_id), .state = State::kConnected, .channel_type = channel_type, .data_transfer = {}, .duration.begin = get_current_time(), .rx = {}, .tx = {}, }; if (ldc.channel_map.count(bd_addr) == 0) { ldc.channel_map.insert(std::pair>( bd_addr, std::list({channel_details}))); } else { ldc.channel_map[bd_addr].emplace_back(channel_details); } pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogChannelDisconnected(uint16_t psm, int32_t src_id, int32_t dst_id, const RawAddress& bd_addr) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); std::list channel_details_list; LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); if (ldc.channel_map.count(bd_addr) == 0) { return; } const ChannelType channel_type = PsmToChannelType(psm); for (auto& channel_detail : ldc.channel_map[bd_addr]) { if (channel_detail.src.cid == src_id && channel_detail.dst.cid == dst_id && channel_detail.channel_type == channel_type) { channel_detail.state = State::kDisconnected; channel_detail.duration.end = get_current_time(); break; } } pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogTxBytes(uint16_t psm, int32_t src_id, int32_t dst_id, const RawAddress& bd_addr, int32_t num_bytes) { if (!power_telemerty_enabled_) { return; } const ChannelType channel_type = PsmToChannelType(psm); std::lock_guard lock(pimpl_->dumpsys_mutex_); if (pimpl_->log_per_channel_ == true) { std::list channel_details_list; LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); if (ldc.channel_map.count(bd_addr) == 0) { return; } for (auto& channel_details : ldc.channel_map[bd_addr]) { if (channel_details.src.cid == src_id && channel_details.dst.cid == dst_id && channel_details.channel_type == channel_type) { channel_details.data_transfer.tx.bytes += num_bytes; channel_details.tx.last_data_sent = get_current_time(); break; } } } if (channel_type == ChannelType::kRfcomm) { pimpl_->rfc.tx.bytes_ += num_bytes; } else { pimpl_->l2c.tx.bytes_ += num_bytes; } pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::LogRxBytes(uint16_t psm, int32_t src_id, int32_t dst_id, const RawAddress& bd_addr, int32_t num_bytes) { if (!power_telemerty_enabled_) { return; } const ChannelType channel_type = PsmToChannelType(psm); std::lock_guard lock(pimpl_->dumpsys_mutex_); if (pimpl_->log_per_channel_ == true) { std::list channel_details_list; LogDataContainer& ldc = pimpl_->GetCurrentLogDataContainer(); if (ldc.channel_map.count(bd_addr) == 0) { return; } for (auto& channel_detail : ldc.channel_map[bd_addr]) { if (channel_detail.src.cid == src_id && channel_detail.dst.cid == dst_id && channel_detail.channel_type == channel_type) { channel_detail.data_transfer.rx.bytes += num_bytes; channel_detail.rx.last_data_sent = get_current_time(); break; } } } switch (channel_type) { case ChannelType::kRfcomm: pimpl_->rfc.rx.bytes_ += num_bytes; break; case ChannelType::kL2cap: pimpl_->l2c.rx.bytes_ += num_bytes; break; case ChannelType::kUnknown: break; } pimpl_->maybe_log_data(); } void power_telemetry::PowerTelemetry::Dumpsys(int32_t fd) { if (!power_telemerty_enabled_) { return; } std::lock_guard lock(pimpl_->dumpsys_mutex_); pimpl_->RecordLogDataContainer(); dprintf(fd, "\nPower Telemetry Data:\n"); dprintf(fd, "\nBR/EDR Scan Events:\n"); dprintf(fd, "%-22s %-22s %-15s\n", "StartTimeStamp", "EndTimeStamp", "Number of Scans"); for (auto&& ldc : pimpl_->log_data_containers_) { if (ldc.scan_details.count == 0) { continue; } dprintf(fd, "%-22s %-22s %-15d\n", GetTimeString(ldc.lifetime.begin).c_str(), GetTimeString(ldc.lifetime.end).c_str(), ldc.scan_details.count); } dprintf(fd, "\nBR/EDR InqScan Events:\n"); dprintf(fd, "%-22s %-22s %-15s\n", "StartTimeStamp", "EndTimeStamp", "Number of InqScans"); for (auto&& ldc : pimpl_->log_data_containers_) { if (ldc.inq_scan_details.count == 0) { continue; } dprintf(fd, "%-22s %-22s %-15d\n", GetTimeString(ldc.lifetime.begin).c_str(), GetTimeString(ldc.lifetime.end).c_str(), ldc.inq_scan_details.count); } dprintf(fd, "\nACL Packet Details:\n"); dprintf(fd, "%-22s %-22s %-12s %-12s %-12s %-12s\n", "StartTimeStamp", "EndTimeStamp", "Tx Packets", "Tx Bytes", "Rx Packets", "Rx Bytes"); for (auto&& ldc : pimpl_->log_data_containers_) { if ((ldc.acl_pkt_ds.tx.byte_count == 0) && (ldc.acl_pkt_ds.rx.byte_count == 0)) { continue; } dprintf(fd, "%-22s %-22s %-12d %-12ld %-12d %-12ld\n", GetTimeString(ldc.lifetime.begin).c_str(), GetTimeString(ldc.lifetime.end).c_str(), ldc.acl_pkt_ds.tx.pkt_count, (long)ldc.acl_pkt_ds.tx.byte_count, ldc.acl_pkt_ds.rx.pkt_count, (long)ldc.acl_pkt_ds.rx.byte_count); } dprintf(fd, "\nHCI CMD/EVT Details:\n"); dprintf(fd, "%-22s %-22s %-14s %-14s\n", "StartTimeStamp", "EndTimeStamp", "HCI Commands", "HCI Events"); for (auto&& ldc : pimpl_->log_data_containers_) { if ((ldc.hci_cmd_evt_ds.tx.pkt_count == 0) && (ldc.hci_cmd_evt_ds.rx.pkt_count == 0)) { continue; } dprintf(fd, "%-22s %-22s %-14d %-14d\n", GetTimeString(ldc.lifetime.begin).c_str(), GetTimeString(ldc.lifetime.end).c_str(), ldc.hci_cmd_evt_ds.tx.pkt_count, ldc.hci_cmd_evt_ds.rx.pkt_count); } dprintf(fd, "\nBLE Scan Details:\n"); dprintf(fd, "%-22s %-22s %-14s\n", "StartTimeStamp", "EndTimeStamp", "Number of scans"); for (auto&& ldc : pimpl_->log_data_containers_) { if (ldc.le_scan_details.count == 0) { continue; } dprintf(fd, "%-22s %-22s %-14d\n", GetTimeString(ldc.lifetime.begin).c_str(), GetTimeString(ldc.lifetime.end).c_str(), ldc.le_scan_details.count); } dprintf(fd, "\nL2CAP/RFCOMM Channel Events:\n"); dprintf(fd, "%-19s %-7s %-7s %-7s %-8s %-22s", "RemoteAddress", "Type", "SrcId", "DstId", "PSM", "ConnectedTimeStamp"); dprintf(fd, " %-22s %-14s ", "DisconnectedTimeStamp", "State"); if (pimpl_->log_per_channel_ == true) { dprintf(fd, " %-10s %-10s %-22s %-22s", "TxBytes", "RxBytes", "LastTxTimeStamp", "LastRxTimeStamp"); } dprintf(fd, "\n"); for (auto&& ldc : pimpl_->log_data_containers_) { for (auto& itr : ldc.channel_map) { const RawAddress& bd_addr = itr.first; std::list channel_details_list = itr.second; for (auto& channel_details : channel_details_list) { dprintf(fd, "%-19s ", ADDRESS_TO_LOGGABLE_CSTR(bd_addr)); dprintf(fd, "%-7s %-7d %-7d %-8d %-22s %-22s %-14s", (channel_details.channel_type == ChannelType::kRfcomm) ? "RFCOMM" : "L2CAP", channel_details.src.cid, channel_details.dst.cid, channel_details.psm, GetTimeString(channel_details.duration.begin).c_str(), GetTimeString(channel_details.duration.end).c_str(), (channel_details.state == State::kDisconnected) ? "DISCONNECTED" : "CONNECTED"); if (pimpl_->log_per_channel_ == true) { dprintf(fd, "%-10ld %-10ld %-22s %-22s", (long)channel_details.data_transfer.tx.bytes, (long)channel_details.data_transfer.rx.bytes, GetTimeString(channel_details.tx.last_data_sent).c_str(), GetTimeString(channel_details.rx.last_data_sent).c_str()); } dprintf(fd, "\n"); } } } dprintf(fd, "\n\nBluetooth Data Traffic Details\n"); dprintf(fd, "L2cap Data Traffic\n"); dprintf(fd, "%-22s %-22s %-10s %-10s\n", "StartTime", "EndTime", "TxBytes", "RxBytes"); for (auto&& ldc : pimpl_->log_data_containers_) { if (ldc.l2c_data.tx.bytes == 0 && ldc.l2c_data.rx.bytes) { continue; } dprintf(fd, "%-22s %-22s %-10ld %-10ld\n", GetTimeString(ldc.lifetime.begin).c_str(), GetTimeString(ldc.lifetime.end).c_str(), (long)ldc.l2c_data.tx.bytes, (long)ldc.l2c_data.rx.bytes); } dprintf(fd, "\nRfcomm Data Traffic\n"); dprintf(fd, "%-22s %-22s %-10s %-10s\n", "StartTime", "EndTime", "TxBytes", "RxBytes"); for (auto&& ldc : pimpl_->log_data_containers_) { if (ldc.rfc_data.tx.bytes == 0 && ldc.rfc_data.rx.bytes == 0) { continue; } dprintf(fd, "%-22s %-22s %-10ld %-10ld\n", GetTimeString(ldc.lifetime.begin).c_str(), GetTimeString(ldc.lifetime.end).c_str(), (long)ldc.rfc_data.tx.bytes, (long)ldc.rfc_data.rx.bytes); } dprintf(fd, "\n\nSniff Activity Details\n"); dprintf(fd, "%-8s %-19s %-19s %-24s %-19s %-24s\n", "Handle", "BDADDR", "ActiveModeCount", "ActiveModeDuration(sec)", "SniffModeCount", "SniffModeDuration(sec)"); for (auto&& ldc : pimpl_->log_data_containers_) { for (auto itr : ldc.sniff_activity_map) { uint16_t handle = itr.first; SniffData sniff_data = itr.second; dprintf(fd, "%-8d %-19s %-19d %-24ld %-19d %-24ld\n", handle, ADDRESS_TO_LOGGABLE_CSTR(sniff_data.bd_addr), sniff_data.active_count, (long)sniff_data.active_duration_ts, sniff_data.sniff_count, (long)sniff_data.sniff_duration_ts); } } dprintf(fd, "\n\nACL Link Details\n"); dprintf(fd, "%-6s %-19s %-22s %-22s %-8s\n", "handle", "BDADDR", "ConnectedTimeStamp", "DisconnectedTimeStamp", "TxPower"); for (auto&& ldc : pimpl_->log_data_containers_) { for (auto it : ldc.acl.link_details_map) { uint16_t handle = it.first; LinkDetails lds = it.second; dprintf(fd, "%-6d %-19s %-22s %-22s %-8d\n", handle, ADDRESS_TO_LOGGABLE_CSTR(lds.bd_addr), GetTimeString(lds.duration.begin).c_str(), GetTimeString(lds.duration.end).c_str(), lds.tx_power_level); } for (auto& it : ldc.acl.link_details_list) { dprintf(fd, "%-6d %-19s %-22s %-22s %-8d\n", it.handle, ADDRESS_TO_LOGGABLE_CSTR(it.bd_addr), GetTimeString(it.duration.begin).c_str(), GetTimeString(it.duration.end).c_str(), it.tx_power_level); } } dprintf(fd, "\nSCO Link Details\n"); dprintf(fd, "%-6s %-19s %-22s %-22s\n", "handle", "BDADDR", "ConnectedTimeStamp", "DisconnectedTimeStamp"); for (auto&& ldc : pimpl_->log_data_containers_) { for (auto& it : ldc.sco.link_details_list) { dprintf(fd, "%-6d %-19s %-22s %-22s\n", it.handle, ADDRESS_TO_LOGGABLE_CSTR(it.bd_addr), GetTimeString(it.duration.begin).c_str(), GetTimeString(it.duration.end).c_str()); } } dprintf(fd, "\n\n"); }