tobiko/tobiko/shell/ping/_statistics.py

201 lines
6.5 KiB
Python

# Copyright (c) 2019 Red Hat, Inc.
#
# All Rights Reserved.
#
# 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.
from __future__ import absolute_import
from __future__ import division
import re
from oslo_log import log
import netaddr
import tobiko
LOG = log.getLogger(__name__)
def parse_ping_statistics(output, begin_interval=None, end_interval=None):
lines = output.split('\n')
line_it = iter(lines)
try:
source, destination = parse_ping_header(line_it)
except Exception as ex:
LOG.debug('Error parsing ping output header: %s', ex)
source = destination = None
try:
transmitted, received, errors = parse_ping_footer(line_it)
except Exception as ex:
LOG.debug('Error parsing ping output footer: %s', ex)
transmitted = received = errors = 0
return PingStatistics(source=source, destination=destination,
transmitted=transmitted, received=received,
undelivered=errors, end_interval=end_interval,
begin_interval=begin_interval)
def parse_ping_header(line_it):
for line in line_it:
if line.startswith('PING '):
header = line
break
else:
raise ValueError('Ping output header not found')
header_fields = [f.strip() for f in header.split()]
header_fields = [(f[:-1] if f.endswith(':') else f)
for f in header_fields]
destination = header_fields[2]
while destination and destination[0] == '(':
destination = destination[1:]
while destination and destination[-1] == ')':
destination = destination[:-1]
destination = netaddr.IPAddress(destination)
field_it = iter(header_fields[3:])
source = None
for field in field_it:
if field == 'from':
source = next(field_it).strip()
if source.endswith(':'):
source = source[:-1]
source = netaddr.IPAddress(source)
break
return source, destination
def parse_ping_footer(line_it):
for line in line_it:
if line.startswith('--- ') and line.endswith(' ping statistics ---'):
# parse footer
footer = next(line_it).strip()
break
else:
raise ValueError('Ping output footer not found')
transmitted = received = errors = 0
for field in footer.split(','):
try:
if 'transmitted' in field:
transmitted = extract_integer(field)
elif 'received' in field:
received = extract_integer(field)
elif 'error' in field:
errors = extract_integer(field)
except Exception as ex:
LOG.exception('Error parsing ping output footer: %s', ex)
return transmitted, received, errors
def extract_integer(field):
for number in extract_integers(field):
return number
raise ValueError("Integer not found in {!r}".format(field))
MATCH_NUMBERS_RE = re.compile('([0-9]).')
def extract_integers(field):
for match_obj in MATCH_NUMBERS_RE.finditer(field):
yield int(field[match_obj.start():match_obj.end()])
class PingStatistics(object):
"""Ping command statistics
"""
def __init__(self, source=None, destination=None, transmitted=0,
received=0, undelivered=0, begin_interval=None,
end_interval=None):
self.source = source
self.destination = destination
self.transmitted = transmitted
self.received = received
self.undelivered = undelivered
self.begin_interval = begin_interval
self.end_interval = end_interval
@property
def unreceived(self):
return max(0, self.transmitted - self.received)
@property
def delivered(self):
return max(0, self.transmitted - self.undelivered)
@property
def loss(self):
transmitted = max(0, float(self.transmitted))
if transmitted:
return float(self.unreceived) / transmitted
else:
return 0.
def __bool__(self):
return bool(self.received)
def __add__(self, other):
begin_interval = min(i for i in [self.begin_interval,
other.begin_interval] if i)
end_interval = max(i for i in [self.end_interval,
other.end_interval] if i)
return PingStatistics(
source=self.source or other.source,
destination=self.destination or other.destination,
transmitted=self.transmitted + other.transmitted,
received=self.received + other.received,
undelivered=self.undelivered + other.undelivered,
begin_interval=begin_interval,
end_interval=end_interval)
def __repr__(self):
return "PingStatistics({!s})".format(
", ".join("{!s}={!r}".format(k, v)
for k, v in self.__dict__.items()))
def assert_transmitted(self):
if not self.transmitted:
tobiko.fail("{transmitted!r} package(s) has been transmitted "
"to {destination!r}",
transmitted=self.transmitted,
destination=self.destination)
def assert_not_transmitted(self):
if self.transmitted:
tobiko.fail("{transmitted!r} package(s) has been transmitted to "
"{destination!r}",
transmitted=self.transmitted,
destination=self.destination)
def assert_replied(self):
if not self.received:
tobiko.fail("{received!r} reply package(s) has been received from "
"{destination!r}",
received=self.received,
destination=self.destination)
def assert_not_replied(self):
if self.received:
tobiko.fail("{received!r} reply package(s) has been received from "
"{destination!r}",
received=self.received,
destination=self.destination)