blob: 3be4025ff93420bdf2cb1b79b1796450997c4493 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/multicast_metrics.h"
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <base/containers/contains.h>
#include <base/containers/fixed_flat_map.h>
#include <base/containers/fixed_flat_set.h>
#include <base/containers/flat_map.h>
#include <base/containers/flat_set.h>
#include <base/logging.h>
#include <base/time/time.h>
#include <metrics/metrics_library.h>
#include "patchpanel/metrics.h"
#include "patchpanel/multicast_counters_service.h"
#include "patchpanel/shill_client.h"
namespace patchpanel {
namespace {
// Maximum recorded packet count for the multicast metrics, equivalent to 30
// packets per second.
constexpr int kPacketCountMax = 30 * kMulticastPollDelay.InSeconds();
constexpr int kPacketCountBuckets = 100;
std::optional<MulticastMetrics::Type> ShillDeviceTypeToMulticastMetricsType(
ShillClient::Device::Type type) {
switch (type) {
case ShillClient::Device::Type::kEthernet:
return MulticastMetrics::Type::kEthernet;
case ShillClient::Device::Type::kWifi:
return MulticastMetrics::Type::kWiFi;
default:
// Invalid multicast metrics type.
return std::nullopt;
}
}
std::string MulticastMetricsTypeToString(MulticastMetrics::Type type) {
switch (type) {
case MulticastMetrics::Type::kTotal:
return "Total";
case MulticastMetrics::Type::kEthernet:
return "Ethernet";
case MulticastMetrics::Type::kWiFi:
return "WiFi";
case MulticastMetrics::Type::kARC:
return "ARC";
}
}
// Map of multicast protocol to Ethernet metrics name.
static constexpr auto kEthernetMetricNames = base::MakeFixedFlatMap<
std::optional<MulticastCountersService::MulticastProtocolType>,
std::string_view>({
{std::nullopt, kMulticastEthernetConnectedCountMetrics},
{MulticastCountersService::MulticastProtocolType::kMdns,
kMulticastEthernetMDNSConnectedCountMetrics},
{MulticastCountersService::MulticastProtocolType::kSsdp,
kMulticastEthernetSSDPConnectedCountMetrics},
});
// Map of multicast protocol to WiFi metrics name.
static constexpr auto kWiFiMetricNames = base::MakeFixedFlatMap<
std::optional<MulticastCountersService::MulticastProtocolType>,
std::string_view>({
{std::nullopt, kMulticastWiFiConnectedCountMetrics},
{MulticastCountersService::MulticastProtocolType::kMdns,
kMulticastWiFiMDNSConnectedCountMetrics},
{MulticastCountersService::MulticastProtocolType::kSsdp,
kMulticastWiFiSSDPConnectedCountMetrics},
});
// Map of pair of ARC multicast forwarder status and multicast protocol to
// metrics name.
static constexpr auto kARCMetricNames = base::MakeFixedFlatMap<
std::pair<bool, MulticastCountersService::MulticastProtocolType>,
std::string_view>({
{{/*arc_fwd_enabled=*/true,
MulticastCountersService::MulticastProtocolType::kMdns},
kMulticastARCWiFiMDNSActiveCountMetrics},
{{/*arc_fwd_enabled=*/true,
MulticastCountersService::MulticastProtocolType::kSsdp},
kMulticastARCWiFiSSDPActiveCountMetrics},
{{/*arc_fwd_enabled=*/false,
MulticastCountersService::MulticastProtocolType::kMdns},
kMulticastARCWiFiMDNSInactiveCountMetrics},
{{/*arc_fwd_enabled=*/false,
MulticastCountersService::MulticastProtocolType::kSsdp},
kMulticastARCWiFiSSDPInactiveCountMetrics},
});
// Get metrics name for UMA.
std::optional<std::string_view> GetMetricsName(
MulticastMetrics::Type type,
std::optional<MulticastCountersService::MulticastProtocolType> protocol,
std::optional<bool> arc_fwd_enabled) {
switch (type) {
case MulticastMetrics::Type::kTotal:
if (protocol) {
// No need to report specific multicast protocol metrics for total.
return std::nullopt;
}
return kMulticastTotalCountMetrics;
case MulticastMetrics::Type::kEthernet:
if (!kEthernetMetricNames.contains(protocol)) {
return std::nullopt;
}
return kEthernetMetricNames.at(protocol);
case MulticastMetrics::Type::kWiFi:
if (!kWiFiMetricNames.contains(protocol)) {
return std::nullopt;
}
return kWiFiMetricNames.at(protocol);
case MulticastMetrics::Type::kARC:
if (!arc_fwd_enabled || !protocol) {
// Only report specific multicast protocol metrics for ARC.
return std::nullopt;
}
std::pair<bool, MulticastCountersService::MulticastProtocolType> key = {
*arc_fwd_enabled, *protocol};
if (!kARCMetricNames.contains(key)) {
return std::nullopt;
}
return kARCMetricNames.at(key);
}
}
// Contains accepted multicast metrics type for each multicast counters
// technology.
static constexpr auto kAccepted = base::MakeFixedFlatSet<
std::pair<MulticastCountersService::MulticastTechnologyType,
MulticastMetrics::Type>>({
{MulticastCountersService::MulticastTechnologyType::kEthernet,
MulticastMetrics::Type::kTotal},
{MulticastCountersService::MulticastTechnologyType::kEthernet,
MulticastMetrics::Type::kEthernet},
{MulticastCountersService::MulticastTechnologyType::kWifi,
MulticastMetrics::Type::kTotal},
{MulticastCountersService::MulticastTechnologyType::kWifi,
MulticastMetrics::Type::kWiFi},
{MulticastCountersService::MulticastTechnologyType::kWifi,
MulticastMetrics::Type::kARC},
});
} // namespace
MulticastMetrics::MulticastMetrics(MulticastCountersService* counters_service,
MetricsLibraryInterface* metrics)
: counters_service_(counters_service), metrics_lib_(metrics) {
pollers_.emplace(Type::kTotal, std::make_unique<Poller>(Type::kTotal, this));
pollers_.emplace(Type::kEthernet,
std::make_unique<Poller>(Type::kEthernet, this));
pollers_.emplace(Type::kWiFi, std::make_unique<Poller>(Type::kWiFi, this));
pollers_.emplace(Type::kARC, std::make_unique<Poller>(Type::kARC, this));
}
void MulticastMetrics::Start(MulticastMetrics::Type type,
std::string_view ifname) {
pollers_[type]->Start(ifname);
}
void MulticastMetrics::Stop(MulticastMetrics::Type type,
std::string_view ifname) {
pollers_[type]->Stop(ifname);
}
void MulticastMetrics::OnIPConfigsChanged(const ShillClient::Device& device) {
auto type = ShillDeviceTypeToMulticastMetricsType(device.type);
if (!type) {
return;
}
// Handle network technology specific pollers.
if (device.IsConnected()) {
Start(*type, device.ifname);
} else {
Stop(*type, device.ifname);
}
// Handle ARC pollers.
if (device.type != ShillClient::Device::Type::kWifi) {
return;
}
if (device.IsConnected()) {
Start(Type::kARC, device.ifname);
} else {
Stop(Type::kARC, device.ifname);
}
}
void MulticastMetrics::OnPhysicalDeviceAdded(
const ShillClient::Device& device) {
auto type = ShillDeviceTypeToMulticastMetricsType(device.type);
if (!type) {
return;
}
// Handle network technology specific pollers.
if (device.IsConnected()) {
Start(*type, device.ifname);
}
// Handle ARC pollers.
if (device.type != ShillClient::Device::Type::kWifi) {
return;
}
if (device.IsConnected()) {
Start(Type::kARC, device.ifname);
}
}
void MulticastMetrics::OnPhysicalDeviceRemoved(
const ShillClient::Device& device) {
auto type = ShillDeviceTypeToMulticastMetricsType(device.type);
if (!type) {
return;
}
// Handle network technology specific pollers.
Stop(*type, device.ifname);
// Handle ARC pollers.
if (device.type != ShillClient::Device::Type::kWifi) {
return;
}
Stop(Type::kARC, device.ifname);
}
void MulticastMetrics::OnARCStarted() {
pollers_[Type::kARC]->UpdateARCState(/*running=*/true);
}
void MulticastMetrics::OnARCStopped() {
pollers_[Type::kARC]->UpdateARCState(/*running=*/false);
}
void MulticastMetrics::OnARCWiFiForwarderStarted() {
pollers_[Type::kARC]->UpdateARCForwarderState(/*enabled=*/true);
}
void MulticastMetrics::OnARCWiFiForwarderStopped() {
pollers_[Type::kARC]->UpdateARCForwarderState(/*enabled=*/false);
}
std::optional<
std::map<MulticastCountersService::MulticastProtocolType, uint64_t>>
MulticastMetrics::GetCounters(Type type) {
if (!counters_service_) {
LOG(ERROR) << "Empty multicast counters service";
return std::nullopt;
}
auto counters = counters_service_->GetCounters();
if (!counters) {
return std::nullopt;
}
std::map<MulticastCountersService::MulticastProtocolType, uint64_t> ret = {
{MulticastCountersService::MulticastProtocolType::kMdns, 0},
{MulticastCountersService::MulticastProtocolType::kSsdp, 0}};
for (const auto& counter : *counters) {
const MulticastCountersService::CounterKey& key = counter.first;
if (kAccepted.contains({key.second, type})) {
ret[key.first] += counter.second;
}
}
return ret;
}
void MulticastMetrics::SendPacketCountMetrics(
Type type,
uint64_t packet_count,
std::optional<MulticastCountersService::MulticastProtocolType> protocol,
std::optional<bool> arc_fwd_enabled) {
if (!metrics_lib_) {
LOG(ERROR) << "Metrics client is not valid";
return;
}
auto metrics_name = GetMetricsName(type, protocol, arc_fwd_enabled);
if (!metrics_name) {
LOG(ERROR) << "Trying to send invalid metrics";
return;
}
if (packet_count > kPacketCountMax) {
packet_count = kPacketCountMax;
}
metrics_lib_->SendToUMA(std::string(*metrics_name),
static_cast<int>(packet_count),
/*min=*/0, kPacketCountMax, kPacketCountBuckets);
}
void MulticastMetrics::SendARCActiveTimeMetrics(
base::TimeDelta multicast_enabled_duration,
base::TimeDelta wifi_enabled_duration) {
if (!metrics_lib_) {
LOG(ERROR) << "Metrics client is not valid";
return;
}
if (wifi_enabled_duration.InSeconds() == 0) {
return;
}
metrics_lib_->SendPercentageToUMA(
kMulticastActiveTimeMetrics,
static_cast<int>((100 * multicast_enabled_duration.InSeconds() /
wifi_enabled_duration.InSeconds())));
}
MulticastMetrics::Poller::Poller(MulticastMetrics::Type type,
MulticastMetrics* metrics)
: type_(type), metrics_(metrics) {}
void MulticastMetrics::Poller::Start(std::string_view ifname) {
// Do nothing if poll is already started.
if (base::Contains(ifnames_, ifname)) {
return;
}
ifnames_.insert(std::string(ifname));
if (ifnames_.size() > 1) {
return;
}
// For ARC, poll is only started whenever there is at least one WiFi interface
// connected and ARC is running. Keep track of the states.
if (type_ == MulticastMetrics::Type::kARC && !arc_running_) {
return;
}
StartTimer();
total_arc_multicast_enabled_duration_ = base::Seconds(0);
total_arc_wifi_connection_duration_ = base::Seconds(0);
}
void MulticastMetrics::Poller::Stop(std::string_view ifname) {
// Do nothing if poll is already stopped.
auto num_removed = ifnames_.erase(std::string(ifname));
if (num_removed == 0 || !ifnames_.empty()) {
return;
}
if (type_ == MulticastMetrics::Type::kARC && !arc_running_) {
return;
}
StopTimer();
UpdateARCActiveTimeDuration(IsARCForwardingEnabled());
if (type_ == MulticastMetrics::Type::kARC) {
metrics_->SendARCActiveTimeMetrics(total_arc_multicast_enabled_duration_,
total_arc_wifi_connection_duration_);
}
}
void MulticastMetrics::Poller::UpdateARCState(bool running) {
if (arc_running_ == running) {
return;
}
arc_running_ = running;
// Do nothing if there is no active WiFi device.
if (ifnames_.empty()) {
return;
}
if (arc_running_) {
StartTimer();
} else {
StopTimer();
}
}
void MulticastMetrics::Poller::UpdateARCForwarderState(bool enabled) {
if (arc_fwd_enabled_ == enabled) {
return;
}
arc_fwd_enabled_ = enabled;
if (!arc_running_) {
return;
}
// We add all time intervals between ARC multicast forwarder state update
// to wifi connection duration, and only add those between enable and disable
// of ARC multicast forwarder to multicast enabled duration.
// Since update active time duration is based on previous ARC forwarder
// state, negate enable state here.
UpdateARCActiveTimeDuration(!enabled);
// Restart polling to emit different metrics.
StopTimer();
StartTimer();
}
void MulticastMetrics::Poller::StartTimer() {
if (!metrics_) {
return;
}
auto packet_counts = metrics_->GetCounters(type_);
if (!packet_counts) {
LOG(ERROR) << "Failed to fetch multicast packet counts";
return;
}
packet_counts_ = *packet_counts;
last_record_timepoint_ = base::TimeTicks::Now();
timer_.Start(FROM_HERE, kMulticastPollDelay, this,
&MulticastMetrics::Poller::Record);
}
void MulticastMetrics::Poller::StopTimer() {
timer_.Stop();
packet_counts_.clear();
}
void MulticastMetrics::Poller::Record() {
if (!metrics_) {
return;
}
auto packet_counts = metrics_->GetCounters(type_);
if (!packet_counts) {
LOG(ERROR) << "Failed to get multicast packet counts for "
<< MulticastMetricsTypeToString(type_);
return;
}
// Send specific multicast protocol packet count metrics.
uint64_t total_packet_count = 0;
for (const auto& packet_count : *packet_counts) {
uint64_t count = packet_count.second - packet_counts_[packet_count.first];
total_packet_count += count;
if (type_ == Type::kTotal) {
// No need to report specific multicast protocol metrics for total.
continue;
}
metrics_->SendPacketCountMetrics(type_, count, packet_count.first,
arc_fwd_enabled_);
}
packet_counts_ = *packet_counts;
if (type_ == Type::kARC) {
// Update active time duration based on ARC forwarder state.
UpdateARCActiveTimeDuration(IsARCForwardingEnabled());
return;
}
// Send total packet count metrics. This is not sent for |type_| == kARC.
metrics_->SendPacketCountMetrics(type_, total_packet_count);
}
base::flat_set<std::string> MulticastMetrics::Poller::ifnames() {
return ifnames_;
}
bool MulticastMetrics::Poller::IsTimerRunning() {
return timer_.IsRunning();
}
bool MulticastMetrics::Poller::IsARCForwardingEnabled() {
return arc_fwd_enabled_;
}
void MulticastMetrics::Poller::UpdateARCActiveTimeDuration(
bool prev_arc_multicast_fwd_running) {
auto duration = base::TimeTicks::Now() - last_record_timepoint_;
last_record_timepoint_ = base::TimeTicks::Now();
total_arc_wifi_connection_duration_ += duration;
if (prev_arc_multicast_fwd_running) {
total_arc_multicast_enabled_duration_ += duration;
}
}
} // namespace patchpanel