| // Copyright (c) 2014 The Chromium OS 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 "shill/mac80211_monitor.h" |
| |
| #include <algorithm> |
| |
| #include <base/files/file_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "shill/logging.h" |
| #include "shill/metrics.h" |
| #include "shill/net/shill_time.h" |
| |
| namespace shill { |
| |
| using std::string; |
| using std::vector; |
| |
| namespace Logging { |
| static auto kModuleLogScope = ScopeLogger::kWiFi; |
| static string ObjectID(Mac80211Monitor *m) { return m->link_name(); } |
| } |
| |
| // statics |
| // At 17-25 bytes per queue, this accommodates 80 queues. |
| // ath9k has 4 queues, and WP2 has 16 queues. |
| const size_t Mac80211Monitor::kMaxQueueStateSizeBytes = 2048; |
| const char Mac80211Monitor::kQueueStatusPathFormat[] = |
| "/sys/kernel/debug/ieee80211/%s/queues"; |
| const char Mac80211Monitor::kWakeQueuesPathFormat[] = |
| "/sys/kernel/debug/ieee80211/%s/wake_queues"; |
| const time_t Mac80211Monitor::kQueueStatePollIntervalSeconds = 30; |
| const time_t Mac80211Monitor::kMinimumTimeBetweenWakesSeconds = 60; |
| |
| Mac80211Monitor::Mac80211Monitor( |
| EventDispatcher *dispatcher, |
| const string &link_name, |
| size_t queue_length_limit, |
| const base::Closure &on_repair_callback, |
| Metrics *metrics) |
| : time_(Time::GetInstance()), |
| dispatcher_(dispatcher), |
| link_name_(link_name), |
| queue_length_limit_(queue_length_limit), |
| on_repair_callback_(on_repair_callback), |
| metrics_(metrics), |
| phy_name_("phy-unknown"), |
| last_woke_queues_monotonic_seconds_(0), |
| is_running_(false), |
| is_device_connected_(false), |
| weak_ptr_factory_(this) { |
| CHECK(time_); |
| CHECK(dispatcher_); |
| CHECK(metrics_); |
| } |
| |
| Mac80211Monitor::~Mac80211Monitor() { |
| Stop(); |
| } |
| |
| void Mac80211Monitor::Start(const string &phy_name) { |
| SLOG(this, 2) << __func__ << " on " << link_name_ << " (" << phy_name << ")"; |
| CHECK(!is_running_); |
| phy_name_ = phy_name; |
| queue_state_file_path_ = base::FilePath( |
| base::StringPrintf(kQueueStatusPathFormat, phy_name.c_str())); |
| wake_queues_file_path_ = base::FilePath( |
| base::StringPrintf(kWakeQueuesPathFormat, phy_name.c_str())); |
| last_woke_queues_monotonic_seconds_ = 0; |
| StartTimer(); |
| is_running_ = true; |
| } |
| |
| void Mac80211Monitor::Stop() { |
| SLOG(this, 2) << __func__ << " on " << link_name_ << " (" << phy_name_ << ")"; |
| StopTimer(); |
| is_running_ = false; |
| } |
| |
| void Mac80211Monitor::UpdateConnectedState(bool new_state) { |
| SLOG(this, 2) << __func__ << " (new_state=" << new_state << ")"; |
| is_device_connected_ = new_state; |
| } |
| |
| void Mac80211Monitor::StartTimer() { |
| SLOG(this, 2) << __func__; |
| if (check_queues_callback_.IsCancelled()) { |
| check_queues_callback_.Reset( |
| Bind(&Mac80211Monitor::WakeQueuesIfNeeded, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| dispatcher_->PostDelayedTask(check_queues_callback_.callback(), |
| kQueueStatePollIntervalSeconds * 1000); |
| } |
| |
| void Mac80211Monitor::StopTimer() { |
| SLOG(this, 2) << __func__; |
| check_queues_callback_.Cancel(); |
| } |
| |
| void Mac80211Monitor::WakeQueuesIfNeeded() { |
| SLOG(this, 2) << __func__ << " on " << link_name_ << " (" << phy_name_ << ")"; |
| CHECK(is_running_); |
| StartTimer(); // Always re-arm timer. |
| |
| if (is_device_connected_) { |
| SLOG(this, 5) << "Skipping queue check: device is connected."; |
| return; |
| } |
| |
| string queue_state_string; |
| if (!base::ReadFileToString(queue_state_file_path_, &queue_state_string, |
| kMaxQueueStateSizeBytes)) { |
| LOG(WARNING) << __func__ << ": incomplete read on " |
| << queue_state_file_path_.value(); |
| } |
| |
| uint32_t stuck_flags = |
| CheckAreQueuesStuck(ParseQueueState(queue_state_string)); |
| SLOG(this, 2) << __func__ << " stuck_flags=" << stuck_flags; |
| if (!(stuck_flags & kStopFlagPowerSave)) { |
| if (stuck_flags) { |
| LOG(INFO) << "Skipping wake: stuck_flags is " |
| << std::showbase << std::hex << stuck_flags |
| << " (require " << kStopFlagPowerSave << " to wake)." |
| << std::dec << std::noshowbase; |
| } |
| return; |
| } |
| |
| time_t now_monotonic_seconds = 0; |
| if (!time_->GetSecondsMonotonic(&now_monotonic_seconds)) { |
| LOG(WARNING) << "Skipping reset: failed to get monotonic time"; |
| return; |
| } |
| |
| time_t elapsed = now_monotonic_seconds - last_woke_queues_monotonic_seconds_; |
| if (elapsed < kMinimumTimeBetweenWakesSeconds) { |
| LOG(WARNING) << "Skipping reset " |
| << "(min interval=" << kMinimumTimeBetweenWakesSeconds |
| << ", elapsed=" << elapsed << ")"; |
| return; |
| } |
| |
| LOG(WARNING) << "Queues appear stuck; waking."; |
| if (base::WriteFile(wake_queues_file_path_, "", sizeof("")) < 0) { |
| LOG(ERROR) << "Failed to write to " << wake_queues_file_path_.value() |
| << ": " << strerror(errno); |
| return; |
| } |
| |
| if (!on_repair_callback_.is_null()) { |
| on_repair_callback_.Run(); |
| } |
| |
| last_woke_queues_monotonic_seconds_ = now_monotonic_seconds; |
| } |
| |
| uint32_t Mac80211Monitor::CheckAreQueuesStuck( |
| const vector<QueueState> &queue_states) { |
| size_t max_stuck_queue_len = 0; |
| uint32_t stuck_flags = 0; |
| for (const auto &queue_state : queue_states) { |
| if (queue_state.queue_length < queue_length_limit_) { |
| SLOG(this, 5) << __func__ |
| << " skipping queue of length " << queue_state.queue_length |
| << " (threshold is " << queue_length_limit_ << ")"; |
| continue; |
| } |
| if (!queue_state.stop_flags) { |
| SLOG(this, 5) << __func__ |
| << " skipping queue of length " << queue_state.queue_length |
| << " (not stopped)"; |
| continue; |
| } |
| stuck_flags = stuck_flags | queue_state.stop_flags; |
| max_stuck_queue_len = |
| std::max(max_stuck_queue_len, queue_state.queue_length); |
| } |
| |
| if (max_stuck_queue_len >= queue_length_limit_) { |
| LOG(WARNING) << "max queue length is " << max_stuck_queue_len; |
| } |
| |
| if (stuck_flags) { |
| for (unsigned int i = 0; i < kStopReasonMax; ++i) { |
| auto stop_reason = static_cast<QueueStopReason>(i); |
| if (stuck_flags & GetFlagForReason(stop_reason)) { |
| metrics_->SendEnumToUMA(Metrics::kMetricWifiStoppedTxQueueReason, |
| stop_reason, |
| kStopReasonMax); |
| } |
| } |
| |
| metrics_->SendToUMA(Metrics::kMetricWifiStoppedTxQueueLength, |
| max_stuck_queue_len, |
| Metrics::kMetricWifiStoppedTxQueueLengthMin, |
| Metrics::kMetricWifiStoppedTxQueueLengthMax, |
| Metrics::kMetricWifiStoppedTxQueueLengthNumBuckets); |
| } |
| |
| return stuck_flags; |
| } |
| |
| // example input: |
| // 01: 0x00000000/0 |
| // ... |
| // 04: 0x00000000/0 |
| // |
| // static |
| vector<Mac80211Monitor::QueueState> |
| Mac80211Monitor::ParseQueueState(const string &state_string) { |
| vector<string> queue_state_strings; |
| vector <QueueState> queue_states; |
| base::SplitString(state_string, '\n', &queue_state_strings); |
| |
| if (queue_state_strings.empty()) { |
| return queue_states; |
| } |
| |
| // Trailing newline generates empty string as last element. |
| // Discard that empty string if present. |
| if (queue_state_strings.back().empty()) { |
| queue_state_strings.pop_back(); |
| } |
| |
| for (const auto &queue_state : queue_state_strings) { |
| // Example |queue_state|: "00: 0x00000000/10". |
| vector<string> queuenum_and_state; |
| base::SplitString(queue_state, ':', &queuenum_and_state); |
| if (queuenum_and_state.size() != 2) { |
| LOG(WARNING) << __func__ << ": parse error on " << queue_state; |
| continue; |
| } |
| |
| // Example |queuenum_and_state|: {"00", "0x00000000/10"}. |
| vector<string> stopflags_and_length; |
| base::SplitString(queuenum_and_state[1], '/', &stopflags_and_length); |
| if (stopflags_and_length.size() != 2) { |
| LOG(WARNING) << __func__ << ": parse error on " << queue_state; |
| continue; |
| } |
| |
| // Example |stopflags_and_length|: {"0x00000000", "10"}. |
| size_t queue_number; |
| uint32_t stop_flags; |
| size_t queue_length; |
| if (!base::StringToSizeT(queuenum_and_state[0], &queue_number)) { |
| LOG(WARNING) << __func__ << ": parse error on " << queue_state; |
| continue; |
| } |
| if (!base::HexStringToUInt(stopflags_and_length[0], &stop_flags)) { |
| LOG(WARNING) << __func__ << ": parse error on " << queue_state; |
| continue; |
| } |
| if (!base::StringToSizeT(stopflags_and_length[1], &queue_length)) { |
| LOG(WARNING) << __func__ << ": parse error on " << queue_state; |
| continue; |
| } |
| queue_states.emplace_back(queue_number, stop_flags, queue_length); |
| } |
| |
| return queue_states; |
| } |
| |
| // static |
| Mac80211Monitor::QueueStopFlag Mac80211Monitor::GetFlagForReason( |
| QueueStopReason reason) { |
| switch (reason) { |
| case kStopReasonDriver: |
| return kStopFlagDriver; |
| case kStopReasonPowerSave: |
| return kStopFlagPowerSave; |
| case kStopReasonChannelSwitch: |
| return kStopFlagChannelSwitch; |
| case kStopReasonAggregation: |
| return kStopFlagAggregation; |
| case kStopReasonSuspend: |
| return kStopFlagSuspend; |
| case kStopReasonBufferAdd: |
| return kStopFlagBufferAdd; |
| case kStopReasonChannelTypeChange: |
| return kStopFlagChannelTypeChange; |
| } |
| |
| // The switch statement above is exhaustive (-Wswitch will complain |
| // if it is not). But |reason| might be outside the defined range for |
| // the enum, so we need this to keep the compiler happy. |
| return kStopFlagInvalid; |
| } |
| |
| } // namespace shill |