Skip to content
This repository has been archived by the owner on May 4, 2021. It is now read-only.

Commit

Permalink
chg: bwfile: Test KeyValues in a bandwidth file
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
juga0 committed Mar 22, 2020
1 parent b4f12e4 commit 4d726f5
Show file tree
Hide file tree
Showing 4 changed files with 8,547 additions and 0 deletions.
19 changes: 19 additions & 0 deletions sbws/core/bwfile_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/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="Banwidth file path.")

args = parser.parse_args()

header_health = BwFile.load(args.file_path)
header_health.report


if __name__ == "__main__":
main()
274 changes: 274 additions & 0 deletions sbws/lib/bwfile_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
"""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, # 36000
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_lt_max}
recent_priority_list_count >= min, {self.is_priority_list_gte_min}
recent_priority_list_count < max, {self.is_priority_list_lt_max}
recent_priority_relay_count >= min, {self.is_priority_relay_gte_min}
recent_priority_relay_count < max, {self.is_priority_relay_lt_max}
""" + (
"recent_measurement_attempt_count >= min, "
"{self.is_measurement_attempt_gte_min}\n"
"recent_measurement_attempt_count < max, "
"{self.is_measurement_attempt_lt_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 = """
recent_measurement_attempt_count >= recent_measurement_failure_count,
{is_relay_recent_consensus_count_lte_recent_consensus_count}
relay_recent_priority_list_count <= relay_recent_consensus_count,
{self.is_relay_recent_priority_list_count_lte_relay_recent_consensus_count}
relay_recent_consensus_count <= recent_consensus_count,
{self.is_relay_recent_consensus_count_lte_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_lt_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_lt_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_lt_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_lt_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))
Loading

0 comments on commit 4d726f5

Please sign in to comment.