// // Copyright (C) 2014 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 "shill/wifi/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), have_ever_read_queue_state_file_(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)) { if (have_ever_read_queue_state_file_) { LOG(WARNING) << __func__ << ": incomplete read on " << queue_state_file_path_.value(); } return; } have_ever_read_queue_state_file_ = true; 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) { PLOG(ERROR) << "Failed to write to " << wake_queues_file_path_.value(); 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<QueueState> queue_states; vector<string> queue_state_strings = base::SplitString( state_string, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); 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, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); 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], "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); 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