| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "minstrel.h" |
| |
| #include <wlan/common/channel.h> |
| #include <wlan/mlme/debug.h> |
| #include <wlan/protocol/mac.h> |
| |
| namespace wlan { |
| namespace wlan_minstrel = ::fuchsia::wlan::minstrel; |
| |
| // If the data rate is too low, do not probe more than twice per update interval |
| static constexpr uint8_t kMaxSlowProbe = 2; |
| // If success rate is below (1 - kProbabilityThreshold), only probe once every this many cycles. |
| static constexpr uint8_t kDeadProbeCycleCount = 32; |
| |
| zx::duration HeaderTxTimeErp() { |
| // TODO(eyw): Implement Erp preamble and header |
| return zx::nsec(0); |
| } |
| |
| zx::duration PayloadTxTimeErp(SupportedRate rate) { |
| // D_{bps} as defined in IEEE 802.11-2016 Table 17-4 |
| // Unit: Number of data bits per OFDM symbol |
| uint16_t bits_per_symbol = rate.rate() * 2; |
| constexpr int kTxTimePerSymbol = 4000; // nanoseconds. |
| |
| uint32_t total_time = kTxTimePerSymbol * 8 * kMinstrelFrameLength / bits_per_symbol; |
| return zx::nsec(total_time); |
| } |
| |
| zx::duration TxTimeErp(SupportedRate rate) { |
| return HeaderTxTimeErp() + PayloadTxTimeErp(rate); |
| } |
| |
| void EmplaceErp(std::unordered_map<tx_vec_idx_t, TxStats>* map, tx_vec_idx_t idx, |
| SupportedRate rate) { |
| zx::duration time = TxTimeErp(rate); |
| ZX_DEBUG_ASSERT(time.to_nsecs() != 0); |
| debugmstl("%s, tx_time %lu nsec\n", debug::Describe(idx).c_str(), time.to_nsecs()); |
| TxStats tx_stats{ |
| .tx_vector_idx = idx, |
| .perfect_tx_time = time, |
| }; |
| map->emplace(idx, tx_stats); |
| } |
| |
| std::unordered_set<tx_vec_idx_t> AddSupportedErp( |
| std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map, |
| const std::vector<SupportedRate>& rates) { |
| size_t tx_stats_added = 0; |
| std::unordered_set<tx_vec_idx_t> basic_rates; |
| for (const auto& rate : rates) { |
| TxVector tx_vector; |
| zx_status_t status = TxVector::FromSupportedRate(rate, &tx_vector); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| // Fuchsia only uses 802.11a/g/n and later data rates for transmission. |
| if (tx_vector.phy != WLAN_PHY_ERP) { continue; } |
| tx_vec_idx_t tx_vector_idx; |
| status = tx_vector.ToIdx(&tx_vector_idx); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| EmplaceErp(tx_stats_map, tx_vector_idx, rate); |
| ++tx_stats_added; |
| if (rate.is_basic()) { |
| debugmstl("basic_rate: %s\n", debug::Describe(tx_vector_idx).c_str()); |
| basic_rates.emplace(tx_vector_idx); |
| } |
| } |
| debugmstl("%zu ERP added.\n", tx_stats_added); |
| if (basic_rates.empty()) { basic_rates.emplace(kErpStartIdx); } |
| return basic_rates; |
| } |
| |
| bool AddMissingErp(std::unordered_map<tx_vec_idx_t, TxStats>* map, tx_vec_idx_t idx) { |
| auto erp_rate = TxVectorIdxToErpRate(idx); |
| if (!erp_rate.has_value()) { |
| ZX_DEBUG_ASSERT(0); |
| return false; |
| } else { |
| EmplaceErp(map, idx, *erp_rate); |
| return true; |
| } |
| } |
| |
| zx::duration HeaderTxTimeHt() { |
| // TODO(eyw): Implement Plcp preamble and header |
| return zx::nsec(0); |
| } |
| |
| // relative_mcs_idx is the index for combination of (modulation, coding rate) tuple |
| // when listed in the same order as MCS Index, without nss. i.e. |
| // 0: BPSK, 1/2 |
| // 1: QPSK, 1/2 |
| // 2: QPSK, 3/4 |
| // 3: 16-QAM, 1/2 |
| // 4: 16-QAM, 3/4 |
| // 5: 64-QAM, 2/3 |
| // 6: 64-QAM, 3/4 |
| // 7: 64-QAM, 5/6 |
| // 8: 256-QAM, 3/4 (since VHT) |
| // 9: 256-QAM, 5/6 (since VHT) |
| zx::duration PayloadTxTimeHt(CBW cbw, GI gi, size_t mcs_idx) { |
| // D_{bps} as defined in IEEE 802.11-2016 Table 19-26 |
| // Unit: Number of data bits per OFDM symbol (20 MHz channel width) |
| constexpr uint16_t bits_per_symbol_list[] = { |
| 26, 52, 78, 104, 156, 208, 234, 260, /* since VHT */ 312, 347}; |
| constexpr uint16_t kDataSubCarriers20 = 52; |
| constexpr uint16_t kDataSubCarriers40 = 108; |
| // TODO(eyw): VHT would have kDataSubCarriers80 = 234 and kDataSubCarriers160 = 468 |
| |
| ZX_DEBUG_ASSERT(gi == WLAN_GI_400NS || gi == WLAN_GI_800NS); |
| |
| int nss = 1 + mcs_idx / kHtNumUniqueMcs; |
| int relative_mcs_idx = mcs_idx % kHtNumUniqueMcs; |
| |
| uint16_t bits_per_symbol = bits_per_symbol_list[relative_mcs_idx]; |
| if (cbw == CBW40) { |
| bits_per_symbol = bits_per_symbol * kDataSubCarriers40 / kDataSubCarriers20; |
| } |
| |
| constexpr int kTxTimePerSymbolGi800 = 4000; // nanoseconds. |
| constexpr int kTxTimePerSymbolGi400 = 3600; // nanoseconds. |
| // Perform multiplication before division to prevent precision loss |
| uint32_t total_time = |
| kTxTimePerSymbolGi800 * 8 * kMinstrelFrameLength / (nss * bits_per_symbol); |
| |
| if (gi == WLAN_GI_400NS) { |
| total_time = |
| 800 + (kTxTimePerSymbolGi400 * 8 * kMinstrelFrameLength / (nss * bits_per_symbol)); |
| } |
| return zx::nsec(total_time); |
| } |
| |
| zx::duration TxTimeHt(CBW cbw, GI gi, uint8_t relative_mcs_idx) { |
| return HeaderTxTimeHt() + PayloadTxTimeHt(cbw, gi, relative_mcs_idx); |
| } |
| |
| // SupportedMcsRx is 78 bit long in IEEE802.11-2016, Figure 9-334 |
| // In reality, devices implement MCS 0-31, sometimes 32, almost never beyond 32. |
| void AddSupportedHt(std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map, CBW cbw, GI gi, |
| const SupportedMcsRxMcsHead& mcs_set) { |
| size_t tx_stats_added = 0; |
| for (uint8_t mcs_idx = 0; mcs_idx < kHtNumMcs; ++mcs_idx) { |
| // Skip if this mcs is not supported |
| if (!mcs_set.Support(mcs_idx)) { continue; } |
| |
| TxVector tx_vector{ |
| .phy = WLAN_PHY_HT, |
| .gi = gi, |
| .cbw = cbw, |
| .mcs_idx = mcs_idx, |
| }; |
| tx_vec_idx_t tx_vector_idx; |
| zx_status_t status = tx_vector.ToIdx(&tx_vector_idx); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| zx::duration perfect_tx_time = TxTimeHt(cbw, gi, mcs_idx); |
| ZX_DEBUG_ASSERT(perfect_tx_time.to_nsecs() != 0); |
| debugmstl("%s, tx_time %lu nsec\n", debug::Describe(tx_vector).c_str(), |
| perfect_tx_time.to_nsecs()); |
| |
| TxStats tx_stats{ |
| .tx_vector_idx = tx_vector_idx, |
| .perfect_tx_time = perfect_tx_time, |
| }; |
| tx_stats_map->emplace(tx_vector_idx, tx_stats); |
| ++tx_stats_added; |
| } |
| debugmstl("%zu HT added with cbw=%s, gi=%s\n", tx_stats_added, ::wlan::common::CbwStr(cbw), |
| debug::Describe(gi).c_str()); |
| } |
| |
| MinstrelRateSelector::MinstrelRateSelector(fbl::unique_ptr<Timer>&& timer, |
| ProbeSequence&& probe_sequence, |
| zx::duration update_interval) |
| : timer_mgr_(std::move(timer)), |
| probe_sequence_(std::move(probe_sequence)), |
| update_interval_(update_interval) {} |
| |
| std::unordered_set<tx_vec_idx_t> AddErp(std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map, |
| const wlan_assoc_ctx_t& assoc_ctx) { |
| std::vector<SupportedRate> rates(assoc_ctx.rates_cnt); |
| |
| std::transform(assoc_ctx.rates, assoc_ctx.rates + assoc_ctx.rates_cnt, rates.begin(), |
| SupportedRate::raw); |
| |
| debugmstl("Supported rates: %s\n", debug::Describe(rates).c_str()); |
| return AddSupportedErp(tx_stats_map, rates); |
| } |
| |
| void AddHt(std::unordered_map<tx_vec_idx_t, TxStats>* tx_stats_map, const HtCapabilities& ht_cap) { |
| tx_vec_idx_t max_size = kHtNumMcs; |
| // TODO(NET-1726): Enable CBW40 support once its information is available from AssocCtx |
| const CBW assoc_chan_width = CBW20; |
| const bool sgi_20 = ht_cap.ht_cap_info.short_gi_20() == 1; |
| const bool sgi_40 = ht_cap.ht_cap_info.short_gi_40() == 1; |
| |
| if (sgi_20) { max_size += kHtNumMcs; } |
| if (assoc_chan_width == CBW40) { |
| max_size += kHtNumMcs; |
| if (sgi_40) { max_size += kHtNumMcs; } |
| } |
| |
| max_size += kErpNumTxVector; // Taking in to account erp_rates. |
| |
| debugmstl("max_size is %d.\n", max_size); |
| |
| tx_stats_map->reserve(max_size); |
| |
| AddSupportedHt(tx_stats_map, CBW20, WLAN_GI_800NS, ht_cap.mcs_set.rx_mcs_head); |
| if (sgi_20) { AddSupportedHt(tx_stats_map, CBW20, WLAN_GI_400NS, ht_cap.mcs_set.rx_mcs_head); } |
| if (assoc_chan_width == CBW40) { |
| AddSupportedHt(tx_stats_map, CBW40, WLAN_GI_800NS, ht_cap.mcs_set.rx_mcs_head); |
| if (sgi_40) { |
| AddSupportedHt(tx_stats_map, CBW40, WLAN_GI_400NS, ht_cap.mcs_set.rx_mcs_head); |
| } |
| } |
| debugmstl("tx_stats_map size: %zu.\n", tx_stats_map->size()); |
| } |
| |
| void MinstrelRateSelector::AddPeer(const wlan_assoc_ctx_t& assoc_ctx) { |
| auto addr = common::MacAddr(assoc_ctx.bssid); |
| Peer peer{}; |
| peer.addr = addr; |
| |
| { |
| std::lock_guard<std::mutex> guard(*peer.update_lock); |
| HtCapabilities ht_cap; |
| constexpr uint32_t kMcsMask0_31 = 0xFFFFFFFF; |
| if (assoc_ctx.has_ht_cap) { |
| ht_cap = HtCapabilities::FromDdk(assoc_ctx.ht_cap); |
| |
| // TODO(eyw): SGI support suppressed. Remove these once they are supported. |
| ht_cap.ht_cap_info.set_short_gi_20(false); |
| ht_cap.ht_cap_info.set_short_gi_40(false); |
| |
| if ((ht_cap.mcs_set.rx_mcs_head.bitmask() & kMcsMask0_31) == 0) { |
| errorf("Invalid AssocCtx: HT supported but no valid MCS. %s\n", |
| debug::Describe(ht_cap.mcs_set).c_str()); |
| ZX_DEBUG_ASSERT(false); |
| } else { |
| peer.is_ht = true; |
| AddHt(&peer.tx_stats_map, ht_cap); |
| } |
| } |
| |
| if (assoc_ctx.rates_cnt > 0) { |
| peer.basic_rates = AddErp(&peer.tx_stats_map, assoc_ctx); |
| if (peer.basic_rates.size() > 0) { |
| peer.basic_highest = |
| *std::max_element(peer.basic_rates.cbegin(), peer.basic_rates.cend()); |
| } |
| } |
| debugmstl("tx_stats_map populated. size: %zu.\n", peer.tx_stats_map.size()); |
| |
| if (peer.tx_stats_map.size() == 0) { |
| errorf("No usable rates for peer %s.\n", addr.ToString().c_str()); |
| ZX_DEBUG_ASSERT(false); |
| } |
| } |
| |
| debugmstl("Minstrel peer added: %s\n", addr.ToString().c_str()); |
| if (peer_map_.empty()) { |
| ZX_DEBUG_ASSERT(next_update_ == TimeoutId{}); |
| timer_mgr_.Schedule(timer_mgr_.Now() + update_interval_, {}, &next_update_); |
| } else if (GetPeer(addr) != nullptr) { |
| warnf("Peer %s already exists. Forgot to clean up?\n", addr.ToString().c_str()); |
| } |
| peer_map_.emplace(addr, std::move(peer)); |
| outdated_peers_.emplace(addr); |
| UpdateStats(); |
| // TODO(eyw): RemovePeer() for roles other than client. |
| } |
| |
| void MinstrelRateSelector::RemovePeer(const common::MacAddr& addr) { |
| auto iter = peer_map_.find(addr); |
| if (iter == peer_map_.end()) { |
| debugmstl("peer %s not found.\n", addr.ToString().c_str()); |
| return; |
| } |
| |
| outdated_peers_.erase(addr); |
| peer_map_.erase(iter); |
| if (peer_map_.empty()) { |
| timer_mgr_.Cancel(next_update_); |
| next_update_ = {}; |
| } |
| debugmstl("peer %s removed.\n", addr.ToString().c_str()); |
| } |
| |
| void MinstrelRateSelector::HandleTxStatusReport(const wlan_tx_status_t& tx_status) { |
| auto peer_addr = common::MacAddr(tx_status.peer_addr); |
| auto peer = GetPeer(peer_addr); |
| if (peer == nullptr) { |
| debugmstl("Peer [%s] received tx status report after it is removed.\n", |
| peer_addr.ToString().c_str()); |
| return; |
| } |
| |
| std::lock_guard<std::mutex> guard(*peer->update_lock); |
| auto tx_stats_map = &peer->tx_stats_map; |
| tx_vec_idx_t last_idx = kInvalidTxVectorIdx; |
| for (auto entry : tx_status.tx_status_entry) { |
| if (entry.tx_vector_idx == kInvalidTxVectorIdx) { break; } |
| last_idx = entry.tx_vector_idx; |
| if (tx_stats_map->count(last_idx) == 0) { |
| if (!AddMissingErp(&peer->tx_stats_map, last_idx)) { |
| debugmstl("error: Invalid tx_vec_idx: %u.\n", last_idx); |
| last_idx = kInvalidTxVectorIdx; |
| } |
| } |
| if (last_idx != kInvalidTxVectorIdx) { |
| (*tx_stats_map)[last_idx].attempts_cur += entry.attempts; |
| } |
| } |
| |
| if (tx_status.success && last_idx != kInvalidTxVectorIdx) { |
| (*tx_stats_map)[last_idx].success_cur++; |
| } |
| |
| outdated_peers_.emplace(peer_addr); |
| } |
| |
| inline constexpr bool TxStats::PhyPreferredOver(const wlan::TxStats& other) const { |
| // based on experiment, If HT is supported, it is better not to use ERP for data frames. |
| // With ralink RT5592 and Netgear Nighthawk X10, approximately 80 feet away, |
| // HT/ERP tx throughput < 1 Mbps, HT only tx 4-8 Mbps |
| // TODO(WLAN-868): Revisit with VHT support. |
| return IsHt(tx_vector_idx) && !IsHt(other.tx_vector_idx); |
| } |
| |
| inline constexpr bool TxStats::ThroughputHigherThan(const TxStats& other) const { |
| return cur_tp > other.cur_tp || (cur_tp == other.cur_tp && probability > other.probability); |
| } |
| |
| inline constexpr bool TxStats::ProbabilityHigherThan(const TxStats& other) const { |
| if (probability >= kMinstrelProbabilityThreshold && |
| other.probability >= kMinstrelProbabilityThreshold) { |
| // When probability is "high enough", consider throughput instead. |
| return cur_tp > other.cur_tp; |
| } |
| return probability > other.probability; |
| } |
| |
| inline constexpr bool IsTxUnlikely(const TxStats& ts) { |
| return ts.probability < 1.0f - kMinstrelProbabilityThreshold; |
| } |
| |
| void UpdateStatsPeer(Peer* peer) { |
| std::lock_guard<std::mutex> guard(*peer->update_lock); |
| auto& tsm = peer->tx_stats_map; |
| for (auto& [_, stats] : tsm) { |
| if (stats.attempts_cur != 0) { |
| float prob = 1.0 * stats.success_cur / stats.attempts_cur; |
| if (stats.attempts_total == 0) { |
| stats.probability = prob; |
| } else { |
| stats.probability = |
| stats.probability * kMinstrelExpWeight + prob * (1 - kMinstrelExpWeight); |
| } |
| |
| if (stats.attempts_total + stats.attempts_cur < stats.attempts_total) { // overflow |
| stats.attempts_total = stats.attempts_cur; |
| stats.success_total = stats.success_cur; |
| } else { |
| stats.attempts_total += stats.attempts_cur; |
| stats.success_total += stats.success_cur; |
| } |
| stats.attempts_cur = 0; |
| stats.success_cur = 0; |
| stats.probe_cycles_skipped = 0; |
| } else { |
| ++stats.probe_cycles_skipped; |
| } |
| constexpr float kNanoSecondsPerSecond = 1e9; |
| // perfect_tx_time is always non-zero as guaranteed by AddSupportedHt and AddSupportedErp |
| stats.cur_tp = kNanoSecondsPerSecond / stats.perfect_tx_time.to_nsecs() * stats.probability; |
| } |
| |
| const auto& ctsm = tsm; |
| const auto& brs = peer->basic_rates; |
| |
| // Pick a random tx vector as the starting point because we will go through them all. |
| tx_vec_idx_t max_tp = ctsm.cbegin()->first; |
| tx_vec_idx_t max_probability = max_tp; |
| tx_vec_idx_t basic_max_probability = peer->basic_highest; |
| for (const auto& [idx, stats] : peer->tx_stats_map) { |
| if ((!IsTxUnlikely(stats) && stats.PhyPreferredOver(ctsm.at(max_tp))) || |
| stats.ThroughputHigherThan(ctsm.at(max_tp))) { |
| max_tp = idx; |
| } |
| if ((!IsTxUnlikely(stats) && stats.PhyPreferredOver(ctsm.at(max_probability))) || |
| stats.ProbabilityHigherThan(ctsm.at(max_probability))) { |
| max_probability = idx; |
| } |
| if (brs.count(idx) != 0 && stats.ProbabilityHigherThan(ctsm.at(basic_max_probability))) { |
| basic_max_probability = idx; |
| } |
| } |
| |
| peer->max_tp = max_tp; |
| peer->max_probability = max_probability; |
| peer->basic_max_probability = basic_max_probability; |
| } |
| |
| bool MinstrelRateSelector::HandleTimeout() { |
| bool handled = false; |
| timer_mgr_.HandleTimeout([&](auto now, auto _event, auto timeout_id) { |
| if (next_update_ == timeout_id) { |
| timer_mgr_.Schedule(now + update_interval_, {}, &next_update_); |
| UpdateStats(); |
| handled = true; |
| } |
| }); |
| return handled; |
| } |
| |
| tx_vec_idx_t GetNextProbe(Peer* peer, const ProbeSequence& probe_sequence) { |
| std::lock_guard<std::mutex> gaurd(*peer->update_lock); |
| tx_vec_idx_t probe_idx = kInvalidTxVectorIdx; |
| zx::duration baseline_tx_time = peer->tx_stats_map[peer->max_probability].perfect_tx_time; |
| auto potential_probes = peer->tx_stats_map.size(); |
| if (potential_probes == 1) { return peer->max_tp; } |
| while (potential_probes > 0) { |
| --potential_probes; |
| do { |
| if (probe_sequence.Next(&peer->probe_entry, &probe_idx)) { |
| ++peer->num_probe_cycles_done; |
| } |
| } while (peer->tx_stats_map.count(probe_idx) == 0); // not supported, keep looking |
| const TxStats& tx_stats = peer->tx_stats_map[probe_idx]; |
| const bool skip = |
| // These are used by default so no probing needed |
| probe_idx == peer->basic_max_probability || probe_idx == peer->basic_highest || |
| probe_idx == peer->max_tp || probe_idx == peer->max_probability || |
| // It has been probed more than anyone else |
| (tx_stats.attempts_cur > peer->num_probe_cycles_done) || |
| // It will not provide higher throughput, minimum probing is enough |
| ((tx_stats.perfect_tx_time > baseline_tx_time) && |
| (tx_stats.attempts_cur >= kMaxSlowProbe)) || |
| // It is almost guaranteed to fail, defer until enough cycles pass |
| (IsTxUnlikely(tx_stats) && |
| (tx_stats.probe_cycles_skipped < kDeadProbeCycleCount || tx_stats.attempts_cur > 0)); |
| if (!skip) { break; } |
| } |
| if (potential_probes == 0) { return peer->max_tp; } |
| ++peer->probes; |
| ++peer->tx_stats_map[probe_idx].probes_total; |
| return probe_idx; |
| } |
| |
| tx_vec_idx_t GetTxVector(Peer* peer, bool needs_reliability, const ProbeSequence& probe_sequence) { |
| if (needs_reliability) { return peer->max_probability; } |
| if (peer->num_pkt_until_next_probe > 0) { |
| --peer->num_pkt_until_next_probe; |
| return peer->max_tp; |
| } |
| peer->num_pkt_until_next_probe = kProbeInterval - 1; |
| return GetNextProbe(peer, probe_sequence); |
| } |
| |
| tx_vec_idx_t MinstrelRateSelector::GetTxVectorIdx(const FrameControl& fc, |
| const common::MacAddr& peer_addr, |
| uint32_t flags) { |
| Peer* peer = GetPeer(peer_addr); |
| if (peer == nullptr) { return kErpStartIdx + kErpNumTxVector - 1; } |
| if (!fc.IsData()) { return peer->basic_max_probability; } |
| const bool needs_reliability = (flags & WLAN_TX_INFO_FLAGS_FAVOR_RELIABILITY) != 0; |
| return GetTxVector(peer, needs_reliability, probe_sequence_); |
| } |
| |
| void MinstrelRateSelector::UpdateStats() { |
| for (auto peer_addr : outdated_peers_) { |
| auto* peer = GetPeer(peer_addr); |
| if (peer != nullptr) { |
| UpdateStatsPeer(peer); |
| } else { |
| ZX_DEBUG_ASSERT(0); |
| } |
| } |
| outdated_peers_.clear(); |
| } |
| |
| Peer* MinstrelRateSelector::GetPeer(const common::MacAddr& addr) { |
| auto iter = peer_map_.find(addr); |
| if (iter != peer_map_.end()) { return &(iter->second); } |
| return nullptr; |
| } |
| |
| const Peer* MinstrelRateSelector::GetPeer(const common::MacAddr& addr) const { |
| auto iter = peer_map_.find(addr); |
| if (iter != peer_map_.end()) { return &(iter->second); } |
| return nullptr; |
| } |
| |
| zx_status_t MinstrelRateSelector::GetListToFidl(wlan_minstrel::Peers* peers_fidl) const { |
| peers_fidl->peers.resize(peer_map_.size()); |
| size_t idx = 0; |
| for (const auto& iter : peer_map_) { |
| peers_fidl->peers[idx].resize(common::kMacAddrLen); |
| iter.first.CopyTo(peers_fidl->peers[idx].data()); |
| ++idx; |
| } |
| return ZX_OK; |
| } |
| |
| wlan_minstrel::StatsEntry TxStats::ToFidl() const { |
| return wlan_minstrel::StatsEntry{ |
| .tx_vector_idx = tx_vector_idx, |
| .tx_vec_desc = debug::Describe(tx_vector_idx), |
| .success_cur = success_cur, |
| .attempts_cur = attempts_cur, |
| .probability = probability, |
| .cur_tp = cur_tp, |
| .success_total = success_total, |
| .attempts_total = attempts_total, |
| .probes_total = probes_total, |
| .probe_cycles_skipped = probe_cycles_skipped, |
| }; |
| } |
| |
| zx_status_t MinstrelRateSelector::GetStatsToFidl(const common::MacAddr& peer_addr, |
| wlan_minstrel::Peer* peer_fidl) const { |
| const auto* peer = GetPeer(peer_addr); |
| if (peer == nullptr) { return ZX_ERR_NOT_FOUND; } |
| |
| peer_addr.CopyTo(peer_fidl->mac_addr.mutable_data()); |
| |
| std::lock_guard<std::mutex> guard(*peer->update_lock); |
| peer_fidl->entries.resize(peer->tx_stats_map.size()); |
| |
| size_t idx = 0; |
| for (const auto& [_, tx_stats] : peer->tx_stats_map) { |
| peer_fidl->entries[idx++] = tx_stats.ToFidl(); |
| } |
| peer_fidl->max_tp = peer->max_tp; |
| peer_fidl->max_probability = peer->max_probability; |
| peer_fidl->basic_highest = peer->basic_highest; |
| peer_fidl->basic_max_probability = peer->basic_max_probability; |
| peer_fidl->probes = peer->probes; |
| |
| return ZX_OK; |
| } |
| |
| bool MinstrelRateSelector::IsActive() const { |
| return next_update_ != TimeoutId{}; |
| } |
| |
| namespace debug { |
| // This macro requires char buf[] and size_t offset variable definitions |
| // in each function. |
| #define BUFFER(args...) \ |
| do { \ |
| offset += snprintf(buf + offset, sizeof(buf) - offset, " " args); \ |
| if (offset >= sizeof(buf)) { \ |
| snprintf(buf + sizeof(buf) - 12, 12, " ..(trunc)"); \ |
| offset = sizeof(buf); \ |
| } \ |
| } while (false) |
| |
| std::string Describe(const TxStats& tx_stats) { |
| char buf[256]; |
| size_t offset = 0; |
| |
| BUFFER("%s", Describe(tx_stats.tx_vector_idx).c_str()); |
| BUFFER("succ_c: %zu", tx_stats.success_cur); |
| BUFFER("att_c: %zu", tx_stats.attempts_cur); |
| BUFFER("succ_t: %zu", tx_stats.success_total); |
| BUFFER("att_t: %zu", tx_stats.attempts_total); |
| BUFFER("prob: %f", tx_stats.probability); |
| BUFFER("tp: %f", tx_stats.cur_tp); |
| BUFFER("probes: %zu", tx_stats.probes_total); |
| BUFFER("probe_cycle_skipped: %hhu", tx_stats.probe_cycles_skipped); |
| |
| return std::string(buf, buf + offset); |
| } |
| #undef BUFFER |
| } // namespace debug |
| |
| } // namespace wlan |