blob: 1a4919def4b10b46cbd5aad3c7080b00177471b0 [file] [log] [blame]
# Copyright 2017 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Network metrics."""
from __future__ import absolute_import
import collections
import logging
import socket
from chromite.third_party.infra_libs import ts_mon
import psutil # pylint: disable=import-error
from chromite.lib import metrics
logger = logging.getLogger(__name__)
_BOOT_TIME = psutil.boot_time()
_net_bytes_metric = metrics.CounterMetric(
"dev/net/bytes",
start_time=_BOOT_TIME,
description="Number of bytes up/down on interface.",
)
_net_packets_metric = metrics.CounterMetric(
"dev/net/packets",
start_time=_BOOT_TIME,
description="Number of packets up/down on interface.",
)
_net_errors_metric = metrics.CounterMetric(
"dev/net/errors",
start_time=_BOOT_TIME,
description="Total number of errors up/down on interface.",
)
_net_dropped_metric = metrics.CounterMetric(
"dev/net/dropped",
start_time=_BOOT_TIME,
description="Total number of dropped packages up/down on interface.",
)
_net_if_isup_metric = metrics.BooleanMetric(
"dev/net/isup", description="Whether interface is up or down."
)
_net_if_duplex_metric = metrics.GaugeMetric(
"dev/net/duplex",
description="Whether interface supports full or half duplex.",
)
_net_if_speed_metric = metrics.GaugeMetric(
"dev/net/speed", description="Network interface speed in Mb."
)
_net_if_mtu_metric = metrics.GaugeMetric(
"dev/net/mtu", description="Network interface MTU in B."
)
def collect_net_info():
"""Collect network metrics."""
_collect_net_io_duplex_counters()
_collect_net_if_stats()
_collect_fqdn()
_collect_net_if_addrs()
# Network IO metrics to collect
_IOMetric = collections.namedtuple(
"_IOMetric", ["metric", "up_counter_name", "down_counter_name"]
)
_net_io_duplex_metrics = (
_IOMetric(
metric=_net_bytes_metric,
up_counter_name="bytes_sent",
down_counter_name="bytes_recv",
),
_IOMetric(
metric=_net_packets_metric,
up_counter_name="packets_sent",
down_counter_name="packets_recv",
),
_IOMetric(
metric=_net_errors_metric,
up_counter_name="errout",
down_counter_name="errin",
),
_IOMetric(
metric=_net_dropped_metric,
up_counter_name="dropout",
down_counter_name="dropin",
),
)
def _collect_net_io_duplex_counters():
"""Collect metrics for network IO duplex counters."""
for nic, counters in _net_io_iter():
fields = {"interface": nic}
for (
metric,
up_counter_name,
down_counter_name,
) in _net_io_duplex_metrics:
try:
metric.set(
getattr(counters, up_counter_name),
fields=dict(direction="up", **fields),
)
metric.set(
getattr(counters, down_counter_name),
fields=dict(direction="down", **fields),
)
except ts_mon.MonitoringDecreasingValueError as ex:
# This normally shouldn't happen, but might if the network
# driver module is reloaded, so log an error and continue
# instead of raising an exception.
logger.warning(str(ex))
def _net_io_iter():
"""Generate network IO information."""
nics = psutil.net_io_counters(pernic=True)
for nic, counters in nics.items():
if _is_virtual_netif(nic):
continue
yield nic, counters
_net_if_metrics = (
(_net_if_isup_metric, "isup"),
(_net_if_duplex_metric, "duplex"),
(_net_if_speed_metric, "speed"),
(_net_if_mtu_metric, "mtu"),
)
def _collect_net_if_stats():
"""Collect metrics for network interface stats."""
for nic, stats in psutil.net_if_stats().items():
if _is_virtual_netif(nic):
continue
fields = {"interface": nic}
for metric, counter_name in _net_if_metrics:
metric.set(getattr(stats, counter_name), fields=fields)
_net_if_addrs_metrics = metrics.StringMetric(
"dev/net/address",
description="Network address of physical network interfaces.",
)
_family_field_strings = {
psutil.AF_LINK: "AF_LINK",
socket.AF_INET: "AF_INET",
socket.AF_INET6: "AF_INET6",
}
def _collect_net_if_addrs():
"""Collects network addresses as metrics."""
for nic, addresses in psutil.net_if_addrs().items():
if _is_virtual_netif(nic):
continue
for address in addresses:
fields = {
"interface": nic,
"family": _family_field_strings.get(address.family, "UNKNOWN"),
}
_net_if_addrs_metrics.set(address.address, fields)
def _is_virtual_netif(nic):
"""Return whether the network interface is virtual."""
# TODO(ayatane): Use a different way of identifying virtual interfaces
return nic.startswith("veth")
_fqdn_metric = metrics.StringMetric("net/fqdn", description="FQDN")
def _collect_fqdn():
fqdn = socket.getfqdn()
logging.debug("Got FQDN: %s", fqdn)
_fqdn_metric.set(fqdn)