diff --git a/base/parse_result_json.py b/base/parse_result_json.py new file mode 100755 index 0000000000000000000000000000000000000000..8f99bb5ef6106e6fd9e9b5074e17f7f6de738ef0 --- /dev/null +++ b/base/parse_result_json.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +解析loop-001.json或者base.json文件,统计测试case和指标信息 +""" + +import json +import argparse +from collections import defaultdict +from typing import Dict, List, Tuple + + +def parse_json_file(file_path: str) -> Dict: + """读取并解析JSON文件""" + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + + +def extract_case_prefix(case_name: str, l0: str = None) -> str: + """从case名称中提取前缀(子系统名称) + 例如: tperf-fio-ext4-randread4K -> tperf-fio-ext4 + 对于内存子系统,去掉第三字段的下划线和后面的内容 + 例如: tperf-libmicro-mallocT2_100k -> tperf-libmicro-mallocT2 + """ + parts = case_name.split('-') + if len(parts) >= 3: + prefix = '-'.join(parts[:3]) + # 如果是内存子系统,去掉第三字段的下划线和后面的内容 + if l0 == '内存' and '_' in parts[2]: + parts[2] = parts[2].split('_')[0] + prefix = '-'.join(parts[:3]) + return prefix + return case_name + + +def analyze_cases(data: Dict) -> Tuple[Dict, Dict, Dict, float]: + """ + 分析case数据 + 返回: + - l0_stats: 按L0分类的统计 {L0: {'cases': set, 'metrics': int}} + - prefix_stats: 按case前缀分类的统计 {prefix: {'cases': set, 'metrics': int, 'l0': str}} + - case_durations: case名称和duration的列表 [(case_name, duration, l0), ...] + - total_duration: 总duration + """ + l0_stats = defaultdict(lambda: {'cases': set(), 'metrics': 0}) + prefix_stats = defaultdict(lambda: {'cases': set(), 'metrics': 0, 'l0': None}) + case_durations = [] + total_duration = 0.0 + + for case_name, case_data in data.items(): + duration = case_data.get('duration', 0.0) + total_duration += duration + + result = case_data.get('result', {}) + + # 获取该case的L0分类(从第一个指标中获取) + l0 = None + for metric_name, metric_data in result.items(): + if isinstance(metric_data, dict): + l0 = metric_data.get('L0', '') + break + + if l0 is None: + l0 = '未知' + + # 统计指标数量 + metric_count = len(result) + + # 更新L0统计 + l0_stats[l0]['cases'].add(case_name) + l0_stats[l0]['metrics'] += metric_count + + # 更新前缀统计 + prefix = extract_case_prefix(case_name, l0) + prefix_stats[prefix]['cases'].add(case_name) + prefix_stats[prefix]['metrics'] += metric_count + prefix_stats[prefix]['l0'] = l0 + + # 记录duration信息 + case_durations.append((case_name, duration, l0)) + + return l0_stats, prefix_stats, case_durations, total_duration + + +def format_duration(seconds: float) -> str: + """将秒数转换为小时、分钟、秒格式""" + hours = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + secs = seconds % 60 + return f"{hours}小时 {minutes}分钟 {secs:.3f}秒" + + +def print_summary(l0_stats: Dict, prefix_stats: Dict, case_durations: List[Tuple], + total_duration: float, top_n: int = 10, reverse: bool = True): + """打印统计结果 + Args: + l0_stats: L0分类统计 + prefix_stats: 前缀分类统计 + case_durations: case duration列表 + total_duration: 总duration + top_n: 显示top N耗时case + reverse: True表示降序(耗时最长的在前),False表示升序(耗时最短的在前) + """ + # 计算总数 + total_cases = sum(len(stats['cases']) for stats in l0_stats.values()) + total_metrics = sum(stats['metrics'] for stats in l0_stats.values()) + + print(f"总Case数: {total_cases}") + print(f"总指标数: {total_metrics}") + print() + + # 各维度统计 + print("各维度统计:") + print("-" * 80) + for l0 in sorted(l0_stats.keys()): + stats = l0_stats[l0] + case_count = len(stats['cases']) + metric_count = stats['metrics'] + print(f"{l0:8s} : Case={case_count:4d}, 指标={metric_count:4d}") + print() + + # 各子系统详细统计 + print("=" * 80) + print("各子系统详细统计:") + print("=" * 80) + print() + + # 按L0分组打印 + l0_prefixes = defaultdict(list) + for prefix, stats in prefix_stats.items(): + l0 = stats['l0'] or '未知' + l0_prefixes[l0].append((prefix, stats)) + + for l0 in sorted(l0_prefixes.keys()): + print(f"{l0}子系统:") + print("-" * 80) + + prefixes = sorted(l0_prefixes[l0], key=lambda x: x[0]) + for prefix, stats in prefixes: + case_count = len(stats['cases']) + metric_count = stats['metrics'] + print(f" {prefix:50s} : Case={case_count:4d}, 指标={metric_count:4d}") + + # 计算该L0的总数 + l0_case_total = sum(len(s['cases']) for _, s in prefixes) + l0_metric_total = sum(s['metrics'] for _, s in prefixes) + print(f"{' ':50s} Case共{l0_case_total}, 指标共{l0_metric_total}") + print() + + # Top N耗时Case + if top_n > 0: + print("=" * 80) + order_desc = "降序" if reverse else "升序" + print(f"Top {top_n} 耗时Case ({order_desc}):") + print("=" * 80) + print() + + sorted_cases = sorted(case_durations, key=lambda x: x[1], reverse=reverse) + for i, (case_name, duration, l0) in enumerate(sorted_cases[:top_n], 1): + percentage = (duration / total_duration * 100) if total_duration > 0 else 0 + print(f"{i:3d}. {case_name:60s} | {l0:4s} | duration={duration:10.3f} | 占比={percentage:6.2f}%") + print() + + # 总duration + print("=" * 80) + print(f"所有Case的duration总和: {total_duration:.3f}秒 ({format_duration(total_duration)})") + if total_cases > 0: + avg_duration = total_duration / total_cases + avg_percentage = (avg_duration / total_duration) * 100 if total_duration > 0 else 0 + print(f"平均每个Case耗时: {avg_duration:.3f}秒 ({format_duration(avg_duration)}), 占比: {avg_percentage:.4f}%") + print("=" * 80) + + +def main(): + parser = argparse.ArgumentParser(description='解析JSON结果文件,统计测试case和指标信息') + parser.add_argument('json_file', help='JSON文件路径') + parser.add_argument('--top', type=int, default=10, help='显示top N耗时最长的case(默认10,设为0可禁用)') + parser.add_argument('--asc', action='store_true', help='Top N耗时Case按升序排列(耗时最短的在前),默认是降序') + + args = parser.parse_args() + + # 解析JSON文件 + data = parse_json_file(args.json_file) + + # 分析数据 + l0_stats, prefix_stats, case_durations, total_duration = analyze_cases(data) + + # 打印结果 + print_summary(l0_stats, prefix_stats, case_durations, total_duration, args.top, reverse=not args.asc) + + +if __name__ == '__main__': + main() diff --git a/configure b/configure index 4b6b8bbad24ff44ff4da5cfb7865455c8a29113f..f020833ae790fa8c6544c9eba871e781bd7ea762 100755 --- a/configure +++ b/configure @@ -16,10 +16,19 @@ main() { libtool glibc-devel numactl - numactl-devel" + numactl-devel + python3-pip" # shellcheck disable=SC2086 yum install --skip-broken -y $pkg_to_install + pip3_packages="numpy openpyxl" + for pkg in $pip3_packages; do + pip3 install "$pkg" || { + echo "Warning: Failed to install $pkg" + _main_ret=1 + } + done + return ${_main_ret} } diff --git a/templates/stat_tperf_csv.py b/templates/stat_tperf_csv.py index 2506e89280e9159b701c27ac781d463c297d76e1..ed0252fc5d2b3e737f00f94127c3d1f24af98c4a 100755 --- a/templates/stat_tperf_csv.py +++ b/templates/stat_tperf_csv.py @@ -53,6 +53,12 @@ def stat_csv(csv_file): current_dimension = None current_case_name = None + # 用于检测重复的字典 + case_to_dimension = {} # case名 -> 维度列表(可能有多个维度) + case_positions = defaultdict(list) # case名 -> 行号列表 + metric_to_cases = defaultdict(set) # 指标名 -> case名集合 + case_metrics = defaultdict(lambda: defaultdict(list)) # case名 -> {指标名: [行号列表]} + with open(csv_file, 'r', encoding='utf-8') as f: # 跳过前5行说明 for _ in range(5): @@ -81,9 +87,16 @@ def stat_csv(csv_file): if case_name: total_cases += 1 current_case_name = case_name + # 记录case的位置和维度 + case_positions[case_name].append(line_num) if dimension: case_count_by_dimension[dimension] += 1 current_dimension = dimension + # 记录case所属的维度 + if case_name not in case_to_dimension: + case_to_dimension[case_name] = [] + if dimension not in case_to_dimension[case_name]: + case_to_dimension[case_name].append(dimension) case_type = extract_case_type(case_name, dimension) if case_type: case_stats_by_dimension[dimension][case_type]['cases'] += 1 @@ -91,6 +104,11 @@ def stat_csv(csv_file): # 统计指标(所有行都算指标,但需要有效的指标名) if metric_name: total_metrics += 1 + # 记录指标和case的关联关系 + if current_case_name: + metric_to_cases[metric_name].add(current_case_name) + # 记录每个case中的指标名和行号 + case_metrics[current_case_name][metric_name].append(line_num) # 如果当前行没有维度,使用上一个 Case 的维度 if dimension: metric_count_by_dimension[dimension] += 1 @@ -111,6 +129,10 @@ def stat_csv(csv_file): 'case_count_by_dimension': dict(case_count_by_dimension), 'metric_count_by_dimension': dict(metric_count_by_dimension), 'case_stats_by_dimension': dict(case_stats_by_dimension), + 'case_to_dimension': case_to_dimension, + 'case_positions': dict(case_positions), + 'metric_to_cases': dict(metric_to_cases), + 'case_metrics': dict(case_metrics), } def main(): @@ -182,6 +204,60 @@ def main(): print() print("=" * 80) + # 检测重复的case名 + duplicate_cases = [] + for case_name, dimensions in stats['case_to_dimension'].items(): + if len(dimensions) > 1: + # case出现在多个维度中 + duplicate_cases.append((case_name, dimensions, stats['case_positions'][case_name])) + elif len(stats['case_positions'][case_name]) > 1: + # case在同一维度中出现多次 + duplicate_cases.append((case_name, dimensions, stats['case_positions'][case_name])) + + # 检测重复的指标名(同一个case中重复的指标名) + duplicate_metrics = [] + for case_name, metrics_dict in stats['case_metrics'].items(): + for metric_name, positions in metrics_dict.items(): + if len(positions) > 1: + # 获取case所属的子系统 + case_dims = stats['case_to_dimension'].get(case_name, ['未知']) + dim_str = ', '.join(case_dims) if case_dims else '未知' + duplicate_metrics.append((case_name, metric_name, dim_str, positions)) + + # 输出告警信息 + if duplicate_cases or duplicate_metrics: + print() + print("=" * 80) + print("⚠️ 重复检测告警:") + print("=" * 80) + print() + + if duplicate_cases: + print("❌ 发现重复的Case名:") + print("-" * 80) + for case_name, dimensions, positions in duplicate_cases: + if len(dimensions) > 1: + print(f" Case名: {case_name}") + print(f" 出现在多个子系统: {', '.join(dimensions)}") + print(f" 出现位置(行号): {', '.join(map(str, positions))}") + else: + print(f" Case名: {case_name}") + print(f" 子系统: {dimensions[0]}") + print(f" 重复出现 {len(positions)} 次,位置(行号): {', '.join(map(str, positions))}") + print() + + if duplicate_metrics: + print("❌ 发现重复的指标名:") + print("-" * 80) + for case_name, metric_name, dim_str, positions in duplicate_metrics: + print(f" Case名: {case_name}") + print(f" 子系统: {dim_str}") + print(f" 指标名: {metric_name}") + print(f" 重复出现 {len(positions)} 次,位置(行号): {', '.join(map(str, positions))}") + print() + + print("=" * 80) + except FileNotFoundError: print(f"错误: 文件 {csv_file} 不存在", file=sys.stderr) sys.exit(1) diff --git a/templates/tperf.csv b/templates/tperf.csv index a229c926f159ced75d59621345f870228d2614da..3492f14ea7d79f6585b7489b3a309433cfa321e4 100644 --- a/templates/tperf.csv +++ b/templates/tperf.csv @@ -779,11 +779,11 @@ tperf-lmbench-lat_ctx-8k-3p,tperf-lmbench-lat_ctx-8k-3p,调度,,,10 tperf-lmbench-lat_ctx-8k-48p,tperf-lmbench-lat_ctx-8k-48p,调度,,,80 tperf-lmbench-lat_ctx-8k-6p,tperf-lmbench-lat_ctx-8k-6p,调度,,,20 tperf-lmbench-lat_ctx-8k-96p,tperf-lmbench-lat_ctx-8k-96p,调度,,,90 -tperf-lmbench-lat_pipe-n11,tperf-lmbench-lat_pipe-n11,调度,,,90 -tperf-lmbench-lat_pipe-n3,tperf-lmbench-lat_pipe-n3,调度,,,20 -tperf-lmbench-lat_pipe-n5,tperf-lmbench-lat_pipe-n5,调度,,,40 -tperf-lmbench-lat_pipe-n7,tperf-lmbench-lat_pipe-n7,调度,,,60 -tperf-lmbench-lat_pipe-n9,tperf-lmbench-lat_pipe-n9,调度,,,80 +tperf-lmbench-lat_pipe-n11,tperf-lmbench-lat_pipe-n1,调度,,,20 +tperf-lmbench-lat_pipe-n3,tperf-lmbench-lat_pipe-n3,调度,,,40 +tperf-lmbench-lat_pipe-n5,tperf-lmbench-lat_pipe-n5,调度,,,60 +tperf-lmbench-lat_pipe-n7,tperf-lmbench-lat_pipe-n7,调度,,,80 +tperf-lmbench-lat_pipe-n9,tperf-lmbench-lat_pipe-n9,调度,,,90 tperf-rt-tests-hackbench-process-pipe-fds100-groups10,tperf-rt-tests-hackbench-process-pipe-fds100-groups10,调度,,,50 tperf-rt-tests-hackbench-process-pipe-fds10-groups100,tperf-rt-tests-hackbench-process-pipe-fds10-groups100,调度,,,70 tperf-rt-tests-hackbench-process-pipe-fds15-groups100,tperf-rt-tests-hackbench-process-pipe-fds15-groups100,调度,,,80 @@ -820,10 +820,6 @@ tperf-rt-tests-hackbench-thread-socket-fds40-groups10,tperf-rt-tests-hackbench-t tperf-rt-tests-hackbench-thread-socket-fds5-groups100,tperf-rt-tests-hackbench-thread-socket-fds5-groups100,调度,,,60 tperf-rt-tests-hackbench-thread-socket-fds60-groups10,tperf-rt-tests-hackbench-thread-socket-fds60-groups10,调度,,,30 tperf-rt-tests-hackbench-thread-socket-fds80-groups10,tperf-rt-tests-hackbench-thread-socket-fds80-groups10,调度,,,40 -tperf-stream-single,stream-single-thread-Add,内存,,,50 -,stream-single-thread-Copy,内存,,,50 -,stream-single-thread-Scale,内存,,,50 -,stream-single-thread-Triad,内存,,,50 tperf-sysbench-threads-16-locks-48-yields-1000,tperf-sysbench-threads-16-locks-48-yields-1000,调度,,,30 tperf-sysbench-threads-16-locks-48-yields-10000,tperf-sysbench-threads-16-locks-48-yields-10000,调度,,,90 tperf-sysbench-threads-16-locks-96-yields-1000,tperf-sysbench-threads-16-locks-96-yields-1000,调度,,,30 diff --git a/templates/tperf.daily.csv b/templates/tperf.daily.csv index c7a90bcf8bc656f3a34bea2067d9f6343f1f0303..e103f4a779732962c513aa5f66e817510f0247fc 100644 --- a/templates/tperf.daily.csv +++ b/templates/tperf.daily.csv @@ -125,7 +125,7 @@ tperf-lmbench-lat_ctx-64k-48p,tperf-lmbench-lat_ctx-64k-48p,调度,,,80 tperf-lmbench-lat_ctx-64k-96p,tperf-lmbench-lat_ctx-64k-96p,调度,,,90 tperf-lmbench-lat_mmap,tperf-lmbench-lat_mmap,内存,,,50 tperf-lmbench-lat_pagefault,tperf-lmbench-lat_pagefault,内存,,,50 -tperf-lmbench-lat_pipe-n11,tperf-lmbench-lat_pipe-n11,调度,,,90 +tperf-lmbench-lat_pipe-n1,tperf-lmbench-lat_pipe-n1,调度,,,20 tperf-lmbench-lat_proc-exec,tperf-lmbench-lat_proc-exec,内存,,,50 tperf-lmbench-lat_proc-fork,tperf-lmbench-lat_proc-fork,内存,,,50 tperf-lmbench-lat_proc-shell,tperf-lmbench-lat_proc-shell,内存,,,50 diff --git a/testcase/tperf-lmbench-lat_pipe-n11.py b/testcase/tperf-lmbench-lat_pipe-n1.py similarity index 92% rename from testcase/tperf-lmbench-lat_pipe-n11.py rename to testcase/tperf-lmbench-lat_pipe-n1.py index 24cce1b5212bf9ef62aecad722dfeb410839b02e..ba15298ea2902ae1c0b9e66b6e2b549914d74642 100755 --- a/testcase/tperf-lmbench-lat_pipe-n11.py +++ b/testcase/tperf-lmbench-lat_pipe-n1.py @@ -18,7 +18,7 @@ from lib.ts_common import MyTestCase, PerfLMbench # noqa: E402 class PythonTestCase(MyTestCase): """ @用例ID: 20250715-200002-000000037 - @用例名称: tperf-lmbench-lat_pipe + @用例名称: tperf-lmbench-lat_pipe-n1 @用例级别: 3 @用例标签: @用例类型: 性能测试 @@ -39,8 +39,8 @@ class PythonTestCase(MyTestCase): perf = PerfLMbench( name="lat_pipe", testname=self.tc_name, - label="L0:net", - general_opt="-P 1" + label="L0:sched", + general_opt="-P 1 -N 1" ) perf.run(warmup=0, run_loop=30, result_select_percent=70) perf.report(testcase=self) diff --git a/testcase/tperf-lmbench-lat_pipe-n3.py b/testcase/tperf-lmbench-lat_pipe-n3.py index 57863cf26368f6ab1ee63be92f8961365925a7f3..fd7c0bcc27a52ebaa83bef301e3dce13d2b9138e 100755 --- a/testcase/tperf-lmbench-lat_pipe-n3.py +++ b/testcase/tperf-lmbench-lat_pipe-n3.py @@ -39,7 +39,7 @@ class PythonTestCase(MyTestCase): perf = PerfLMbench( name="lat_pipe", testname=self.tc_name, - label="L0:net", + label="L0:sched", general_opt="-P 1 -N 3" ) perf.run(warmup=0, run_loop=30, result_select_percent=70) diff --git a/testcase/tperf-lmbench-lat_pipe-n5.py b/testcase/tperf-lmbench-lat_pipe-n5.py index 0a5d46385b538d7d12729fe616956e0eb848fb24..4a20ce3c0a3db8f56508de543200f2f40053d614 100755 --- a/testcase/tperf-lmbench-lat_pipe-n5.py +++ b/testcase/tperf-lmbench-lat_pipe-n5.py @@ -39,7 +39,7 @@ class PythonTestCase(MyTestCase): perf = PerfLMbench( name="lat_pipe", testname=self.tc_name, - label="L0:net", + label="L0:sched", general_opt="-P 1 -N 5" ) perf.run(warmup=0, run_loop=30, result_select_percent=70) diff --git a/testcase/tperf-lmbench-lat_pipe-n7.py b/testcase/tperf-lmbench-lat_pipe-n7.py index f006ae76240a4f1607661e09b4e4b2b0110cdfd1..a1d2e7cb7654d806bc1b2b71652583c55f00f506 100755 --- a/testcase/tperf-lmbench-lat_pipe-n7.py +++ b/testcase/tperf-lmbench-lat_pipe-n7.py @@ -39,7 +39,7 @@ class PythonTestCase(MyTestCase): perf = PerfLMbench( name="lat_pipe", testname=self.tc_name, - label="L0:net", + label="L0:sched", general_opt="-P 1 -N 7" ) perf.run(warmup=0, run_loop=30, result_select_percent=70) diff --git a/testcase/tperf-lmbench-lat_pipe-n9.py b/testcase/tperf-lmbench-lat_pipe-n9.py index d0597b0d8228fc5a2792f6d84862069c6e50ff02..161a1216fde4d41a1f48d866c95e72db508ceee0 100755 --- a/testcase/tperf-lmbench-lat_pipe-n9.py +++ b/testcase/tperf-lmbench-lat_pipe-n9.py @@ -39,7 +39,7 @@ class PythonTestCase(MyTestCase): perf = PerfLMbench( name="lat_pipe", testname=self.tc_name, - label="L0:net", + label="L0:sched", general_opt="-P 1 -N 9" ) perf.run(warmup=0, run_loop=30, result_select_percent=70) diff --git a/tperf.py b/tperf.py index fea136bb307af40d199d134f2c6d3c492f91db80..e5bda5f7103fd5dde3e104749be8c74b43952f40 100755 --- a/tperf.py +++ b/tperf.py @@ -577,28 +577,31 @@ def main(): result_dict = {} for loop in range(1, args.loop + 1): time_loop_start = time.time() - msg(f"run loop {loop}/{args.loop}") - data_dir = os.path.join(tperf_top_dir, "logs", "perf") - case_log_dir = os.path.join(tperf_top_dir, "logs", "testcase") - if os.path.exists(data_dir): - shutil.rmtree(data_dir) - if os.path.exists(case_log_dir): - shutil.rmtree(case_log_dir) loop_dir = os.path.join(result_dir, f"loop-{loop:03}") - os.makedirs(loop_dir) - with open(os.path.join(loop_dir, "env-info.json"), "w") as f: - json.dump(get_node_env_info(config=config), f, indent=4, ensure_ascii=False) - result_dict[loop] = run_tperf(template=template, runner_id=runner_id) - dbg(result_dict[loop]) - shutil.move(src=data_dir, dst=os.path.join(loop_dir, "perf")) - shutil.move(src=case_log_dir, dst=os.path.join(loop_dir, "testcase")) - with open(f"{loop_dir}.json", "w") as f: - json.dump(result_dict[loop], f, indent=4, ensure_ascii=False) - time_loop_cost = time.time() - time_loop_start - loop_hour = int(time_loop_cost / 3600) - loop_min = int((time_loop_cost % 3600) / 60) - loop_sec = int(time_loop_cost % 60) - msg(f"loop {loop} cost {loop_hour} hours {loop_min} minutes {loop_sec} seconds, dir: {loop_dir}") + try: + msg(f"run loop {loop}/{args.loop}") + data_dir = os.path.join(tperf_top_dir, "logs", "perf") + case_log_dir = os.path.join(tperf_top_dir, "logs", "testcase") + if os.path.exists(data_dir): + shutil.rmtree(data_dir) + if os.path.exists(case_log_dir): + shutil.rmtree(case_log_dir) + os.makedirs(loop_dir) + with open(os.path.join(loop_dir, "env-info.json"), "w") as f: + json.dump(get_node_env_info(config=config), f, indent=4, ensure_ascii=False) + result_dict[loop] = run_tperf(template=template, runner_id=runner_id) + dbg(result_dict[loop]) + shutil.move(src=data_dir, dst=os.path.join(loop_dir, "perf")) + shutil.move(src=case_log_dir, dst=os.path.join(loop_dir, "testcase")) + with open(f"{loop_dir}.json", "w") as f: + json.dump(result_dict[loop], f, indent=4, ensure_ascii=False) + finally: + # 即使有异常也输出轮次耗时 + time_loop_cost = time.time() - time_loop_start + loop_hour = int(time_loop_cost / 3600) + loop_min = int((time_loop_cost % 3600) / 60) + loop_sec = int(time_loop_cost % 60) + msg(f"loop {loop} cost {loop_hour} hours {loop_min} minutes {loop_sec} seconds, dir: {loop_dir}") dbg(json.dumps(result_dict, indent=4, ensure_ascii=False)) result_json = os.path.join(result_dir, f"report-{runner_id}.json")