-
Notifications
You must be signed in to change notification settings - Fork 145
dc baseline #2038
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
dc baseline #2038
Changes from all commits
c641f9f
07feb6e
f30e895
2a0e93d
dfad750
be22d59
3c39034
769fee7
41a0a61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,245 @@ | ||||||||||||||||||||||||||||||
| # Copyright 2026 Google LLC | ||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||
| # Licensed under the Apache License, Version 2.0 (the 'License'); | ||||||||||||||||||||||||||||||
| # you may not use this file except in compliance with the License. | ||||||||||||||||||||||||||||||
| # You may obtain a copy of the License at | ||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||
| # Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||||||
| # distributed under the License is distributed on an 'AS IS' BASIS, | ||||||||||||||||||||||||||||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||||||
| # See the License for the specific language governing permissions and | ||||||||||||||||||||||||||||||
| # limitations under the License. | ||||||||||||||||||||||||||||||
| """Utilities to generate metrics for PV map generation for statvar imports. | ||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||
| import tempfile | ||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| from absl import app | ||||||||||||||||||||||||||||||
| from absl import flags | ||||||||||||||||||||||||||||||
| from absl import logging | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) | ||||||||||||||||||||||||||||||
| sys.path.append(_SCRIPT_DIR) | ||||||||||||||||||||||||||||||
| sys.path.append(os.path.dirname(_SCRIPT_DIR)) | ||||||||||||||||||||||||||||||
| sys.path.append(os.path.dirname(os.path.dirname(_SCRIPT_DIR))) | ||||||||||||||||||||||||||||||
| sys.path.append( | ||||||||||||||||||||||||||||||
| os.path.join( | ||||||||||||||||||||||||||||||
| os.path.dirname(os.path.dirname(os.path.dirname(_SCRIPT_DIR)), 'util'))) | ||||||||||||||||||||||||||||||
| sys.path.append( | ||||||||||||||||||||||||||||||
| os.path.join(os.path.dirname(os.path.dirname(_SCRIPT_DIR)), | ||||||||||||||||||||||||||||||
| 'statvar_importer')) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import file_util | ||||||||||||||||||||||||||||||
| from counters import Counters | ||||||||||||||||||||||||||||||
| from mcf_diff import diff_mcf_files | ||||||||||||||||||||||||||||||
| from mcf_file_util import get_value_list | ||||||||||||||||||||||||||||||
| from stat_var_test_runner import StatVarProcessorTestRunner, run_script | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class PVMapGeneratorMetricsRunner(StatVarProcessorTestRunner): | ||||||||||||||||||||||||||||||
| """Class to generate metrics for PV map generation for statvar imports. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| The metrics are generated by running the statvar processor with the test | ||||||||||||||||||||||||||||||
| config and comparing the output with the expected output. | ||||||||||||||||||||||||||||||
| The files for pvmap egenration are specified in the test config as follows: | ||||||||||||||||||||||||||||||
| "test_name": "<test name>", | ||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||
| "pvmap_generator_test": { | ||||||||||||||||||||||||||||||
| "pvmap_generator_args": { | ||||||||||||||||||||||||||||||
| "input_data": "<path to input file>", | ||||||||||||||||||||||||||||||
| "output_data": "<path to output file>", | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| "expected_output_files": [ | ||||||||||||||||||||||||||||||
| "<path to expected output file>", | ||||||||||||||||||||||||||||||
| "<path to expected output file>", | ||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def __init__(self, test_config_file: str = None, test_config: dict = None): | ||||||||||||||||||||||||||||||
| super().__init__(test_config_file, test_config) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def run_pvmap_generator(self, test_config: dict) -> dict: | ||||||||||||||||||||||||||||||
| """Runs a single instance of pvmap generator.""" | ||||||||||||||||||||||||||||||
| test_name = test_config.get('test_name', 'pvmap_generator_test') | ||||||||||||||||||||||||||||||
| test_dir = os.path.join(self._temp_dir.name, test_name) | ||||||||||||||||||||||||||||||
| output_dir = os.path.join(test_dir, 'generated') | ||||||||||||||||||||||||||||||
| logging.info( | ||||||||||||||||||||||||||||||
| f'Running pvmap generator for test {test_name}, output in {output_dir}, config: {test_config}' | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| env_dict = dict(self.get_env_dict()) | ||||||||||||||||||||||||||||||
| env_dict.update(get_env_dict(test_config.get("env_file"))) | ||||||||||||||||||||||||||||||
| # Build the pvmap generator commandline | ||||||||||||||||||||||||||||||
| cmd_args = dict() | ||||||||||||||||||||||||||||||
| # Add default arguments for output | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cmd_args = merged_args([ | ||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| '--output_path': output_dir | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| test_config.get('pvmap_generator_args', {}), | ||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||
| output_arg = get_arg(cmd_args, '--output_path') | ||||||||||||||||||||||||||||||
| if output_arg: | ||||||||||||||||||||||||||||||
| output_dir = os.path.realpath(output_arg) | ||||||||||||||||||||||||||||||
| if not output_dir.endswith('/'): | ||||||||||||||||||||||||||||||
| output_dir = os.path.dirname(output_dir) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| cwd = self.get_local_dir() | ||||||||||||||||||||||||||||||
| script_status = run_script( | ||||||||||||||||||||||||||||||
| interpreter=sys.executable, | ||||||||||||||||||||||||||||||
| script=os.path.join(os.path.dirname(_SCRIPT_DIR), | ||||||||||||||||||||||||||||||
| 'pvmap_generator.py'), | ||||||||||||||||||||||||||||||
| args=cmd_args, | ||||||||||||||||||||||||||||||
| cwd=cwd, | ||||||||||||||||||||||||||||||
| env=env_dict, | ||||||||||||||||||||||||||||||
| output_dir=output_dir, | ||||||||||||||||||||||||||||||
| log_dir=os.path.join(output_dir, 'debug'), | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Check for generated files | ||||||||||||||||||||||||||||||
| generated_config = { | ||||||||||||||||||||||||||||||
| 'pv_map': os.path.join(output_dir, 'generated_pvmap.csv'), | ||||||||||||||||||||||||||||||
| 'config_file': os.path.join(output_dir, 'generated_metadata.csv'), | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| script_status['generated_config'] = generated_config | ||||||||||||||||||||||||||||||
| for arg, file in generated_config.items(): | ||||||||||||||||||||||||||||||
| if not os.path.exists(file): | ||||||||||||||||||||||||||||||
| logging.error( | ||||||||||||||||||||||||||||||
| f'Failed to generate {file} for test: {test_name}') | ||||||||||||||||||||||||||||||
| script_status['status'] = 'FAIL' | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return script_status | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def run_sample_statvar_processor(self, test_config: dict, | ||||||||||||||||||||||||||||||
| generated_config: dict) -> dict: | ||||||||||||||||||||||||||||||
| """Run statvar processor on the sample input using the generated configs.""" | ||||||||||||||||||||||||||||||
| test_name = test_config.get('test_name', 'sample_statvar_processor') | ||||||||||||||||||||||||||||||
| test_dir = os.path.join(self._temp_dir.name, test_name) | ||||||||||||||||||||||||||||||
| output_dir = os.path.join(test_dir, 'sample') | ||||||||||||||||||||||||||||||
| env_dict = dict(self.get_env_dict()) | ||||||||||||||||||||||||||||||
| env_dict.update(get_env_dict(test_config.get("env_file"))) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Get commandline args for stavtar processor | ||||||||||||||||||||||||||||||
| # to use generated pvmap files. | ||||||||||||||||||||||||||||||
| cmd_args = merged_args([ | ||||||||||||||||||||||||||||||
| generated_config, | ||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||
| '--output_path': | ||||||||||||||||||||||||||||||
| output_dir, | ||||||||||||||||||||||||||||||
| '--output_data_pvs': | ||||||||||||||||||||||||||||||
| os.path.join(output_dir, 'output_data_pvs.csv'), | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||
| script_status = self.run_statvar_processor(test_config, cmd_args) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return script_status | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def generate_metrics(self, test_config: dict, test_status: dict) -> dict: | ||||||||||||||||||||||||||||||
| """Generates metrics for a test config. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||
| test_config: Dictionary with the test config. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||
| Dictionary with the metrics. | ||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
| test_name = test_config.get('test_name', 'sample_statvar_processor') | ||||||||||||||||||||||||||||||
| test_dir = os.path.join(self._temp_dir.name, test_name) | ||||||||||||||||||||||||||||||
| outputs = test_config.get('pvmap_generator_outputs') | ||||||||||||||||||||||||||||||
| output_stats = self.verify_outputs(outputs, test_config, | ||||||||||||||||||||||||||||||
| test_status.get('output_dir')) | ||||||||||||||||||||||||||||||
| stats = {} | ||||||||||||||||||||||||||||||
| for output_name, stats in output_stats: | ||||||||||||||||||||||||||||||
| output_stats = output.get('counters', {}) | ||||||||||||||||||||||||||||||
| output_stats = self.get_stats_from_diff_counters(output_stats) | ||||||||||||||||||||||||||||||
| file_util.file_write_csv_dict( | ||||||||||||||||||||||||||||||
| output_stats, | ||||||||||||||||||||||||||||||
| os.path.join(test_dir, output_name + '-diff-counters.csv')) | ||||||||||||||||||||||||||||||
| stats[output_name] = output_stats | ||||||||||||||||||||||||||||||
|
Comment on lines
+161
to
+167
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are multiple issues in this loop:
Suggested change
References
|
||||||||||||||||||||||||||||||
| logging.info(f'Diff stats for {output_name}: {output_stats}') | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return stats | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def get_stats_from_diff_counters(self, | ||||||||||||||||||||||||||||||
| diff_stats: dict, | ||||||||||||||||||||||||||||||
| stats: dict = None) -> dict: | ||||||||||||||||||||||||||||||
| """Returns precision and recall stats from diff counters. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||
| diff_stats: diff counters for diff between actual and expected output files. | ||||||||||||||||||||||||||||||
| stats: output stats dictionary into which stats are added. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
| if stats is None: | ||||||||||||||||||||||||||||||
| stats = dict({ | ||||||||||||||||||||||||||||||
| 'false_positive': 0, | ||||||||||||||||||||||||||||||
| 'false_negative': 0, | ||||||||||||||||||||||||||||||
| 'true_positive': 0, | ||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||
| counters = diff_stats.get('counters', {}) | ||||||||||||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F1 score from node diffs |
||||||||||||||||||||||||||||||
| matched = counters.get('pvs-matched', 0) | ||||||||||||||||||||||||||||||
| false_negative = counters.get('pvs-added', 0) | ||||||||||||||||||||||||||||||
| false_positive = counters.get('pvs-deleted', 0) | ||||||||||||||||||||||||||||||
| # Treat modfied as deleted+added | ||||||||||||||||||||||||||||||
| modified = counters.get('pvs-modified', 0) | ||||||||||||||||||||||||||||||
| false_negative += modified | ||||||||||||||||||||||||||||||
| false_positive += modified | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| stats['true_positive'] += matched | ||||||||||||||||||||||||||||||
| stats['false_positive'] += false_positive | ||||||||||||||||||||||||||||||
| stats['false_negative'] += false_negative | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # Compute precision and recall | ||||||||||||||||||||||||||||||
| true_positive = stats['true_positive'] | ||||||||||||||||||||||||||||||
| precision = true_positive / (max( | ||||||||||||||||||||||||||||||
| true_positive + stats['false_positive'], 1)) | ||||||||||||||||||||||||||||||
| recall = true_positive / (max(true_positive + stats['false_negative'], | ||||||||||||||||||||||||||||||
| 1)) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| stats['precision'] = precision | ||||||||||||||||||||||||||||||
| stats['recall'] = recall | ||||||||||||||||||||||||||||||
| stats['f1'] = 2 * (precision * recall) / max(precision + recall, 1) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return stats | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def run_test(self, test_output: str = None) -> dict: | ||||||||||||||||||||||||||||||
| """Runs the pvmap generator and returns the comparison metrics. | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||
| test_output: JSON file with the test output status | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||
| JSON dict with the test summary | ||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||
| cwd = self.get_local_dir() | ||||||||||||||||||||||||||||||
| os.chdir(cwd) | ||||||||||||||||||||||||||||||
| return_status = {'status': 'PASS'} | ||||||||||||||||||||||||||||||
| for index, test_config in enumerate(self._test_config): | ||||||||||||||||||||||||||||||
| test_status = {} | ||||||||||||||||||||||||||||||
| test_name = test_config.get('test_name') | ||||||||||||||||||||||||||||||
| if not test_name: | ||||||||||||||||||||||||||||||
| test_name = f'pvmap-generator-test-{index}' | ||||||||||||||||||||||||||||||
| basedir = os.path.basename(self.get_local_dir()) | ||||||||||||||||||||||||||||||
| if basedir: | ||||||||||||||||||||||||||||||
| test_name += '-' + basedir | ||||||||||||||||||||||||||||||
| test_config['test_name'] = test_name | ||||||||||||||||||||||||||||||
| logging.info(f'Running pvmap generator test: {index}:{test_name}') | ||||||||||||||||||||||||||||||
| pvmap_generator_status = self.run_pvmap_generator(test_config) | ||||||||||||||||||||||||||||||
| test_status['pvmap_generator_status'] = pvmap_generator_status | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| logging.info( | ||||||||||||||||||||||||||||||
| f'Running statvar processor for test: {index}:{test_name}') | ||||||||||||||||||||||||||||||
| statvar_status = self.run_sample_statvar_processor( | ||||||||||||||||||||||||||||||
| test_config, pvmap_generator_status.get('generated_config', {})) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| test_status['statvar_processor_status'] = statvar_status | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current path calculation relies on a specific directory structure and contains a syntax error (os.path.dirname takes only one argument). Instead of hardcoding relative path traversal, use a more robust method to locate the repository root, such as searching for a marker file like 'WORKSPACE' or '.git'.
References