Files
RN-Praxis3/praxis3/test/util.py
T
2025-02-02 16:51:59 +01:00

424 lines
14 KiB
Python

import multiprocessing
import os
import random
import re
import signal
import string
from io import StringIO
import subprocess
import sys
import time
import xml.etree.ElementTree as ET
from collections import Counter
from typing import List, Union
import numpy as np
import pandas as pd
import requests
import shutil
import zmq
def init_tests(args):
random.seed()
distributor_exec = args["distributor"]
worker_exec = args["worker"]
no_cache = args["no_cache"]
cache_dir = "test_files/"
if args["cache_dir"] != "":
cache_dir = args["cache_dir"]
simple_delimiters = [", ", ". ", " "]
all_delimiters = [x + " " for x in string.punctuation]
filename_simple = "test_simple_text.txt"
filename_complex = "test_complex_text.txt"
base_port = 6001
base_port = check_ports_free(base_port)
word_list_path = cache_dir + "word_list.txt"
book_base_url = "https://www.gutenberg.org/cache/epub/"
book_urls = ["84/pg84.txt",
"145/pg145.txt",
"1342/pg1342.txt",
"1513/pg1513.txt",
"2701/pg2701.txt"]
# book_urls = ["84/pg84.txt",
# "1342/pg1342.txt",
# "16389/pg16389.txt",
# "50150/pg50150.txt",
# "67979/pg67979.txt",
# "75201/pg75201.txt"]
books_selected = [random.choice(book_urls)]
book_urls.remove(books_selected[0])
books_selected.append(random.choice(book_urls))
if not os.path.exists(cache_dir):
os.mkdir(cache_dir)
if not os.path.isfile(word_list_path):
download_word_list(word_list_path)
with open(word_list_path, "r") as f:
word_list = f.read().splitlines()
book_list = []
for b in books_selected:
book_path = cache_dir + b.split("/")[1]
existing_book_size = 1
if os.path.isfile(book_path):
existing_book_size = os.path.getsize(book_path)
if not os.path.isfile(book_path) or existing_book_size == 0:
book_url = book_base_url + b
download_book(book_url, book_path)
with open(book_path, "r") as f:
book_list.append(f.read().encode("ascii", errors="ignore"))
filename_book_1 = "book_1.txt"
filename_book_2 = "book_2.txt"
filename_interop = "interop_test.txt"
filename_valgrind = "valgrind_test.txt"
test_args = {"is_ubuntu20_eecs_system": is_ubuntu20_eecs(),
"distributor": distributor_exec,
"worker": worker_exec,
"no_cache": no_cache,
"cache_dir": cache_dir,
"simple_delimiters": simple_delimiters,
"all_delimiters": all_delimiters,
"filename_simple": filename_simple,
"filename_complex": filename_complex,
"base_port": base_port,
"word_list": word_list,
"books": book_list,
"filename_book_1": filename_book_1,
"filename_book_2": filename_book_2,
"filename_interop": filename_interop,
"filename_valgrind": filename_valgrind,
}
generate_test_files(test_args)
return test_args
# check if current system is Ubuntu 20 EECS test system
def is_ubuntu20_eecs():
return True
# required_operating_system = "Operating System: Ubuntu 20.04.6 LTS"
# required_domain_name = "eecsit.tu-berlin.de"
#
# hostnamectl = subprocess.Popen("hostnamectl", stdout=subprocess.PIPE)
# os_name = subprocess.check_output(["grep", "-o", "Operating System:.*"], stdin=hostnamectl.stdout).decode("utf-8").strip()
# hostnamectl.wait()
#
# domain_name = subprocess.check_output(["hostname", "-d"]).decode("utf-8").strip()
#
# return required_operating_system == os_name and required_domain_name == domain_name
# checks if the base port and all following 8 ports are actually free for testing
# if not, check the next 10
def check_ports_free(base_port):
if not shutil.which("netstat") or sys.platform != "linux":
print("netstat not found. "
"To ensure all used ports during testing are free, please install net-tools with: sudo apt install net-tools",
file=sys.stderr)
return base_port
proc = subprocess.Popen(["netstat -tuna | tail -n +2 | tr -s [:blank:] ','"], stdout=subprocess.PIPE, shell=True)
netstat_output, netstat_err = proc.communicate()
netstat_output = netstat_output.decode("ascii")
df = pd.read_csv(StringIO(netstat_output))
df = df["Local"]
df = df.apply(lambda x: x.split(":")[-1])
ports = np.arange(base_port, base_port + 8).tolist()
port_list = [str(x) for x in ports]
if sum((df == i).any() for i in port_list) > 0:
base_port += 10
base_port = check_ports_free(base_port)
return base_port
def download_word_list(output_path):
word_list_url = "https://raw.githubusercontent.com/dwyl/english-words/master/words_alpha.txt"
word_list = None
try:
url_content = requests.get(word_list_url, timeout=5)
word_list = url_content.text
except requests.exceptions.Timeout:
sys.exit("Failed to get word list. Can not run tests without it. Quitting...")
if word_list is None:
sys.exit("Failed to get word list. Can not run tests without it. Quitting...")
with open(output_path, "w") as f:
f.write(word_list)
def download_book(url, output_path):
# book_text = None
# try:
# url_content = requests.get(url, timeout=90, stream=True)
# book_text = url_content.text
# except requests.exceptions.Timeout:
# sys.exit("Failed to get book text. Can not run tests without it. Quitting...")
# if book_text is None:
# sys.exit("Failed to get book text. Can not run tests without it. Quitting...")
#
# with open(output_path, "w") as f:
# f.write(book_text)
#### code based on https://stackoverflow.com/a/77873699
session = requests.Session()
book = b""
expected_length = None
i = 0
while len(book) != expected_length and i < 15:
if len(book):
headers = {"Range": f"bytes={len(book)}-"}
expected_status = 206
else:
headers = {}
expected_status = 200
# print(f"{url}: got {len(book)} / {expected_length} bytes...")
resp = session.get(url, stream=True, headers=headers)
resp.raise_for_status()
if resp.status_code != expected_status:
raise ValueError(f"Unexpected status code: {resp.status_code}")
if expected_length is None: # Only update this on the first request
content_length = resp.headers.get("Content-Length")
if not content_length:
raise ValueError("Content-Length header not found")
expected_length = int(content_length)
try:
for chunk in resp.iter_content(chunk_size=8192):
book += chunk
except requests.exceptions.ChunkedEncodingError:
pass
i += 1
if len(book) != expected_length:
sys.exit("Failed to get book text. Can not run tests without it. Quitting...")
#### code end
with open(output_path, "w") as f:
f.write(book.decode("utf-8",errors="ignore"))
def generate_test_files(test_args):
# generate simple file
word_list = get_part_of_word_list(test_args["word_list"], 250)
simple_delimiters = test_args["simple_delimiters"]
max_msg_size = 1500
max_string_length = 50 * max_msg_size
test_string = generate_text_from_word_list(word_list, simple_delimiters, max_string_length)
f = open(test_args["filename_simple"], "w")
f.write(test_string)
f.close()
# generate complex file
word_list = get_part_of_word_list(test_args["word_list"], 2500)
all_delimiters = test_args["all_delimiters"]
max_string_length = 2.5e5
test_string = generate_text_from_word_list(word_list, all_delimiters, max_string_length)
f = open(test_args["filename_complex"], "w")
f.write(test_string)
f.close()
def kill_zmq_distributor_and_worker(kill_memcheck=False):
try:
distributor_procs = subprocess.check_output(["pidof", "zmq_distributor"]).decode("utf-8").split()
distributor_procs = [int(x) for x in distributor_procs]
except subprocess.CalledProcessError:
distributor_procs = []
try:
worker_procs = subprocess.check_output(["pidof", "zmq_worker"]).decode("utf-8").split()
worker_procs = [int(x) for x in worker_procs]
except subprocess.CalledProcessError:
worker_procs = []
if kill_memcheck:
# pgrep required, because pidof only returns exact matches
try:
memcheck_procs = subprocess.check_output(["pgrep", "memcheck"]).decode("utf-8").split()
memcheck_procs = [int(x) for x in memcheck_procs]
except subprocess.CalledProcessError:
memcheck_procs = []
else:
memcheck_procs = []
procs = distributor_procs + worker_procs + memcheck_procs
for p in procs:
try:
os.kill(p, signal.SIGKILL)
except ProcessLookupError:
continue
def get_part_of_word_list(word_list, n_words):
word_list_part = random.sample(word_list, n_words)
return word_list_part
def random_capitalization(words):
for i in range(len(words)):
words[i] = ''.join(map(random.choice, zip(words[i].lower(), words[i].upper())))
def generate_text_from_word_list(w_list, delim, text_size):
word_lengths = [len(x) for x in w_list]
median_length = np.median(word_lengths)
words_to_pick = int(text_size / median_length)
words = random.choices(w_list, k=words_to_pick)
random_capitalization(words)
i = random.randint(25,100)
while i < len(words):
words.insert(i, "\n")
i += random.randint(25,100)
test_string = "".join(x + random.choice(delim) for x in words)
return test_string
def count_words(input_string):
input_string = input_string.lower()
words = re.findall("[a-z]+", input_string)
words = Counter(words)
words = sorted(words.items(), key=lambda a: (-a[1], a[0]))
output_string = "word,frequency\n"
for w in words:
output_string += w[0] + "," + str(w[1]) + "\n"
return output_string
def create_test_debug_output(test_name, num_w, expected_output, dist_output):
debug_base_path = "debug/"
if not os.path.isdir(debug_base_path):
os.mkdir(debug_base_path)
out_name = debug_base_path + test_name + "_n-workers=" + str(num_w) + "_"
f = open(out_name + "expected.txt", "w")
f.write(expected_output)
f.close()
f = open(out_name + "distributor_output.txt", "w")
f.write(dist_output)
f.close()
def run_worker(w_exec : Union[str, List[str]], port : str):
proc_worker = None
if isinstance(w_exec, str):
proc_worker = subprocess.Popen([w_exec, port])
elif isinstance(w_exec, list):
w_exec.append(port)
proc_worker = subprocess.Popen(w_exec)
proc_worker.wait()
def join_workers(workers: list):
for w in workers:
if isinstance(w, subprocess.Popen):
w.wait()
elif isinstance(w, multiprocessing.Process):
w.join()
def start_threaded_workers(worker_exec : Union[str, List[List[str]]], port_list : list, use_multiprocessing = True):
# use_multiprocessing = False
if use_multiprocessing:
worker_procs = []
if isinstance(worker_exec, str):
for p in port_list:
proc = multiprocessing.Process(target=run_worker, args=(worker_exec, p))
worker_procs.append(proc)
proc.start()
elif isinstance(worker_exec, list):
for p, exe in zip(port_list, worker_exec):
proc = multiprocessing.Process(target=run_worker, args=(exe, p))
worker_procs.append(proc)
proc.start()
return worker_procs
else:
proc_workers = None
if isinstance(worker_exec, str):
proc_workers = subprocess.Popen([worker_exec] + port_list)
elif isinstance(worker_exec, list):
proc_workers = subprocess.Popen(worker_exec[0] + port_list)
return [proc_workers]
def start_distributor(dist_args : List[str]):
return subprocess.Popen(dist_args, stdout=subprocess.PIPE, encoding="ascii")
def run_worker_load_distribution(port, return_dict):
return_message = "test1worker1load1distribution1\0"
context = zmq.Context.instance()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:" + str(port))
received_requests = 0
while True:
message = socket.recv()
message = message.decode("ascii")
msg_type = message[:3]
if msg_type == "map" or msg_type == "red":
received_requests += 1
socket.send(bytes(return_message, encoding="ascii"))
elif msg_type == "rip":
socket.send(bytes("rip\0", encoding="ascii"))
break
else:
print("Unknown message type!")
break
socket.close()
return_dict[port] = received_requests
def check_valgrind_output_no_errors(filename):
if not os.path.isfile(filename):
return True
tree = ET.parse(filename)
errors = tree.findall("error")
return len(errors) == 0
def cleanup_files(test_args, remove_valgrind_files=True):
for k, v in test_args.items():
if not remove_valgrind_files and "valgrind" in k and ".xml" in v:
continue
if "filename" in k and os.path.isfile(v):
os.remove(v)
if test_args["no_cache"]:
shutil.rmtree(test_args["cache_dir"])