From 78465deb3497a4814feb15f42a0c95390e241886 Mon Sep 17 00:00:00 2001 From: Tim Jammer <tim.jammer@sc.tu-darmstadt.de> Date: Wed, 18 Jun 2025 17:03:08 +0200 Subject: [PATCH] added coverage for parcoach --- scripts/tools/parcoach.py | 193 ++++++++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 71 deletions(-) diff --git a/scripts/tools/parcoach.py b/scripts/tools/parcoach.py index a40049f7..4663c9c3 100644 --- a/scripts/tools/parcoach.py +++ b/scripts/tools/parcoach.py @@ -2,85 +2,138 @@ import re import os from MBButils import * + class Tool(AbstractTool): def identify(self): return "PARCOACH wrapper" def ensure_image(self): - AbstractTool.ensure_image(self, "-x parcoach") + #AbstractTool.ensure_image(self, "-x parcoach") + pass def build(self, rootdir, cached=True): - if cached and os.path.exists(f"/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/bin/parcoachcc"): - print("No need to rebuild PARCOACH.") - os.environ['PATH'] = os.environ['PATH'] + f":/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/bin/" - os.environ['OMPI_CC'] = "clang-15" - return - - here = os.getcwd() # Save where we were - os.chdir(rootdir) - subprocess.run(f"wget https://gitlab.inria.fr/api/v4/projects/12320/packages/generic/parcoach/2.4.0/parcoach-2.4.0-shared-Linux.tar.gz", shell=True, check=True) - subprocess.run(f"tar xfz parcoach-*.tar.gz", shell=True, check=True) - if not os.path.exists("/usr/lib/llvm-15/bin/clang"): - subprocess.run("ln -s $(which clang) /usr/lib/llvm-15/bin/clang", shell=True, check=True) - - # Go to where we want to install it, and build it out-of-tree (we're in the docker) - subprocess.run(f"rm -rf /tmp/build-parcoach/parcoach-2.4.0-shared-Linux/ && mkdir -p /tmp/build-parcoach/", shell=True, check=True) - subprocess.run(f"mv parcoach-*/ /tmp/build-parcoach/ ", shell=True, check=True) - os.environ['PATH'] = os.environ['PATH'] + f":/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/bin/" + # nothing to do + pass + + def setup(self): + # os.environ['PATH'] = os.environ['PATH'] + f":/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/bin" os.environ['OMPI_CC'] = "clang-15" + result_parcoach = subprocess.run( + "which parcoach", shell=True, check=True, capture_output=True, text=True + ) + self.parcoach_exe = result_parcoach.stdout.strip() - # Back to our previous directory - os.chdir(here) + clang_19_bin = os.environ["LLVM_19_BIN"] + self.llvm_cov = clang_19_bin + "/llvm-cov" + self.llvm_profdata = clang_19_bin + "/llvm-profdata" + assert os.path.isfile(self.llvm_cov) + assert os.path.isfile(self.llvm_profdata) - def setup(self): - os.environ['PATH'] = os.environ['PATH'] + f":/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/bin" - os.environ['OMPI_CC'] = "clang-15" + self.llvm_profile_folder = os.environ["LLVM_PROFILE_FOLDER"] + assert os.path.isdir(self.llvm_profile_folder) + self.coverage_profile = "parcoach_profile.pro" + self.coverage_report_html = "parcoach_coverage_html" + self.coverage_source = os.path.dirname(self.parcoach_exe)+"/../src/" def run(self, execcmd, filename, binary, id, number, timeout, batchinfo, loglevel=logging.INFO): + + self.setup() + os.environ['PATH'] = os.environ['PATH'] + f":/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/bin" os.environ['OMPI_CC'] = "clang-15" os.environ['OMPI_ALLOW_RUN_AS_ROOT'] = "1" os.environ['OMPI_ALLOW_RUN_AS_ROOT_CONFIRM'] = "1" cachefile = f'{binary}_{id}' + current_env = os.environ.copy() + base_file = os.path.basename(filename) + current_env["LLVM_PROFILE_FILE"] = f"{self.llvm_profile_folder}/{base_file}-mbb{number}.profraw" execcmd = re.sub('\${EXE}', f'./{binary}', execcmd) - if filename.find('lock')>=0 or filename.find('fence')>=0: + if filename.find('lock') >= 0 or filename.find('fence') >= 0: self.run_cmd( - #buildcmd=f"parcoachcc -check=rma --args mpicc {filename} -c -o {binary}.o", - buildcmd=f"parcoachcc -check=rma --args mpicc {filename} -c -o {binary}.o \n mpicc {binary}.o -o {binary} -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/ -lParcoachRMADynamic_MPI_C", - execcmd=f"mpicc {binary}.o -lParcoachRMADynamic_MPI_C -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/", - #execcmd=execcmd, - cachefile=cachefile, - filename=filename, - number=number, - binary=binary, - timeout=timeout, - batchinfo=batchinfo, - loglevel=loglevel) + # buildcmd=f"parcoachcc -check=rma --args mpicc {filename} -c -o {binary}.o", + # buildcmd=f"parcoachcc -check=rma --args mpicc {filename} -c -o {binary}.o \n mpicc {binary}.o -o {binary} -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/ -lParcoachRMADynamic_MPI_C", + # execcmd=f"mpicc {binary}.o -lParcoachRMADynamic_MPI_C -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/", + buildcmd=f"mpicc {filename} -S -emit-llvm -o {binary}.ir", + execcmd=f"{self.parcoach_exe} --check=rma {binary}.ir ", + # execcmd=execcmd, + cachefile=cachefile, + filename=filename, + number=number, + binary=binary, + timeout=timeout, + batchinfo=batchinfo, + loglevel=loglevel, + current_env=current_env, ) else: self.run_cmd( - #buildcmd=f"parcoachcc -instrum-inter --args mpicc {filename} -c -o {binary}.o", - buildcmd=f"parcoachcc -instrum-inter --args mpicc {filename} -c -o {binary}.o \n mpicc {binary}.o -o {binary} -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/ -lParcoachCollDynamic_MPI_C", - execcmd=f"mpicc {binary}.o -lParcoachCollDynamic_MPI_C -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/", - #execcmd=execcmd, - cachefile=cachefile, - filename=filename, - number=number, - binary=binary, - timeout=timeout, - batchinfo=batchinfo, - loglevel=loglevel) - - + # buildcmd=f"parcoachcc -instrum-inter --args mpicc {filename} -c -o {binary}.o", + # buildcmd=f"parcoachcc -instrum-inter --args mpicc {filename} -c -o {binary}.o \n mpicc {binary}.o -o {binary} -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/ -lParcoachCollDynamic_MPI_C", + buildcmd=f"mpicc {filename} -S -emit-llvm -o {binary}.ir", + execcmd=f"{self.parcoach_exe} --check=mpi {binary}.ir ", + # execcmd=f"mpicc {binary}.o -lParcoachCollDynamic_MPI_C -L/tmp/build-parcoach/parcoach-2.4.0-shared-Linux/lib/", + # execcmd=execcmd, + cachefile=cachefile, + filename=filename, + number=number, + binary=binary, + timeout=timeout, + batchinfo=batchinfo, + loglevel=loglevel, + current_env=current_env, ) + + self.merge_coverage_single(filename=filename, + profile=f"{self.llvm_profile_folder}/{base_file}-mbb{number}.profraw", number=number) subprocess.run("rm -f *.bc core", shell=True, check=True) + def merge_coverage_single(self, filename, profile, number): + here = os.getcwd() + os.chdir(self.llvm_profile_folder) + # print(f"We are here: {profile}") + try: + if os.path.isfile(profile): + command = f"-o {os.path.basename(filename)}-mbb{number}.pro" + subprocess.run( + f"{self.llvm_profdata} merge -sparse {profile} {command}", + shell=True, + check=True, + ) + subprocess.run( + f"rm {profile}", + shell=True, + check=False, + ) + except Exception: + pass + finally: + os.chdir(here) + + def teardown(self): + self.setup() + here = os.getcwd() + os.chdir(self.llvm_profile_folder) + subprocess.run( + f"{self.llvm_profdata} merge -sparse *.pro -o {self.coverage_profile}", + shell=True, + check=True, + ) + if os.path.exists(self.coverage_profile): + subprocess.run( + f"{self.llvm_cov} show -format=html {self.parcoach_exe} " + f"--instr-profile={self.coverage_profile} " + f"--show-directory-coverage --output-dir {self.coverage_report_html} " + f"--sources {self.coverage_source}", + shell=True, + check=True, + ) + os.chdir(here) + def get_mbb_error_label(self, error_message): mbb_error_dict = { 'LocalConcurrency': "LocalConcurrency", 'Call Ordering': "CallOrdering" } - for k, v in mbb_error_dict.items(): if error_message.startswith(k): return v @@ -88,25 +141,23 @@ class Tool(AbstractTool): assert False and "ERROR MESSAGE NOT KNOWN PARSING DOES NOT WORK CORRECLTY" return "UNKNOWN" - - def parse(self, cachefile, logs_dir): if os.path.exists(f'{cachefile}.timeout') or os.path.exists(f'{logs_dir}/parcoach/{cachefile}.timeout'): outcome = 'timeout' if not (os.path.exists(f'{cachefile}.txt') or os.path.exists(f'{logs_dir}/parcoach/{cachefile}.txt')): return 'failure' - with open(f'{cachefile}.txt' if os.path.exists(f'{cachefile}.txt') else f'{logs_dir}/parcoach/{cachefile}.txt', 'r') as infile: + with open(f'{cachefile}.txt' if os.path.exists(f'{cachefile}.txt') else f'{logs_dir}/parcoach/{cachefile}.txt', + 'r') as infile: output = infile.read() - if re.search('Compilation of .*? raised an error \(retcode: ', output): output = {} output["status"] = "UNIMPLEMENTED" return output - #if re.search('0 warning\(s\) issued', output): - #if re.search('No issues found', output): + # if re.search('0 warning\(s\) issued', output): + # if re.search('No issues found', output): # return 'OK' if re.search('missing info for external function', output): @@ -125,10 +176,11 @@ class Tool(AbstractTool): for line in lines: # get the error/warning blocks: - if re.match(COerror_line_prefix, line) or re.match(LC1error_line_prefix,line) or re.match(LC2error_line_prefix, line) or re.match(LCinfo_line_prefix,line): + if re.match(COerror_line_prefix, line) or re.match(LC1error_line_prefix, line) or re.match( + LC2error_line_prefix, line) or re.match(LCinfo_line_prefix, line): current_report.append(line) reports.append(current_report) - #print("--- current_report = ", current_report, "\n") + # print("--- current_report = ", current_report, "\n") test_file_name = cachefile.rsplit('_', 1)[0].strip() + ".c" output = {} output["status"] = "successful" @@ -145,13 +197,13 @@ class Tool(AbstractTool): for i, line in enumerate(report): error_found = {} error_class = {} - func = {} - mpi_func = {} + func = {} + mpi_func = {} number = {} line_number = {} - #print( test_file_name, ":\n") - #print("======> ", line, "\n") - #if test_file_name in line: + # print( test_file_name, ":\n") + # print("======> ", line, "\n") + # if test_file_name in line: if "PARCOACH" in line: # get the error class for Call ordering errors error_found = re.search(r"(\w+) (\w+) Error", line) @@ -163,19 +215,19 @@ class Tool(AbstractTool): number = re.search('line (\d+)', line) line_number = number.group(1) parsed_report['error_class'] = self.get_mbb_error_label(error_class) - #parsed_report['error_class'].append(self.get_mbb_error_label(error_class)) + # parsed_report['error_class'].append(self.get_mbb_error_label(error_class)) parsed_report['calls'].append(mpi_func) parsed_report['lines'].append(int(line_number)) - #print("CO ----> ", test_file_name, " - func = ", mpi_func, " - line_number = ", line_number, " - error class = ", error_class) + # print("CO ----> ", test_file_name, " - func = ", mpi_func, " - line_number = ", line_number, " - error class = ", error_class) if "i32" in line: - #error_found = re.search(r"(\w+) detected:", before_line) - #error_class = error_found.group(1) + # error_found = re.search(r"(\w+) detected:", before_line) + # error_class = error_found.group(1) error_class = "LocalConcurrency" func = re.search("call i32 \@(\S+)\(", line) mpi_func = func.group(1) number = re.search('LINE (\d+)', line) line_number = number.group(1) - #print("LC ----> ", test_file_name, " - func = ", mpi_func, " - line_number = ", line_number, " - error class = ", error_class) + # print("LC ----> ", test_file_name, " - func = ", mpi_func, " - line_number = ", line_number, " - error class = ", error_class) parsed_report['error_class'] = self.get_mbb_error_label(error_class) parsed_report['calls'].append(mpi_func) parsed_report['lines'].append(int(line_number)) @@ -186,9 +238,8 @@ class Tool(AbstractTool): parsed_report['ranks'] = list(set(parsed_report['ranks'])) parsed_reports.append(parsed_report) - - #parsed_reports = list(set(parsed_reports)) + # parsed_reports = list(set(parsed_reports)) output["messages"] = parsed_reports - #output["messages"] = list(set(output["messages"])) - #print("---> Messages = ", output["messages"], " ---> parsed_reports = ", parsed_reports, "\n") + # output["messages"] = list(set(output["messages"])) + # print("---> Messages = ", output["messages"], " ---> parsed_reports = ", parsed_reports, "\n") return output -- GitLab