• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ZJU-SPAIL / pipa / 22699554347

05 Mar 2026 02:33AM UTC coverage: 19.922% (-50.1%) from 70.041%
22699554347

push

github

Rui Hu
Merge github/main, resolve two version conflicts by keeping gitlab local

6 of 2484 new or added lines in 35 files covered. (0.24%)

871 of 4372 relevant lines covered (19.92%)

0.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/pipa/parser/perf_script.py
NEW
1
import pandas as pd
×
NEW
2
import re
×
NEW
3
import multiprocessing
×
NEW
4
import os
×
NEW
5
from pipa.common.logger import logger
×
NEW
6
from pipa.common.hardware.cpu import NUM_CORES_PHYSICAL
×
NEW
7
from pandarallel import pandarallel
×
8

9

NEW
10
class PerfScriptData:
×
NEW
11
    def __init__(self, parsed_script_path: str, threads_num=NUM_CORES_PHYSICAL):
×
12
        """
13
        Initialize the PerfScriptParser object.
14

15
        Args:
16
            parsed_script_path (str): The path to the parsed script file.
17
            threads_num (int, optional): The number of threads to use for parsing. Defaults to min(12, NUM_CORES_PHYSICAL).
18

19
        Raises:
20
            FileNotFoundError: If the parsed script file does not exist.
21
        """
NEW
22
        if not os.path.exists(parsed_script_path):
×
NEW
23
            logger.error(f"File not found: {parsed_script_path}")
×
NEW
24
            raise FileNotFoundError()
×
NEW
25
        self._perf_script_data = parse_perf_script_file(parsed_script_path, threads_num)
×
26

NEW
27
        if len(self._perf_script_data) == 0:
×
NEW
28
            logger.warning("No data found in the perf script file.")
×
29

NEW
30
        if len(self._perf_script_data) >= 10**6:
×
NEW
31
            logger.info(f"Using pandarallel to speed up, threads_num is {threads_num}.")
×
NEW
32
            pandarallel.initialize(nb_workers=threads_num)
×
33

NEW
34
        self._df_wider = None
×
35

NEW
36
    def get_raw_data(self):
×
37
        """
38
        Returns the raw data of the perf script.
39

40
        Returns:
41
            The raw data of the perf script.
42
        """
NEW
43
        return self._perf_script_data
×
44

NEW
45
    def get_wider_data(self):
×
46
        """
47
        Returns a wider version of the performance script data, where the cycles and instructions
48
        are merged into a single DataFrame. The DataFrame includes columns for command, pid, cpu,
49
        time, cycles, instructions, addr, symbol, dso_short_name, and CPI (cycles per instruction).
50

51
        If the wider data has already been computed, it is returned from the cache. Otherwise, the
52
        wider data is computed by merging the cycles and instructions DataFrames and calculating
53
        the CPI.
54

55
        Returns:
56
            pandas.DataFrame: The wider version of the performance script data.
57
        """
NEW
58
        if self._df_wider is not None:
×
NEW
59
            return self._df_wider
×
60

NEW
61
        df = self._perf_script_data
×
NEW
62
        df_cycles = df.query("event == 'cycles'")
×
NEW
63
        df_insns = df.query("event == 'instructions'")
×
NEW
64
        df_wider = (
×
65
            df_cycles.merge(
66
                df_insns,
67
                on=[
68
                    "time",
69
                    "cpu",
70
                    "command",
71
                    "pid",
72
                    "addr",
73
                    "symbol",
74
                    "dso_short_name",
75
                ],
76
                suffixes=("_cycles", "_insns"),
77
            )
78
            .drop(columns=["event_cycles", "event_insns"])
79
            .rename(
80
                {
81
                    "value_cycles": "cycles",
82
                    "value_insns": "instructions",
83
                },
84
                axis=1,
85
            )
86
        )
87

NEW
88
        df_wider = df_wider[
×
89
            [
90
                "command",
91
                "pid",
92
                "cpu",
93
                "time",
94
                "cycles",
95
                "instructions",
96
                "addr",
97
                "symbol",
98
                "dso_short_name",
99
            ]
100
        ]
NEW
101
        df_wider["CPI"] = df_wider["cycles"] / df_wider["instructions"]
×
NEW
102
        self._df_wider = df_wider
×
NEW
103
        return df_wider
×
104

NEW
105
    def get_tidy_data(self, thread_list: list = None):
×
106
        """
107
        Returns a tidy version of the data by pivoting the wider data and renaming the columns.
108

109
        Args:
110
            thread_list (list): A list of hardware thread names to include in the tidy data. If None, all
111
                threads are included.
112

113
        Returns:
114
            pandas.DataFrame: A tidy version of the data.
115
        """
NEW
116
        df_wider = self.get_wider_data()
×
117

NEW
118
        if thread_list is not None:
×
NEW
119
            thread_list = [int(thread) for thread in thread_list]
×
NEW
120
            df_wider = df_wider[df_wider["cpu"].isin(thread_list)]
×
NEW
121
            if len(thread_list) == 1:
×
NEW
122
                return df_wider
×
NEW
123
        df_t = df_wider.pivot_table(
×
124
            index=["time"], columns="cpu", aggfunc="first"
125
        ).reset_index()
NEW
126
        df_t.columns = [f"{col[0]}_{col[1]}" for col in df_t.columns]
×
NEW
127
        df_t.rename(columns={"time_": "time"}, inplace=True)
×
NEW
128
        return df_t
×
129

130

NEW
131
def parse_one_line(line):
×
132
    """
133
    Parses a single line of input and returns a list of parsed values.
134

135
    Args:
136
        line (str): The input line to be parsed.
137

138
    Returns:
139
        list: A list containing the parsed values from the input line.
140
            The list contains the following elements in order:
141
            - command (str): The command name.
142
            - pid (int): The process ID.
143
            - tid (int): The thread ID.
144
            - cpu (int): The CPU number.
145
            - time (str): The time value.
146
            - value (int): The event value.
147
            - event (str): The event name.
148
            - addr (str): The address value.
149
            - symbol (str): The symbol name.
150
            - caller (str): The caller value.
151
    """
NEW
152
    try:
×
NEW
153
        try:
×
NEW
154
            pattern = r"(\S+|\:-\d+)\s+(\d+|-\d+)\s+\[(\d+)]\s+(\d+\.\d+):\s+(\d+)\s+(\S+):\s+(\S+)\s+(.*?)\s+\((\S+)\)"
×
155

NEW
156
            command, pid, cpu, time, value, event, addr, symbol, caller = re.match(
×
157
                pattern, line.strip()
158
            ).groups()
NEW
159
        except Exception as e:
×
NEW
160
            pattern = r"(\d+|-\d+)\s+\[(\d+)]\s+(\d+\.\d+):\s+(\d+)\s+(\S+):\s+(\S+)\s+(.*?)\s+\((\S+)\)"
×
NEW
161
            (
×
162
                pid,
163
                cpu,
164
                time,
165
                value,
166
                event,
167
                addr,
168
                symbol,
169
                caller,
170
            ) = re.match(pattern, line[15:].strip()).groups()
171

NEW
172
            command = line[:15].strip()
×
173

NEW
174
    except Exception as e:
×
NEW
175
        logger.warning("parse failed for line: " + line + "\n with error: " + str(e))
×
NEW
176
        return None
×
177

NEW
178
    return [
×
179
        command,
180
        int(pid),
181
        int(cpu),
182
        time,
183
        int(value),
184
        event,
185
        addr,
186
        symbol,
187
        caller,
188
    ]
189

190

NEW
191
def parse_perf_script_file(parsed_script_path: str, processes_num=NUM_CORES_PHYSICAL):
×
192
    """
193
    Parses a perf script file and returns the data as a pandas DataFrame.
194

195
    Args:
196
        parsed_script_path (str): The path to the perf script file.
197

198
    Returns:
199
        pandas.DataFrame: The parsed data as a DataFrame.
200
    """
201

202
    # Open the perf script file in read mode
NEW
203
    with open(parsed_script_path, "r") as file:
×
204
        # Read all lines from the file and remove leading/trailing whitespaces
NEW
205
        content = [l.strip() for l in file.readlines()]
×
206

207
    # Ensure that the content is not None
NEW
208
    if content is None:
×
NEW
209
        logger.info("content is None")
×
NEW
210
        return None
×
211

212
    # Define the separator used to split the header and table sections
NEW
213
    SEPARATOR = "# ========"
×
214

215
    # Check if the first line is the separator, and remove it if present
NEW
216
    if content[0] == SEPARATOR:
×
NEW
217
        content.pop(0)
×
218

NEW
219
    if SEPARATOR in content:
×
220
        # Find the index of the separator in the content list
NEW
221
        sep_idx = content.index(SEPARATOR)
×
222

223
        # Split the content into header and table sections
NEW
224
        header, table = content[:sep_idx], content[sep_idx + 2 :]
×
225
    else:
NEW
226
        table = content
×
227

NEW
228
    with multiprocessing.Pool(processes=processes_num) as pool:
×
NEW
229
        data = pool.map(parse_one_line, table)
×
230

NEW
231
    data = [d for d in data if d is not None]
×
232

233
    # Parse each line in the table section and create a DataFrame
NEW
234
    return pd.DataFrame(
×
235
        data,
236
        columns=[
237
            "command",
238
            "pid",
239
            "cpu",
240
            "time",
241
            "value",
242
            "event",
243
            "addr",
244
            "symbol",
245
            "dso_short_name",
246
        ],
247
    )
248

249

NEW
250
if __name__ == "__main__":
×
NEW
251
    parse_perf_script_file(input("input perf script text data path:"))
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc