xref: /openbsd-src/gnu/llvm/clang/utils/analyzer/SATestUtils.py (revision ec727ea710c91afd8ce4f788c5aaa8482b7b69b2)
1e5dd7070Spatrickimport os
2e5dd7070Spatrickimport sys
3*ec727ea7Spatrickimport time
4*ec727ea7Spatrick
5*ec727ea7Spatrickfrom subprocess import CalledProcessError, check_call
6*ec727ea7Spatrickfrom typing import List, IO, Optional, Tuple
7e5dd7070Spatrick
8e5dd7070Spatrick
9*ec727ea7Spatrickdef which(command: str, paths: Optional[str] = None) -> Optional[str]:
10e5dd7070Spatrick    """which(command, [paths]) - Look up the given command in the paths string
11e5dd7070Spatrick    (or the PATH environment variable, if unspecified)."""
12e5dd7070Spatrick
13e5dd7070Spatrick    if paths is None:
14e5dd7070Spatrick        paths = os.environ.get('PATH', '')
15e5dd7070Spatrick
16e5dd7070Spatrick    # Check for absolute match first.
17e5dd7070Spatrick    if os.path.exists(command):
18e5dd7070Spatrick        return command
19e5dd7070Spatrick
20e5dd7070Spatrick    # Would be nice if Python had a lib function for this.
21e5dd7070Spatrick    if not paths:
22e5dd7070Spatrick        paths = os.defpath
23e5dd7070Spatrick
24e5dd7070Spatrick    # Get suffixes to search.
25e5dd7070Spatrick    # On Cygwin, 'PATHEXT' may exist but it should not be used.
26e5dd7070Spatrick    if os.pathsep == ';':
27e5dd7070Spatrick        pathext = os.environ.get('PATHEXT', '').split(';')
28e5dd7070Spatrick    else:
29e5dd7070Spatrick        pathext = ['']
30e5dd7070Spatrick
31e5dd7070Spatrick    # Search the paths...
32e5dd7070Spatrick    for path in paths.split(os.pathsep):
33e5dd7070Spatrick        for ext in pathext:
34e5dd7070Spatrick            p = os.path.join(path, command + ext)
35e5dd7070Spatrick            if os.path.exists(p):
36e5dd7070Spatrick                return p
37e5dd7070Spatrick
38e5dd7070Spatrick    return None
39e5dd7070Spatrick
40e5dd7070Spatrick
41*ec727ea7Spatrickdef has_no_extension(file_name: str) -> bool:
42*ec727ea7Spatrick    root, ext = os.path.splitext(file_name)
43*ec727ea7Spatrick    return ext == ""
44e5dd7070Spatrick
45e5dd7070Spatrick
46*ec727ea7Spatrickdef is_valid_single_input_file(file_name: str) -> bool:
47*ec727ea7Spatrick    root, ext = os.path.splitext(file_name)
48*ec727ea7Spatrick    return ext in (".i", ".ii", ".c", ".cpp", ".m", "")
49e5dd7070Spatrick
50e5dd7070Spatrick
51*ec727ea7Spatrickdef time_to_str(time: float) -> str:
52*ec727ea7Spatrick    """
53*ec727ea7Spatrick    Convert given time in seconds into a human-readable string.
54*ec727ea7Spatrick    """
55*ec727ea7Spatrick    return f"{time:.2f}s"
56*ec727ea7Spatrick
57*ec727ea7Spatrick
58*ec727ea7Spatrickdef memory_to_str(memory: int) -> str:
59*ec727ea7Spatrick    """
60*ec727ea7Spatrick    Convert given number of bytes into a human-readable string.
61*ec727ea7Spatrick    """
62*ec727ea7Spatrick    if memory:
63*ec727ea7Spatrick        try:
64*ec727ea7Spatrick            import humanize
65*ec727ea7Spatrick            return humanize.naturalsize(memory, gnu=True)
66*ec727ea7Spatrick        except ImportError:
67*ec727ea7Spatrick            # no formatter installed, let's keep it in bytes
68*ec727ea7Spatrick            return f"{memory}B"
69*ec727ea7Spatrick
70*ec727ea7Spatrick    # If memory is 0, we didn't succeed measuring it.
71*ec727ea7Spatrick    return "N/A"
72*ec727ea7Spatrick
73*ec727ea7Spatrick
74*ec727ea7Spatrickdef check_and_measure_call(*popenargs, **kwargs) -> Tuple[float, int]:
75*ec727ea7Spatrick    """
76*ec727ea7Spatrick    Run command with arguments.  Wait for command to complete and measure
77*ec727ea7Spatrick    execution time and peak memory consumption.
78*ec727ea7Spatrick    If the exit code was zero then return, otherwise raise
79*ec727ea7Spatrick    CalledProcessError.  The CalledProcessError object will have the
80*ec727ea7Spatrick    return code in the returncode attribute.
81*ec727ea7Spatrick
82*ec727ea7Spatrick    The arguments are the same as for the call and check_call functions.
83*ec727ea7Spatrick
84*ec727ea7Spatrick    Return a tuple of execution time and peak memory.
85*ec727ea7Spatrick    """
86*ec727ea7Spatrick    peak_mem = 0
87*ec727ea7Spatrick    start_time = time.time()
88*ec727ea7Spatrick
89*ec727ea7Spatrick    try:
90*ec727ea7Spatrick        import psutil as ps
91*ec727ea7Spatrick
92*ec727ea7Spatrick        def get_memory(process: ps.Process) -> int:
93*ec727ea7Spatrick            mem = 0
94*ec727ea7Spatrick
95*ec727ea7Spatrick            # we want to gather memory usage from all of the child processes
96*ec727ea7Spatrick            descendants = list(process.children(recursive=True))
97*ec727ea7Spatrick            descendants.append(process)
98*ec727ea7Spatrick
99*ec727ea7Spatrick            for subprocess in descendants:
100*ec727ea7Spatrick                try:
101*ec727ea7Spatrick                    mem += subprocess.memory_info().rss
102*ec727ea7Spatrick                except (ps.NoSuchProcess, ps.AccessDenied):
103*ec727ea7Spatrick                    continue
104*ec727ea7Spatrick
105*ec727ea7Spatrick            return mem
106*ec727ea7Spatrick
107*ec727ea7Spatrick        with ps.Popen(*popenargs, **kwargs) as process:
108*ec727ea7Spatrick            # while the process is running calculate resource utilization.
109*ec727ea7Spatrick            while (process.is_running() and
110*ec727ea7Spatrick                   process.status() != ps.STATUS_ZOMBIE):
111*ec727ea7Spatrick                # track the peak utilization of the process
112*ec727ea7Spatrick                peak_mem = max(peak_mem, get_memory(process))
113*ec727ea7Spatrick                time.sleep(.5)
114*ec727ea7Spatrick
115*ec727ea7Spatrick            if process.is_running():
116*ec727ea7Spatrick                process.kill()
117*ec727ea7Spatrick
118*ec727ea7Spatrick        if process.returncode != 0:
119*ec727ea7Spatrick            cmd = kwargs.get("args")
120*ec727ea7Spatrick            if cmd is None:
121*ec727ea7Spatrick                cmd = popenargs[0]
122*ec727ea7Spatrick            raise CalledProcessError(process.returncode, cmd)
123*ec727ea7Spatrick
124*ec727ea7Spatrick    except ImportError:
125*ec727ea7Spatrick        # back off to subprocess if we don't have psutil installed
126*ec727ea7Spatrick        peak_mem = 0
127*ec727ea7Spatrick        check_call(*popenargs, **kwargs)
128*ec727ea7Spatrick
129*ec727ea7Spatrick    return time.time() - start_time, peak_mem
130*ec727ea7Spatrick
131*ec727ea7Spatrick
132*ec727ea7Spatrickdef run_script(script_path: str, build_log_file: IO, cwd: str,
133*ec727ea7Spatrick               out=sys.stdout, err=sys.stderr, verbose: int = 0):
134e5dd7070Spatrick    """
135e5dd7070Spatrick    Run the provided script if it exists.
136e5dd7070Spatrick    """
137*ec727ea7Spatrick    if os.path.exists(script_path):
138e5dd7070Spatrick        try:
139*ec727ea7Spatrick            if verbose == 1:
140*ec727ea7Spatrick                out.write(f"  Executing: {script_path}\n")
141*ec727ea7Spatrick
142*ec727ea7Spatrick            check_call(f"chmod +x '{script_path}'", cwd=cwd,
143*ec727ea7Spatrick                       stderr=build_log_file,
144*ec727ea7Spatrick                       stdout=build_log_file,
145e5dd7070Spatrick                       shell=True)
146*ec727ea7Spatrick
147*ec727ea7Spatrick            check_call(f"'{script_path}'", cwd=cwd,
148*ec727ea7Spatrick                       stderr=build_log_file,
149*ec727ea7Spatrick                       stdout=build_log_file,
150e5dd7070Spatrick                       shell=True)
151*ec727ea7Spatrick
152*ec727ea7Spatrick        except CalledProcessError:
153*ec727ea7Spatrick            err.write(f"Error: Running {script_path} failed. "
154*ec727ea7Spatrick                      f"See {build_log_file.name} for details.\n")
155e5dd7070Spatrick            sys.exit(-1)
156e5dd7070Spatrick
157e5dd7070Spatrick
158*ec727ea7Spatrickdef is_comment_csv_line(entries: List[str]) -> bool:
159e5dd7070Spatrick    """
160e5dd7070Spatrick    Treat CSV lines starting with a '#' as a comment.
161e5dd7070Spatrick    """
162*ec727ea7Spatrick    return len(entries) > 0 and entries[0].startswith("#")
163