GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Commit 5cffd46e authored by juga  's avatar juga 💬

chg: bwfile: Test KeyValues in a bandwidth file

Added:
- library to check whether the KeyValues make sense
- test an example bandwidth file
- a command to check an arbitrary bandwidth file

Finally, doing something with all these KeyValues!

(Quarantine day 7th)
parent 4e2ccf3c
#!/usr/bin/env python3
""""""
import argparse
from sbws.lib.bwfile_health import BwFile
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--file-path", help="Bandwidth file path.")
args = parser.parse_args()
header_health = BwFile.load(args.file_path)
header_health.report
if __name__ == "__main__":
main()
"""Expected bandwidth file values for KeyValues."""
import logging
from stem import descriptor
from sbws.globals import (
PERIOD_DAYS,
FRACTION_RELAYS,
MAX_RECENT_PRIORITY_RELAY_COUNT, # 48000
MAX_RECENT_PRIORITY_LIST_COUNT, # 120
MAX_RECENT_CONSENSUS_COUNT, # 120
)
from sbws.lib.v3bwfile import HEADER_INT_KEYS, BWLINE_KEYS_V1_4
logging.basicConfig(level=logging.INFO,)
logger = logging.getLogger(__name__)
# Based on observation
MAX_HOURS_PRIORITY_LIST = 5
MIN_RECENT_CONSENSUS_COUNT = PERIOD_DAYS * 12 # 60
MIN_RELAYS = 6000
# 24
MIN_RECENT_PRIORITY_LIST_COUNT = PERIOD_DAYS * 24 / MAX_HOURS_PRIORITY_LIST
MIN_RELAYS_PER_PRIORITY_LIST = int(MIN_RELAYS * FRACTION_RELAYS) # 300
# 7200
MIN_RECENT_PRIORITY_RELAY_COUNT = (
MIN_RECENT_PRIORITY_LIST_COUNT * MIN_RELAYS_PER_PRIORITY_LIST
)
# If the number of attempts is not equal to the number of relays being in the
# priority list, there's a bug.
MIN_RECENT_MEASUREMENT_ATTEMPT_COUNT = MIN_RECENT_PRIORITY_RELAY_COUNT
MAX_RECENT_MEASUREMENT_ATTEMPT_COUNT = MAX_RECENT_PRIORITY_RELAY_COUNT
# noqa
REPORT_TEMPLATE_BWFILE = (
"sum(relay_recent_measurement_attempt_count) "
"<= recent_measurement_attempt_count, "
"{self.is_sum_relay_recent_measurement_attempt_count_lte_recent_measurement_attempt_count}\n" # noqa
)
REPORT_TEMPLATE_BWHEADER = """
Header,
recent_consensus_count >= min, {self.is_consensus_gte_min}
recent_consensus_count <= max, {self.is_consensus_lte_max}
recent_priority_list_count >= min, {self.is_priority_list_gte_min}
recent_priority_list_count <= max, {self.is_priority_list_lte_max}
recent_priority_relay_count >= min, {self.is_priority_relay_gte_min}
recent_priority_relay_count <= max, {self.is_priority_relay_lte_max}
""" + (
"recent_measurement_attempt_count >= min, "
"{self.is_measurement_attempt_gte_min}\n"
"recent_measurement_attempt_count <= max, "
"{self.is_measurement_attempt_lte_max}\n"
"recent_measurement_attempt_count == recent_priority_relay_count, "
"{self.is_attempt_e_priority_relay}\n"
"recent_measurement_attempt_count >= total excluded, "
"{self.is_attempt_gte_failure_exclude}\n"
)
REPORT_TEMPLATE_BWLINES = """
relays correct, {self.are_bwlines_correct}
"""
REPORT_TEMPLATE_BWLINE = """
relay_recent_measurement_attempt_count <= relay_recent_priority_list_count,
{self.is_relay_recent_measurement_attempt_count_lte_relay_recent_priority_list_count}
relay_recent_priority_list_count <= relay_recent_consensus_count,
{self.is_relay_recent_priority_list_count_lte_relay_recent_consensus_count}
"""
class BwFile:
def __init__(self, header, bwlines):
self.header = BwHeader(header)
self.bwlines = [BwLine(line) for line in bwlines]
@classmethod
def load(cls, file_path):
logger.info("Parsing content of %s.", file_path)
document = descriptor.parse_file(file_path)
bwfiles = list(document)
if bwfiles:
# When parsing one file, there is only 1 bwfile
bwfile = bwfiles[0]
return cls(bwfile.header, bwfile.measurements.values())
@property
def sum_relay_recent_measurement_attempt_count(self):
return sum(
[l.relay_recent_measurement_attempt_count for l in self.bwlines]
)
@property
def is_sum_relay_recent_measurement_attempt_count_lte_recent_measurement_attempt_count( # noqa
self,
):
return (
self.sum_relay_recent_measurement_attempt_count
<= self.header.recent_measurement_attempt_count
)
@property
def are_bwlines_correct(self):
return not list(filter(lambda x: not x.is_correct, self.bwlines))
@property
def is_correct(self):
methods = [m for m in dir(self) if m.startswith("is_")]
methods.remove("is_correct")
return not list(filter(lambda x: not getattr(self, x), methods))
@property
def report(self):
print(REPORT_TEMPLATE_BWFILE.format(self=self))
self.header.report
print(REPORT_TEMPLATE_BWLINES.format(self=self))
class BwLine:
def __init__(self, line):
for k, v in line.items():
if k in BWLINE_KEYS_V1_4:
setattr(self, k, int(v))
else:
setattr(self, k, v)
@property
def is_relay_recent_priority_list_count_lte_relay_recent_consensus_count(
self,
):
return (
self.relay_recent_priority_list_count
<= self.relay_in_recent_consensus_count
)
@property
def is_relay_recent_measurement_attempt_count_lte_relay_recent_priority_list_count( # noqa
self,
):
return (
self.relay_recent_measurement_attempt_count
<= self.relay_recent_priority_list_count
)
def is_relay_recent_consensus_count_lte_recent_consensus_count(
self, recent_consensus_count
):
return self.relay_in_recent_consensus_count <= recent_consensus_count
@property
def is_correct(self):
methods = [m for m in dir(self) if m.startswith("is_")]
methods.remove("is_correct")
return not list(filter(lambda x: not getattr(self, x), methods))
@property
def report(self):
print(REPORT_TEMPLATE_BWLINE.format(self=self))
class BwHeader:
def __init__(self, header):
for k, v in header.items():
if k in HEADER_INT_KEYS:
setattr(self, k, int(v))
else:
setattr(self, k, v)
# logger.info(self.__dict__)
@classmethod
def load(cls, file_path):
logger.info("Parsing content of %s.", file_path)
document = descriptor.parse_file(file_path)
bwfiles = list(document)
if bwfiles:
bwfile = bwfiles[0]
return cls(bwfile.header)
@property
def is_consensus_lte_max(self):
return self.recent_consensus_count <= MAX_RECENT_CONSENSUS_COUNT
@property
def is_consensus_gte_min(self):
return self.recent_consensus_count >= MIN_RECENT_CONSENSUS_COUNT
@property
def is_priority_list_lte_max(self):
return (
self.recent_priority_list_count <= MAX_RECENT_PRIORITY_LIST_COUNT
)
@property
def is_priority_list_gte_min(self):
return (
self.recent_priority_list_count >= MIN_RECENT_PRIORITY_LIST_COUNT
)
@property
def is_priority_relay_lte_max(self):
return (
self.recent_priority_relay_count <= MAX_RECENT_PRIORITY_RELAY_COUNT
)
@property
def is_priority_relay_gte_min(self):
return (
self.recent_priority_relay_count >= MIN_RECENT_PRIORITY_RELAY_COUNT
)
@property
def is_measurement_attempt_gte_min(self):
return (
self.recent_measurement_attempt_count
>= MIN_RECENT_MEASUREMENT_ATTEMPT_COUNT
)
@property
def is_measurement_attempt_lte_max(self):
return (
self.recent_measurement_attempt_count
<= MAX_RECENT_MEASUREMENT_ATTEMPT_COUNT
)
@property
def is_attempt_e_priority_relay(self):
return (
self.recent_measurement_attempt_count
== self.recent_priority_relay_count
)
@property
def is_attempt_gte_failure(self):
return (
self.recent_measurement_attempt_count
>= self.recent_measurement_failure_count
)
@property
def total_excluded(self):
return sum(
[
self.recent_measurements_excluded_error_count,
self.recent_measurements_excluded_few_count,
self.recent_measurements_excluded_near_count,
self.recent_measurements_excluded_old_count,
]
)
@property
def total_excluded_failure(self):
return sum(
[self.total_excluded, self.recent_measurement_failure_count]
)
@property
def is_attempt_gte_failure_exclude(self):
return (
self.recent_measurement_attempt_count
>= self.total_excluded_failure
)
@property
def is_correct(self):
methods = [m for m in dir(self) if m.startswith("is_")]
methods.remove("is_correct")
return not list(filter(lambda x: not getattr(self, x), methods))
@property
def report(self):
print(REPORT_TEMPLATE_BWHEADER.format(self=self))
This diff is collapsed.
"""Test that the KeyValues in a bandwidth file make sense."""
import os.path
from sbws.lib.bwfile_health import BwFile
def test_bwfile_health(root_data_path):
bwfile = BwFile.load(os.path.join(
root_data_path, "2020-03-22-08-35-00-bandwidth"
))
assert bwfile.header.is_correct
assert bwfile.are_bwlines_correct
assert bwfile.is_correct
def test_bwlines_health(capsys, root_data_path):
bwfile = BwFile.load(os.path.join(
root_data_path, "2020-03-22-08-35-00-bandwidth"
))
out = (
"\nrelay_recent_measurement_attempt_count <= relay_recent_priority_list_count,\n" # noqa
"True\n"
"relay_recent_priority_list_count <= relay_recent_consensus_count,\n"
"True\n\n"
)
for bwline in bwfile.bwlines:
bwline.report
assert out == capsys.readouterr().out
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment