1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-08-06 22:32:54 +02:00

feat(iftop): add iftop-scanning (#484)

* feat(iftop): add iftop-scanning

this is not even an MVP, but I would like it to exist to allow per client json aggregation

also, a future use is a stream response

* fix typos and test first regex

* add more iftop fun

* Update iftop.py

* add tests and json

Signed-off-by: Ron Green <11993626+georgettica@users.noreply.github.com>

* feat: make work and add tests

Signed-off-by: Ron Green <11993626+georgettica@users.noreply.github.com>

* add completion

* change schema for query looping

* fix: tests

* fix review comments

* feat: add byte parsing

* add no-port to options

* remove completion and format dep

Signed-off-by: Ron Green <11993626+georgettica@users.noreply.github.com>

* Update setup.py

* Update iftop.py

---------

Signed-off-by: Ron Green <11993626+georgettica@users.noreply.github.com>
Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
This commit is contained in:
Ron Green
2023-12-08 04:22:53 +02:00
committed by GitHub
parent f1e0cec9d6
commit 0e7ebf4dc1
8 changed files with 1088 additions and 0 deletions

623
jc/parsers/iftop.py Normal file
View File

@ -0,0 +1,623 @@
"""jc - JSON Convert `iftop` command output parser
Some of `iftop` options are supported.
Usage (cli):
$ iftop -i <device> -t -P -s 1 | jc --iftop
$ iftop -i <device> -t -B -s1 | jc --iftop
Usage (module):
import jc
result = jc.parse('iftop', iftop_command_output)
Schema:
[
{
"device": string,
"ip_address": string,
"mac_address": string,
"clients": [
{
"index": integer,
"connections": [
{
"host_name": string,
"host_port": string, # can be service or missing
"last_2s": string,
"last_10s": string,
"last_40s": string,
"cumulative": string,
"direction": string
}
]
}
]
"total_send_rate": {
"last_2s": string,
"last_10s": string,
"last_40s": string
}
"total_receive_rate": {
"last_2s": string,
"last_10s": string,
"last_40s": string
}
"total_send_and_receive_rate": {
"last_2s": string,
"last_10s": string,
"last_40s": string
}
"peak_rate": {
"last_2s": string,
"last_10s": string,
"last_40s": string
}
"cumulative_rate": {
"last_2s": string,
"last_10s": string,
"last_40s": string
}
Examples:
$ iftop -i eno0 -t -P -s 1 | jc --iftop -p -r
[
{
"device": "enp0s3",
"ip_address": "10.10.15.129",
"mac_address": "11:22:33:44:55:66",
"clients": [
{
"index": 1,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "ssh",
"last_2s": "448b",
"last_10s": "448b",
"last_40s": "448b",
"cumulative": "112B",
"direction": "send"
},
{
"host_name": "10.10.15.72",
"host_port": "40876",
"last_2s": "208b",
"last_10s": "208b",
"last_40s": "208b",
"cumulative": "52B",
"direction": "receive"
}
]
}
],
"total_send_rate": {
"last_2s": "448b",
"last_10s": "448b",
"last_40s": "448b"
},
"total_receive_rate": {
"last_2s": "208b",
"last_10s": "208b",
"last_40s": "208b"
},
"total_send_and_receive_rate": {
"last_2s": "656b",
"last_10s": "656b",
"last_40s": "656b"
},
"peak_rate": {
"last_2s": "448b",
"last_10s": "208b",
"last_40s": "656b"
},
"cumulative_rate": {
"last_2s": "112B",
"last_10s": "52B",
"last_40s": "164B"
}
}
]
"""
import re
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
from collections import namedtuple
from numbers import Number
class info:
"""Provides parser metadata (version, author, etc.)"""
version = "0.1"
description = "`iftop` command parser"
author = "Ron Green"
author_email = "11993626+georgettica@users.noreply.github.com"
compatible = ["linux"]
tags = ["command"]
__version__ = info.version
def _process(proc_data: List[JSONDictType], quiet: bool = False) -> List[JSONDictType]:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
List of Dictionaries. Structured to conform to the schema.
"""
string_to_bytes_fields = ["last_2s", "last_10s", "last_40s", "cumulative"]
one_nesting = [
"total_send_rate",
"total_receive_rate",
"total_send_and_receive_rate",
"peak_rate",
"cumulative_rate",
]
if not proc_data:
return proc_data
for entry in proc_data:
# print(f"{entry=}")
for entry_key in entry:
# print(f"{entry_key=}")
if entry_key in one_nesting:
# print(f"{entry[entry_key]=}")
for one_nesting_item_key in entry[entry_key]:
# print(f"{one_nesting_item_key=}")
if one_nesting_item_key in string_to_bytes_fields:
entry[entry_key][one_nesting_item_key] = humanfriendly_parse_size(entry[entry_key][one_nesting_item_key])
elif entry_key == "clients":
for client in entry[entry_key]:
# print(f"{client=}")
if "connections" not in client:
continue
for connection in client["connections"]:
# print(f"{connection=}")
for connection_key in connection:
# print(f"{connection_key=}")
if connection_key in string_to_bytes_fields:
connection[connection_key] = humanfriendly_parse_size(connection[connection_key])
return proc_data
# Named tuples to define units of size.
SizeUnit = namedtuple('SizeUnit', 'divider, symbol, name')
CombinedUnit = namedtuple('CombinedUnit', 'decimal, binary')
# Differences between Python 2 and 3.
try:
# Python 2.
basestring = basestring
except (ImportError, NameError):
# Python 3.
basestring = str
def humanfriendly_is_string(value):
"""
Check if a value is a :func:`python2:basestring` (in Python 2) or :class:`python3:str` (in Python 3) object.
:param value: The value to check.
:returns: :data:`True` if the value is a string, :data:`False` otherwise.
"""
return isinstance(value, basestring)
# Common disk size units in binary (base-2) and decimal (base-10) multiples.
disk_size_units = (
CombinedUnit(SizeUnit(1000**1, 'KB', 'kilobyte'), SizeUnit(1024**1, 'KiB', 'kibibyte')),
CombinedUnit(SizeUnit(1000**2, 'MB', 'megabyte'), SizeUnit(1024**2, 'MiB', 'mebibyte')),
CombinedUnit(SizeUnit(1000**3, 'GB', 'gigabyte'), SizeUnit(1024**3, 'GiB', 'gibibyte')),
CombinedUnit(SizeUnit(1000**4, 'TB', 'terabyte'), SizeUnit(1024**4, 'TiB', 'tebibyte')),
CombinedUnit(SizeUnit(1000**5, 'PB', 'petabyte'), SizeUnit(1024**5, 'PiB', 'pebibyte')),
CombinedUnit(SizeUnit(1000**6, 'EB', 'exabyte'), SizeUnit(1024**6, 'EiB', 'exbibyte')),
CombinedUnit(SizeUnit(1000**7, 'ZB', 'zettabyte'), SizeUnit(1024**7, 'ZiB', 'zebibyte')),
CombinedUnit(SizeUnit(1000**8, 'YB', 'yottabyte'), SizeUnit(1024**8, 'YiB', 'yobibyte')),
)
class HumanfriendlyInvalidSize(Exception):
pass
def humanfriendly_parse_size(size, binary=False):
"""
Parse a human readable data size and return the number of bytes.
:param size: The human readable file size to parse (a string).
:param binary: :data:`True` to use binary multiples of bytes (base-2) for
ambiguous unit symbols and names, :data:`False` to use
decimal multiples of bytes (base-10).
:returns: The corresponding size in bytes (an integer).
:raises: :exc:`InvalidSize` when the input can't be parsed.
This function knows how to parse sizes in bytes, kilobytes, megabytes,
gigabytes, terabytes and petabytes. Some examples:
>>> from humanfriendly import parse_size
>>> parse_size('42')
42
>>> parse_size('13b')
13
>>> parse_size('5 bytes')
5
>>> parse_size('1 KB')
1000
>>> parse_size('1 kilobyte')
1000
>>> parse_size('1 KiB')
1024
>>> parse_size('1 KB', binary=True)
1024
>>> parse_size('1.5 GB')
1500000000
>>> parse_size('1.5 GB', binary=True)
1610612736
"""
tokens = humanfriendly_tokenize(size)
if tokens and isinstance(tokens[0], Number):
# Get the normalized unit (if any) from the tokenized input.
normalized_unit = tokens[1].lower() if len(tokens) == 2 and humanfriendly_is_string(tokens[1]) else ''
# If the input contains only a number, it's assumed to be the number of
# bytes. The second token can also explicitly reference the unit bytes.
if len(tokens) == 1 or normalized_unit.startswith('b'):
return int(tokens[0])
# Otherwise we expect two tokens: A number and a unit.
if normalized_unit:
# Convert plural units to singular units, for details:
# https://github.com/xolox/python-humanfriendly/issues/26
normalized_unit = normalized_unit.rstrip('s')
for unit in disk_size_units:
# First we check for unambiguous symbols (KiB, MiB, GiB, etc)
# and names (kibibyte, mebibyte, gibibyte, etc) because their
# handling is always the same.
if normalized_unit in (unit.binary.symbol.lower(), unit.binary.name.lower()):
return int(tokens[0] * unit.binary.divider)
# Now we will deal with ambiguous prefixes (K, M, G, etc),
# symbols (KB, MB, GB, etc) and names (kilobyte, megabyte,
# gigabyte, etc) according to the caller's preference.
if (normalized_unit in (unit.decimal.symbol.lower(), unit.decimal.name.lower()) or
normalized_unit.startswith(unit.decimal.symbol[0].lower())):
return int(tokens[0] * (unit.binary.divider if binary else unit.decimal.divider))
# We failed to parse the size specification.
msg = "Failed to parse size! (input %r was tokenized as %r)"
raise HumanfriendlyInvalidSize(format(msg, size, tokens))
# taken from https://github.com/xolox/python-humanfriendly/blob/master/humanfriendly/text.py#L402
# so there are no dependencies on the humanfriendly package
def humanfriendly_tokenize(text):
tokenized_input = []
for token in re.split(r'(\d+(?:\.\d+)?)', text):
token = token.strip()
if re.match(r'\d+\.\d+', token):
tokenized_input.append(float(token))
elif token.isdigit():
tokenized_input.append(int(token))
elif token:
tokenized_input.append(token)
return tokenized_input
def parse(data: str, raw: bool = False, quiet: bool = False) -> List[JSONDictType]:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List[Dict] = []
interface_item: Dict = {}
clients: List = []
before_arrow = r"\s+(?P<index>\d+)\s+(?P<host_name>[^\s]+):(?P<host_port>[^\s]+)\s+"
before_arrow_no_port = r"\s+(?P<index>\d+)\s+(?P<host_name>[^\s]+)\s+"
after_arrow_before_newline = r"\s+(?P<send_last_2s>[^\s]+)\s+(?P<send_last_10s>[^\s]+)\s+(?P<send_last_40s>[^\s]+)\s+(?P<send_cumulative>[^\s]+)"
newline_before_arrow = r"\s+(?P<receive_ip>.+):(?P<receive_port>\w+)\s+"
newline_before_arrow_no_port = r"\s+(?P<receive_ip>.+)\s+"
after_arrow_till_end = r"\s+(?P<receive_last_2s>[^\s]+)\s+(?P<receive_last_10s>[^\s]+)\s+(?P<receive_last_40s>[^\s]+)\s+(?P<receive_cumulative>[^\s]+)"
re_linux_clients_before_newline = re.compile(
rf"{before_arrow}=>{after_arrow_before_newline}"
)
re_linux_clients_before_newline_no_port = re.compile(
rf"{before_arrow_no_port}=>{after_arrow_before_newline}"
)
re_linux_clients_after_newline_no_port = re.compile(
rf"{newline_before_arrow_no_port}<={after_arrow_till_end}"
)
re_linux_clients_after_newline = re.compile(
rf"{newline_before_arrow}<={after_arrow_till_end}"
)
re_total_send_rate = re.compile(
r"Total send rate:\s+(?P<total_send_rate_last_2s>[^\s]+)\s+(?P<total_send_rate_last_10s>[^\s]+)\s+(?P<total_send_rate_last_40s>[^\s]+)"
)
re_total_receive_rate = re.compile(
r"Total receive rate:\s+(?P<total_receive_rate_last_2s>[^\s]+)\s+(?P<total_receive_rate_last_10s>[^\s]+)\s+(?P<total_receive_rate_last_40s>[^\s]+)"
)
re_total_send_and_receive_rate = re.compile(
r"Total send and receive rate:\s+(?P<total_send_and_receive_rate_last_2s>[^\s]+)\s+(?P<total_send_and_receive_rate_last_10s>[^\s]+)\s+(?P<total_send_and_receive_rate_last_40s>[^\s]+)"
)
re_peak_rate = re.compile(
r"Peak rate \(sent/received/total\):\s+(?P<peak_rate_sent>[^\s]+)\s+(?P<peak_rate_received>[^\s]+)\s+(?P<peak_rate_total>[^\s]+)"
)
re_cumulative_rate = re.compile(
r"Cumulative \(sent/received/total\):\s+(?P<cumulative_rate_sent>[^\s]+)\s+(?P<cumulative_rate_received>[^\s]+)\s+(?P<cumulative_rate_total>[^\s]+)"
)
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List[Dict] = []
current_client: Dict = {}
if not jc.utils.has_data(data):
return raw_output if raw else _process(raw_output, quiet=quiet)
is_previous_line_interface = False
saw_already_host_line = False
for line in filter(None, data.splitlines()):
if line.startswith("interface:"):
# Example:
# interface: enp0s3
interface_item["device"] = line.split(":")[1].strip()
elif line.startswith("IP address is:"):
# Example:
# IP address is: 10.10.15.129
interface_item["ip_address"] = line.split(":")[1].strip()
elif line.startswith("MAC address is:"):
# Example:
# MAC address is: 08:00:27:c0:4a:4f
# strip off the "MAC address is: " part
data_without_front = line.split(":")[1:]
# join the remaining parts back together
data_without_front = ":".join(data_without_front)
interface_item["mac_address"] = data_without_front.strip()
elif line.startswith("Listening on"):
# Example:
# Listening on enp0s3
pass
elif (
line.startswith("# Host name (port/service if enabled)")
and not saw_already_host_line
):
saw_already_host_line = True
# Example:
# # Host name (port/service if enabled) last 2s last 10s last 40s cumulative
pass
elif (
line.startswith("# Host name (port/service if enabled)")
and saw_already_host_line
):
old_interface_item, interface_item = interface_item, {}
interface_item.update(
{
"device": old_interface_item["device"],
"ip_address": old_interface_item["ip_address"],
"mac_address": old_interface_item["mac_address"],
}
)
elif "=>" in line and is_previous_line_interface and ":" in line:
# should not happen
pass
elif "=>" in line and not is_previous_line_interface and ":" in line:
# Example:
# 1 ubuntu-2004-clean-01:ssh => 448b 448b 448b 112B
is_previous_line_interface = True
match_raw = re_linux_clients_before_newline.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
current_client = {}
current_client["index"] = int(match_dict["index"])
current_client["connections"] = []
current_client_send = {
"host_name": match_dict["host_name"],
"host_port": match_dict["host_port"],
"last_2s": match_dict["send_last_2s"],
"last_10s": match_dict["send_last_10s"],
"last_40s": match_dict["send_last_40s"],
"cumulative": match_dict["send_cumulative"],
"direction": "send",
}
current_client["connections"].append(current_client_send)
# not adding yet as the receive part is not yet parsed
elif "=>" in line and not is_previous_line_interface and ":" not in line:
# should not happen
pass
elif "=>" in line and is_previous_line_interface and ":" not in line:
is_previous_line_interface = True
match_raw = re_linux_clients_before_newline_no_port.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
current_client = {}
current_client["index"] = int(match_dict["index"])
current_client["connections"] = []
current_client_send = {
"host_name": match_dict["host_name"],
"last_2s": match_dict["send_last_2s"],
"last_10s": match_dict["send_last_10s"],
"last_40s": match_dict["send_last_40s"],
"cumulative": match_dict["send_cumulative"],
"direction": "send",
}
current_client["connections"].append(current_client_send)
# not adding yet as the receive part is not yet parsed
elif "<=" in line and not is_previous_line_interface and ":" in line:
# should not happen
pass
elif "<=" in line and is_previous_line_interface and ":" in line:
# Example:
# 10.10.15.72:40876 <= 208b 208b 208b 52B
is_previous_line_interface = False
match_raw = re_linux_clients_after_newline.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
current_client_receive = {
"host_name": match_dict["receive_ip"],
"host_port": match_dict["receive_port"],
"last_2s": match_dict["receive_last_2s"],
"last_10s": match_dict["receive_last_10s"],
"last_40s": match_dict["receive_last_40s"],
"cumulative": match_dict["receive_cumulative"],
"direction": "receive",
}
current_client["connections"].append(current_client_receive)
clients.append(current_client)
elif "<=" in line and not is_previous_line_interface and ":" not in line:
# should not happen
pass
elif "<=" in line and is_previous_line_interface and ":" not in line:
# Example:
# 10.10.15.72:40876 <= 208b 208b 208b 52B
is_previous_line_interface = False
match_raw = re_linux_clients_after_newline_no_port.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
current_client_receive = {
"host_name": match_dict["receive_ip"],
"last_2s": match_dict["receive_last_2s"],
"last_10s": match_dict["receive_last_10s"],
"last_40s": match_dict["receive_last_40s"],
"cumulative": match_dict["receive_cumulative"],
"direction": "receive",
}
current_client["connections"].append(current_client_receive)
clients.append(current_client)
# check if all of the characters are dashes or equal signs
elif all(c == "-" for c in line):
pass
elif line.startswith("Total send rate"):
# Example:
# Total send rate: 448b 448b 448b
match_raw = re_total_send_rate.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
interface_item["total_send_rate"] = {}
interface_item["total_send_rate"].update(
{
"last_2s": match_dict["total_send_rate_last_2s"],
"last_10s": match_dict["total_send_rate_last_10s"],
"last_40s": match_dict["total_send_rate_last_40s"],
}
)
elif line.startswith("Total receive rate"):
# Example:
# Total receive rate: 208b 208b 208b
match_raw = re_total_receive_rate.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
interface_item["total_receive_rate"] = {}
interface_item["total_receive_rate"].update(
{
"last_2s": match_dict["total_receive_rate_last_2s"],
"last_10s": match_dict["total_receive_rate_last_10s"],
"last_40s": match_dict["total_receive_rate_last_40s"],
}
)
elif line.startswith("Total send and receive rate"):
# Example:
# Total send and receive rate: 656b 656b 656b
match_raw = re_total_send_and_receive_rate.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
interface_item["total_send_and_receive_rate"] = {}
interface_item["total_send_and_receive_rate"].update(
{
"last_2s": match_dict["total_send_and_receive_rate_last_2s"],
"last_10s": match_dict["total_send_and_receive_rate_last_10s"],
"last_40s": match_dict["total_send_and_receive_rate_last_40s"],
}
)
elif line.startswith("Peak rate"):
match_raw = re_peak_rate.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
interface_item["peak_rate"] = {}
interface_item["peak_rate"].update(
{
"last_2s": match_dict["peak_rate_sent"],
"last_10s": match_dict["peak_rate_received"],
"last_40s": match_dict["peak_rate_total"],
}
)
elif line.startswith("Cumulative"):
match_raw = re_cumulative_rate.match(line)
if not match_raw:
# this is a bug in iftop
#
continue
match_dict = match_raw.groupdict()
interface_item["cumulative_rate"] = {}
interface_item["cumulative_rate"].update(
{
"last_2s": match_dict["cumulative_rate_sent"],
"last_10s": match_dict["cumulative_rate_received"],
"last_40s": match_dict["cumulative_rate_total"],
}
)
elif all(c == "=" for c in line):
interface_item["clients"] = clients
clients = []
raw_output.append(interface_item.copy()) # keep the copy here as without it keeps the objects linked
else:
pass
return raw_output if raw else _process(raw_output, quiet=quiet)

View File

@ -0,0 +1,33 @@
[
{
"device": "enp0s3",
"ip_address": "10.10.15.129",
"mac_address": "08:00:27:c0:4a:4f",
"total_send_rate": {
"last_2s": 4820,
"last_10s": 4820,
"last_40s": 4820
},
"total_receive_rate": {
"last_2s": 16600,
"last_10s": 16600,
"last_40s": 16600
},
"total_send_and_receive_rate": {
"last_2s": 21400,
"last_10s": 21400,
"last_40s": 21400
},
"peak_rate": {
"last_2s": 4820,
"last_10s": 16600,
"last_40s": 21400
},
"cumulative_rate": {
"last_2s": 9630,
"last_10s": 33100,
"last_40s": 42800
},
"clients": []
}
]

View File

@ -0,0 +1,18 @@
interface: enp0s3
IP address is: 10.10.15.129
MAC address is: 08:00:27:c0:4a:4f
Listening on enp0s3
# Host name (port/service if enabled) last 2s last 10s last 40s cumulative
--------------------------------------------------------------------------------------------
1 ubuntu-2004-clean-01 => 4.82KB 4.82KB 4.82KB 9.63KB
10.10.15.72 <= 14.5KB 14.5KB 14.5KB 29.1KB
2 ubuntu-2004-clean-02 => 0B 0B 0B 0B
10.10.15.72 <= 2.02KB 2.02KB 2.02KB 4.04KB
--------------------------------------------------------------------------------------------
Total send rate: 4.82KB 4.82KB 4.82KB
Total receive rate: 16.6KB 16.6KB 16.6KB
Total send and receive rate: 21.4KB 21.4KB 21.4KB
--------------------------------------------------------------------------------------------
Peak rate (sent/received/total): 4.82KB 16.6KB 21.4KB
Cumulative (sent/received/total): 9.63KB 33.1KB 42.8KB
============================================================================================

View File

@ -0,0 +1,57 @@
[
{
"device": "enp0s3",
"ip_address": "10.10.15.129",
"mac_address": "08:00:27:c0:4a:4f",
"clients": [
{
"index": 1,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "ssh",
"last_2s": 448,
"last_10s": 448,
"last_40s": 448,
"cumulative": 112,
"direction": "send"
},
{
"host_name": "10.10.15.72",
"host_port": "40876",
"last_2s": 208,
"last_10s": 208,
"last_40s": 208,
"cumulative": 52,
"direction": "receive"
}
]
}
],
"total_send_rate": {
"last_2s": 448,
"last_10s": 448,
"last_40s": 448
},
"total_receive_rate": {
"last_2s": 208,
"last_10s": 208,
"last_40s": 208
},
"total_send_and_receive_rate": {
"last_2s": 656,
"last_10s": 656,
"last_40s": 656
},
"peak_rate": {
"last_2s": 448,
"last_10s": 208,
"last_40s": 656
},
"cumulative_rate": {
"last_2s": 112,
"last_10s": 52,
"last_40s": 164
}
}
]

View File

@ -0,0 +1,16 @@
interface: enp0s3
IP address is: 10.10.15.129
MAC address is: 08:00:27:c0:4a:4f
Listening on enp0s3
# Host name (port/service if enabled) last 2s last 10s last 40s cumulative
--------------------------------------------------------------------------------------------
1 ubuntu-2004-clean-01:ssh => 448b 448b 448b 112B
10.10.15.72:40876 <= 208b 208b 208b 52B
--------------------------------------------------------------------------------------------
Total send rate: 448b 448b 448b
Total receive rate: 208b 208b 208b
Total send and receive rate: 656b 656b 656b
--------------------------------------------------------------------------------------------
Peak rate (sent/received/total): 448b 208b 656b
Cumulative (sent/received/total): 112B 52B 164B
============================================================================================

View File

@ -0,0 +1,236 @@
[
{
"device": "enp0s3",
"ip_address": "10.10.15.129",
"mac_address": "08:00:27:c0:4a:4f",
"total_send_rate": {
"last_2s": 23200000,
"last_10s": 23200000,
"last_40s": 23200000
},
"total_receive_rate": {
"last_2s": 5650000,
"last_10s": 5650000,
"last_40s": 5650000
},
"total_send_and_receive_rate": {
"last_2s": 28800000,
"last_10s": 28800000,
"last_40s": 28800000
},
"peak_rate": {
"last_2s": 23200000,
"last_10s": 5650000,
"last_40s": 28800000
},
"cumulative_rate": {
"last_2s": 5790000,
"last_10s": 1410000,
"last_40s": 7200000
},
"clients": [
{
"index": 1,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "33222",
"last_2s": 4720,
"last_10s": 4720,
"last_40s": 4720,
"cumulative": 1180,
"direction": "send"
},
{
"host_name": "10.10.15.72",
"host_port": "https",
"last_2s": 1990000,
"last_10s": 1990000,
"last_40s": 1990000,
"cumulative": 508000,
"direction": "receive"
}
]
},
{
"index": 2,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "https",
"last_2s": 1980000,
"last_10s": 1980000,
"last_40s": 1980000,
"cumulative": 507000,
"direction": "send"
},
{
"host_name": "10.10.15.73",
"host_port": "34562",
"last_2s": 3170,
"last_10s": 3170,
"last_40s": 3170,
"cumulative": 811,
"direction": "receive"
}
]
}
]
},
{
"device": "enp0s3",
"ip_address": "10.10.15.129",
"mac_address": "08:00:27:c0:4a:4f",
"total_send_rate": {
"last_2s": 23200000,
"last_10s": 23200000,
"last_40s": 23200000
},
"total_receive_rate": {
"last_2s": 5650000,
"last_10s": 5650000,
"last_40s": 5650000
},
"total_send_and_receive_rate": {
"last_2s": 28800000,
"last_10s": 28800000,
"last_40s": 28800000
},
"peak_rate": {
"last_2s": 23200000,
"last_10s": 5650000,
"last_40s": 28800000
},
"cumulative_rate": {
"last_2s": 5790000,
"last_10s": 1410000,
"last_40s": 7200000
},
"clients": [
{
"index": 1,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "33222",
"last_2s": 4720,
"last_10s": 4720,
"last_40s": 4720,
"cumulative": 1180,
"direction": "send"
},
{
"host_name": "10.10.15.72",
"host_port": "https",
"last_2s": 1990000,
"last_10s": 1990000,
"last_40s": 1990000,
"cumulative": 508000,
"direction": "receive"
}
]
},
{
"index": 2,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "https",
"last_2s": 1980000,
"last_10s": 1980000,
"last_40s": 1980000,
"cumulative": 507000,
"direction": "send"
},
{
"host_name": "10.10.15.73",
"host_port": "34562",
"last_2s": 3170,
"last_10s": 3170,
"last_40s": 3170,
"cumulative": 811,
"direction": "receive"
}
]
}
]
},
{
"device": "enp0s3",
"ip_address": "10.10.15.129",
"mac_address": "08:00:27:c0:4a:4f",
"total_send_rate": {
"last_2s": 23200000,
"last_10s": 23200000,
"last_40s": 23200000
},
"total_receive_rate": {
"last_2s": 5650000,
"last_10s": 5650000,
"last_40s": 5650000
},
"total_send_and_receive_rate": {
"last_2s": 28800000,
"last_10s": 28800000,
"last_40s": 28800000
},
"peak_rate": {
"last_2s": 23200000,
"last_10s": 5650000,
"last_40s": 28800000
},
"cumulative_rate": {
"last_2s": 5790000,
"last_10s": 1410000,
"last_40s": 7200000
},
"clients": [
{
"index": 1,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "33222",
"last_2s": 4720,
"last_10s": 4720,
"last_40s": 4720,
"cumulative": 1180,
"direction": "send"
},
{
"host_name": "10.10.15.72",
"host_port": "https",
"last_2s": 1990000,
"last_10s": 1990000,
"last_40s": 1990000,
"cumulative": 508000,
"direction": "receive"
}
]
},
{
"index": 2,
"connections": [
{
"host_name": "ubuntu-2004-clean-01",
"host_port": "https",
"last_2s": 1980000,
"last_10s": 1980000,
"last_40s": 1980000,
"cumulative": 507000,
"direction": "send"
},
{
"host_name": "10.10.15.73",
"host_port": "34562",
"last_2s": 3170,
"last_10s": 3170,
"last_40s": 3170,
"cumulative": 811,
"direction": "receive"
}
]
}
]
}
]

View File

@ -0,0 +1,48 @@
interface: enp0s3
IP address is: 10.10.15.129
MAC address is: 08:00:27:c0:4a:4f
Listening on enp0s3
# Host name (port/service if enabled) last 2s last 10s last 40s cumulative
--------------------------------------------------------------------------------------------
1 ubuntu-2004-clean-01:33222 => 4.72Kb 4.72Kb 4.72Kb 1.18KB
10.10.15.72:https <= 1.99Mb 1.99Mb 1.99Mb 508KB
2 ubuntu-2004-clean-01:https => 1.98Mb 1.98Mb 1.98Mb 507KB
10.10.15.73:34562 <= 3.17Kb 3.17Kb 3.17Kb 811B
--------------------------------------------------------------------------------------------
Total send rate: 23.2Mb 23.2Mb 23.2Mb
Total receive rate: 5.65Mb 5.65Mb 5.65Mb
Total send and receive rate: 28.8Mb 28.8Mb 28.8Mb
--------------------------------------------------------------------------------------------
Peak rate (sent/received/total): 23.2Mb 5.65Mb 28.8Mb
Cumulative (sent/received/total): 5.79MB 1.41MB 7.20MB
============================================================================================
# Host name (port/service if enabled) last 2s last 10s last 40s cumulative
--------------------------------------------------------------------------------------------
1 ubuntu-2004-clean-01:33222 => 4.72Kb 4.72Kb 4.72Kb 1.18KB
10.10.15.72:https <= 1.99Mb 1.99Mb 1.99Mb 508KB
2 ubuntu-2004-clean-01:https => 1.98Mb 1.98Mb 1.98Mb 507KB
10.10.15.73:34562 <= 3.17Kb 3.17Kb 3.17Kb 811B
--------------------------------------------------------------------------------------------
Total send rate: 23.2Mb 23.2Mb 23.2Mb
Total receive rate: 5.65Mb 5.65Mb 5.65Mb
Total send and receive rate: 28.8Mb 28.8Mb 28.8Mb
--------------------------------------------------------------------------------------------
Peak rate (sent/received/total): 23.2Mb 5.65Mb 28.8Mb
Cumulative (sent/received/total): 5.79MB 1.41MB 7.20MB
============================================================================================
# Host name (port/service if enabled) last 2s last 10s last 40s cumulative
--------------------------------------------------------------------------------------------
1 ubuntu-2004-clean-01:33222 => 4.72Kb 4.72Kb 4.72Kb 1.18KB
10.10.15.72:https <= 1.99Mb 1.99Mb 1.99Mb 508KB
2 ubuntu-2004-clean-01:https => 1.98Mb 1.98Mb 1.98Mb 507KB
10.10.15.73:34562 <= 3.17Kb 3.17Kb 3.17Kb 811B
--------------------------------------------------------------------------------------------
Total send rate: 23.2Mb 23.2Mb 23.2Mb
Total receive rate: 5.65Mb 5.65Mb 5.65Mb
Total send and receive rate: 28.8Mb 28.8Mb 28.8Mb
--------------------------------------------------------------------------------------------
Peak rate (sent/received/total): 23.2Mb 5.65Mb 28.8Mb
Cumulative (sent/received/total): 5.79MB 1.41MB 7.20MB
============================================================================================

57
tests/test_iftop.py Normal file
View File

@ -0,0 +1,57 @@
import os
import unittest
import json
import jc.parsers.iftop
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
class MyTests(unittest.TestCase):
# input
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-20.10/iftop-b-n1.out'), 'r', encoding='utf-8') as f:
ubuntu_20_10_iftop_b_n1 = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-20.10/iftop-b-n3.out'), 'r', encoding='utf-8') as f:
ubuntu_20_10_iftop_b_n3 = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-20.10/iftop-b-n1-noport.out'), 'r', encoding='utf-8') as f:
ubuntu_20_10_iftop_b_n1_noport = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-20.10/iftop-b-n1.json'), 'r', encoding='utf-8') as f:
ubuntu_20_10_iftop_b_n1_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-20.10/iftop-b-n3.json'), 'r', encoding='utf-8') as f:
ubuntu_20_10_iftop_b_n3_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-20.10/iftop-b-n1-noport.json'), 'r', encoding='utf-8') as f:
ubuntu_20_10_iftop_b_n1_noport_json = json.loads(f.read())
def test_iftop_nodata(self):
"""
Test 'iftop -b' with no data
"""
self.assertEqual(jc.parsers.iftop.parse('', quiet=True), [])
def test_iftop_ubuntu_20_10(self):
"""
Test 'iftop -i <device> -t -P -s 1' with units as MiB on Ubuntu 20.10
"""
self.assertEqual(jc.parsers.iftop.parse(self.ubuntu_20_10_iftop_b_n1, quiet=True), self.ubuntu_20_10_iftop_b_n1_json)
def test_iftop_multiple_runs_ubuntu_20_10(self):
"""
Test 'iftop -i <device> -t -P -s 1' with units as MiB on Ubuntu 20.10
"""
self.assertEqual(jc.parsers.iftop.parse(self.ubuntu_20_10_iftop_b_n3, quiet=True), self.ubuntu_20_10_iftop_b_n3_json)
def test_iftop_ubuntu_20_10_no_port(self):
"""
Test 'iftop -i <device> -t -B -s 1' with units as MiB on Ubuntu 20.10
"""
self.assertEqual(jc.parsers.iftop.parse(self.ubuntu_20_10_iftop_b_n1_noport, quiet=True), self.ubuntu_20_10_iftop_b_n1_noport_json)
if __name__ == '__main__':
unittest.main()