PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions | Variables
audit_profiling_coverage Namespace Reference

Functions

str blank_comments_and_strings (str text)
 Replace comments, string and character literals with spaces, keeping offsets and newlines.
 
 find_functions (str code)
 Locate top-level function definitions in comment- and string-blanked C code.
 
 check_function (str code, int body_open, int body_close)
 Check one function body against the timer rules.
 
bool ends_in_return (str body)
 Whether a function body's last top-level statement is a return.
 
int line_of (str text, int offset)
 1-based line number of an offset.
 
int main ()
 Run the audit over src/*.c, include/*.h and tests/c, print problems.
 

Variables

 REPO_ROOT = Path(__file__).resolve().parents[2]
 
 SOURCES = sorted((REPO_ROOT / "src").glob("*.c"))
 
dict EXCLUDED
 
dict EXCLUDED_FILES
 
 BEGIN_RE = re.compile(r"\bPROFILE_(?:FUNCTION|HELPER)_BEGIN\b")
 
 END_TAIL_RE = re.compile(r"PROFILE_(?:FUNCTION|HELPER)_END\s*;\s*$")
 
 RETURN_RE = re.compile(r"\b(?:return\b|PetscFunctionReturn(?:Void)?\s*\‍()")
 
 FUNCT_RE = re.compile(r"\b__FUNCT__\b")
 
dict CONTROL_WORDS = {"if", "for", "while", "switch", "return", "sizeof", "do", "else"}
 

Detailed Description

Enforce that every eligible C function in src/ is profiled and closes its timer on every return.

Rules (docs/pages/09_Monitor_Reference.md, profiling):
- every function defined in src/*.c that is not `static inline` and is not on the exclusion
  list below begins exactly one timer, PROFILE_FUNCTION_BEGIN or PROFILE_HELPER_BEGIN;
- every explicit `return` and `PetscFunctionReturn`/`PetscFunctionReturnVoid` after the timer's
  BEGIN is immediately preceded by PROFILE_FUNCTION_END or PROFILE_HELPER_END, and a function whose
  body can fall off its end ends with one; error returns inside SETERRQ/PetscCall/CHKERRQ are
  not explicit returns and are exempt;
- no file defines or reads `__FUNCT__`: timers, logging and PETSc's error traces all take the
  name from `__func__`.

A missed END leaves the function's PETSc event open, after which its calls are no longer recorded.

Function Documentation

◆ blank_comments_and_strings()

str audit_profiling_coverage.blank_comments_and_strings ( str  text)

Replace comments, string and character literals with spaces, keeping offsets and newlines.

Parameters
[in]textC source.
Returns
Text of the same length in which only code remains.

Definition at line 62 of file audit_profiling_coverage.py.

62def blank_comments_and_strings(text: str) -> str:
63 """!
64 @brief Replace comments, string and character literals with spaces, keeping offsets and newlines.
65 @param[in] text C source.
66 @return Text of the same length in which only code remains.
67 """
68 out = list(text)
69 i, n = 0, len(text)
70 while i < n:
71 c = text[i]
72 if text.startswith("//", i):
73 j = text.find("\n", i)
74 j = n if j < 0 else j
75 for k in range(i, j):
76 out[k] = " "
77 i = j
78 elif text.startswith("/*", i):
79 j = text.find("*/", i + 2)
80 j = n if j < 0 else j + 2
81 for k in range(i, j):
82 if out[k] != "\n":
83 out[k] = " "
84 i = j
85 elif c in "\"'":
86 j = i + 1
87 while j < n and text[j] != c:
88 j += 2 if text[j] == "\\" else 1
89 for k in range(i + 1, min(j, n)):
90 if out[k] != "\n":
91 out[k] = " "
92 i = j + 1
93 elif c == "#" and (i == 0 or text[i - 1] == "\n"):
94 j = i
95 while True: # Preprocessor line, with continuations
96 e = text.find("\n", j)
97 e = n if e < 0 else e
98 if e > 0 and text[e - 1] == "\\":
99 j = e + 1
100 continue
101 break
102 for k in range(i, e):
103 out[k] = " "
104 i = e
105 else:
106 i += 1
107 return "".join(out)
108
109
Head of a generic C-style linked list.
Definition variables.h:496
Here is the caller graph for this function:

◆ find_functions()

audit_profiling_coverage.find_functions ( str  code)

Locate top-level function definitions in comment- and string-blanked C code.

Parameters
[in]codeOutput of blank_comments_and_strings().
Returns
List of (name, signature_start, body_open, body_close, is_static_inline).

Definition at line 110 of file audit_profiling_coverage.py.

110def find_functions(code: str):
111 """!
112 @brief Locate top-level function definitions in comment- and string-blanked C code.
113 @param[in] code Output of blank_comments_and_strings().
114 @return List of (name, signature_start, body_open, body_close, is_static_inline).
115 """
116 functions = []
117 depth, i, n = 0, 0, len(code)
118 stmt_start = 0
119 while i < n:
120 c = code[i]
121 if c == "{":
122 if depth == 0:
123 head = code[stmt_start:i]
124 m = re.search(r"\b([A-Za-z_]\w*)\s*\‍(([^;{}]*)\‍)\s*$", head, re.S)
125 if m and m.group(1) not in CONTROL_WORDS and not re.search(r"[=]\s*$", head.split("(")[0]):
126 j, d = i + 1, 1
127 while j < n and d:
128 d += (code[j] == "{") - (code[j] == "}")
129 j += 1
130 sig = head[: m.start()]
131 words = set(re.findall(r"\b\w+\b", sig))
132 functions.append((m.group(1), stmt_start, i, j - 1, "static" in words and "inline" in words))
133 i, stmt_start = j, j
134 continue
135 depth += 1
136 elif c == "}":
137 depth -= 1
138 if depth == 0:
139 stmt_start = i + 1
140 elif c == ";" and depth == 0:
141 stmt_start = i + 1
142 i += 1
143 return functions
144
145
Here is the caller graph for this function:

◆ check_function()

audit_profiling_coverage.check_function ( str  code,
int  body_open,
int  body_close 
)

Check one function body against the timer rules.

Parameters
[in]codeBlanked source.
[in]body_openOffset of the body's opening brace.
[in]body_closeOffset of the body's closing brace.
Returns
List of problem strings with source offsets.

Definition at line 146 of file audit_profiling_coverage.py.

146def check_function(code: str, body_open: int, body_close: int):
147 """!
148 @brief Check one function body against the timer rules.
149 @param[in] code Blanked source.
150 @param[in] body_open Offset of the body's opening brace.
151 @param[in] body_close Offset of the body's closing brace.
152 @return List of problem strings with source offsets.
153 """
154 body = code[body_open + 1 : body_close]
155 problems = []
156 begins = len(BEGIN_RE.findall(body))
157 if begins != 1:
158 problems.append((body_open, f"has {begins} timer BEGIN macros, expected 1"))
159 return problems
160 timer_start = BEGIN_RE.search(body).start()
161 for m in RETURN_RE.finditer(body):
162 if m.start() < timer_start:
163 continue # An early return before the timer starts closes nothing
164 before = body[: m.start()].rstrip()
165 if not END_TAIL_RE.search(before):
166 problems.append((body_open + 1 + m.start(), "return not preceded by PROFILE_*_END"))
167 if not ends_in_return(body) and not END_TAIL_RE.search(body.rstrip()):
168 problems.append((body_close, "body can fall off its end without PROFILE_*_END"))
169 return problems
170
171
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ends_in_return()

bool audit_profiling_coverage.ends_in_return ( str  body)

Whether a function body's last top-level statement is a return.

A body ending in a block (}) or in any other statement can fall off its end.

Parameters
[in]bodyBlanked function body, without its braces.
Returns
True when the final statement returns.

Definition at line 172 of file audit_profiling_coverage.py.

172def ends_in_return(body: str) -> bool:
173 """!
174 @brief Whether a function body's last top-level statement is a return.
175 @details A body ending in a block (`}`) or in any other statement can fall off its end.
176 @param[in] body Blanked function body, without its braces.
177 @return True when the final statement returns.
178 """
179 tail = body.rstrip()
180 if not tail.endswith(";"):
181 return False
182 depth, start = 0, 0
183 for i, c in enumerate(tail[:-1]):
184 if c in "([{":
185 depth += 1
186 elif c in ")]}":
187 depth -= 1
188 if c == "}" and depth == 0:
189 start = i + 1
190 elif c == ";" and depth == 0:
191 start = i + 1
192 return bool(RETURN_RE.match(tail[start:].lstrip()))
193
194
Here is the caller graph for this function:

◆ line_of()

int audit_profiling_coverage.line_of ( str  text,
int  offset 
)

1-based line number of an offset.

Parameters
[in]textSource text.
[in]offsetCharacter offset.
Returns
Line number.

Definition at line 195 of file audit_profiling_coverage.py.

195def line_of(text: str, offset: int) -> int:
196 """!
197 @brief 1-based line number of an offset.
198 @param[in] text Source text.
199 @param[in] offset Character offset.
200 @return Line number.
201 """
202 return text.count("\n", 0, offset) + 1
203
204

◆ main()

int audit_profiling_coverage.main ( )

Run the audit over src/*.c, include/*.h and tests/c, print problems.

Returns
0 when clean, 1 otherwise.

Definition at line 205 of file audit_profiling_coverage.py.

205def main() -> int:
206 """!
207 @brief Run the audit over src/*.c, include/*.h and tests/c, print problems.
208 @return 0 when clean, 1 otherwise.
209 """
210 problems = []
211 seen = set()
212 for path in SOURCES:
213 text = path.read_text(encoding="utf-8")
214 code = blank_comments_and_strings(text)
215 rel = path.relative_to(REPO_ROOT)
216 if str(rel) in EXCLUDED_FILES:
217 continue
218 for name, _, body_open, body_close, static_inline in find_functions(code):
219 seen.add(name)
220 if static_inline or name in EXCLUDED:
221 continue
222 for offset, msg in check_function(code, body_open, body_close):
223 problems.append(f"{rel}:{line_of(text, offset)}: {name}: {msg}")
224 for path in sorted((REPO_ROOT / "src").glob("*.c")) + sorted((REPO_ROOT / "include").glob("*.h")) + sorted((REPO_ROOT / "tests" / "c").glob("*.[ch]")):
225 text = path.read_text(encoding="utf-8")
226 for m in FUNCT_RE.finditer(blank_comments_and_strings(text)):
227 problems.append(f"{path.relative_to(REPO_ROOT)}:{line_of(text, m.start())}: uses __FUNCT__; use __func__")
228 stale = sorted(set(EXCLUDED) - seen)
229 for name in stale:
230 problems.append(f"tests/tooling/audit_profiling_coverage.py: excluded function '{name}' no longer exists")
231 for p in problems:
232 print(p)
233 if problems:
234 print(f"\nFound {len(problems)} profiling coverage issue(s).", file=sys.stderr)
235 return 1
236 print("Profiling coverage audit passed: every eligible src/ function times itself and closes its timer.")
237 return 0
238
239
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ REPO_ROOT

audit_profiling_coverage.REPO_ROOT = Path(__file__).resolve().parents[2]

Definition at line 23 of file audit_profiling_coverage.py.

◆ SOURCES

audit_profiling_coverage.SOURCES = sorted((REPO_ROOT / "src").glob("*.c"))

Definition at line 24 of file audit_profiling_coverage.py.

◆ EXCLUDED

dict audit_profiling_coverage.EXCLUDED
Initial value:
1= {
2 "main": "process entry; runs before PetscInitialize and after PetscFinalize",
3 "RuntimeShutdownSignalHandler": "asynchronous signal handler; PETSc calls are not async-signal-safe",
4 "get_log_level": "read by every logging macro; a timer would tax every log site",
5 "is_function_allowed": "read by every logging macro that passes the level check",
6 "set_allowed_functions": "configures logging before the profiler exists",
7 "_ProfilingRegister": "the profiler itself",
8 "_ProfilingEnsureStarted": "the profiler itself",
9 "_ProfilingNameListed": "the profiler itself",
10 "_ProfilingHelperEnabled": "the profiler itself",
11 "_ProfilingIsPicurvClass": "the profiler itself",
12 "_ProfilingEventTotals": "the profiler itself",
13 "_ProfilingSnapshotStep": "the profiler itself",
14 "_ProfilingReduceSection": "the profiler itself",
15 "_CompareNames": "qsort comparator inside the profiler's report",
16 "_CompareProfiledFunctionRanks": "qsort comparator inside the profiler's report",
17 "ProfilingInitialize": "the profiler itself",
18 "ProfilingResetTimestepCounters": "the profiler itself",
19 "ProfilingLogTimestepSummary": "the profiler itself",
20 "ProfilingFinalize": "the profiler itself; it reads every event's totals",
21}

Definition at line 27 of file audit_profiling_coverage.py.

◆ EXCLUDED_FILES

dict audit_profiling_coverage.EXCLUDED_FILES
Initial value:
1= {
2 "src/checksum.c": "SHA-256 and digest byte helpers; a leaf module with no PICurv headers",
3 "src/statistics_moments.c": "per-sample moment arithmetic called per cell per step; a leaf module with no PICurv headers",
4}

Definition at line 50 of file audit_profiling_coverage.py.

◆ BEGIN_RE

audit_profiling_coverage.BEGIN_RE = re.compile(r"\bPROFILE_(?:FUNCTION|HELPER)_BEGIN\b")

Definition at line 55 of file audit_profiling_coverage.py.

◆ END_TAIL_RE

audit_profiling_coverage.END_TAIL_RE = re.compile(r"PROFILE_(?:FUNCTION|HELPER)_END\s*;\s*$")

Definition at line 56 of file audit_profiling_coverage.py.

◆ RETURN_RE

audit_profiling_coverage.RETURN_RE = re.compile(r"\b(?:return\b|PetscFunctionReturn(?:Void)?\s*\‍()")

Definition at line 57 of file audit_profiling_coverage.py.

◆ FUNCT_RE

audit_profiling_coverage.FUNCT_RE = re.compile(r"\b__FUNCT__\b")

Definition at line 58 of file audit_profiling_coverage.py.

◆ CONTROL_WORDS

dict audit_profiling_coverage.CONTROL_WORDS = {"if", "for", "while", "switch", "return", "sizeof", "do", "else"}

Definition at line 59 of file audit_profiling_coverage.py.