Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/valgrind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ jobs:
- run: echo VALGRIND_ARGS="--tool=massif --massif-out-file=./${{ env.MASSIF_REPORT_FILE_NAME }}" >> $GITHUB_ENV
if: ${{ inputs.massif }}

- run: echo VALGRIND_ARGS="--leak-check=full" >> $GITHUB_ENV
- run: echo VALGRIND_ARGS="--leak-check=full --gen-suppressions=all --suppressions=./valgrind-suppression/new_tests.supp --num-callers=350 " >> $GITHUB_ENV
if: ${{ !inputs.massif }}

- run: PYTHONMALLOC=malloc valgrind --error-exitcode=1 ${{ env.VALGRIND_ARGS }} python3 -m pytest -v new_tests/${{ github.event.inputs.test-file }}
Expand Down
63 changes: 63 additions & 0 deletions test/valgrind-suppression/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Generating valgrind suppressions

## 1. Run the valgrind action with the following arguments

```
--error-limit=no --leak-check=full --gen-suppressions=all --num-callers=350

```

--error-limit=no: Disable the limit of the numbers errors that can be reported
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
--error-limit=no: Disable the limit of the numbers errors that can be reported
`--error-limit=no`: Disable the limit of the numbers errors that can be reported


--leak-check=full: Show details for each individual memory error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
--leak-check=full: Show details for each individual memory error
`--leak-check=full`: Show details for each individual memory error


--gen-suppressions=all: generate suppressions blocks alongside all memory errors
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
--gen-suppressions=all: generate suppressions blocks alongside all memory errors
`--gen-suppressions=all`: generate suppressions blocks alongside all memory errors


--num-callers=350: include 350 stack frames of context for each error and suppression.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
--num-callers=350: include 350 stack frames of context for each error and suppression.
`--num-callers=350`: include 350 stack frames of context for each error and suppression.


**Note:** Additional stack frames decrease the likelihood of over suppression by increasing the specificity of each suppression.
However, more stack frames also reduce the speed of the test runs.
Suppressions may not exceed 500 stack frames or else the valgrind will fail with the following error:

```
too many callers in stack trace
```

When --num-callers=500, suppressions may end up having 600+ frames due to inline frames not counting towards the total number of callers.

Setting --num-callers=350 forces the total number of suppressions frames to be below 500 and avoids this error

## 2. Download the results of the valgrind run step
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## 2. Download the results of the valgrind run step
## 2. Download the logs for the valgrind job

?


Valgrind action runs available [here]https://github.com/aerospike/aerospike-client-python/actions/workflows/valgrind.yml

## 3. Extract suppressions from logs

Run `extract_suppressions.py` from the `valgrind-suppressions` directory to extract any unique expressions from the valgrind logs.

Script usage:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Script usage:
Pass in the Github Actions job logs as the input file and the suppressions file name as the output file. Example usage:


```
python3 extract_suppressions.py log_input.txt new_suppressions.supp
```

For the full-suite, name the output file new_tests.supp

For a single file, name the output file after the name of the test (test_log.py.supp)

## 4. Add suppression file to valgrind argument

Add the following argument to the valgrind arguments:

```
--suppressions=./valgrind-suppressions/new_suppressions.supp
```

**Note:** If the suppressions are not able to be parsed when running valgrind, look for timestamps in the suppression file.
The script should strip them out, but if the logs are corrupted, they might not be stripped out correctly

## 5. Add any additional suppressions from additional valgrind runs

While the suppressions generated should cover most memory issues, some errors show up intermittendly.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
While the suppressions generated should cover most memory issues, some errors show up intermittendly.
While the suppressions generated should cover most memory issues, some errors show up intermittently.


When new false positive error cases pop up in valgrind logs, simply add the newly generated suppressions to the suppression file.
64 changes: 64 additions & 0 deletions test/valgrind-suppression/extract_suppressions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import argparse
import re

parser = argparse.ArgumentParser()
parser.add_argument("input_file", help="path to input file")
parser.add_argument("output_file", help="path to output file")
args = parser.parse_args()

input_file = args.input_file
output_file = args.output_file

unique_blocks = set()
current_block = []
inside_block = False
total_blocks = 0

# Matches the time stamp
ts_re = re.compile(r"^\d{4}-\d{2}-\d{2}T\S+\s+(.*)$")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ts_re = re.compile(r"^\d{4}-\d{2}-\d{2}T\S+\s+(.*)$")
timestamp_pattern = re.compile(r"^\d{4}-\d{2}-\d{2}T\S+\s+(.*)$")

Might be better to rename ts_re to timestamp_pattern?


with open(input_file) as f:
for raw_line in f:
line = raw_line.rstrip()

# Strip timestamp prefix if present
m = ts_re.match(line)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
m = ts_re.match(line)
ts_match = ts_re.match(line)

Rename m to ts_match?

if m:
line = m.group(1)

if line.startswith("{"):
inside_block = True
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inside_block = True
inside_suppressions_block = True

current_block = [line]
elif line.startswith("}"):
current_block.append(line)
inside_block = False
total_blocks += 1

# Only keep if 2nd line matches required suppression name
REQUIRED = "<insert_a_suppression_name_here>"

if len(current_block) > 1 and current_block[1].strip() != REQUIRED:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we pass in a suppressions file when running valgrind, and the suppressions file contains a block with a named suppression, in the logs does valgrind print a suppressions block with that name? I'm not sure when this condition would be true

continue # skip this block

block_str = "\n".join(l.rstrip() for l in current_block)
unique_blocks.add(block_str)

elif inside_block:
current_block.append(line)

# Write unique blocks with tabs for inner lines only
with open(output_file, "w") as f:
for block in sorted(unique_blocks):
lines = block.split("\n")
# Keep first line `{` as is, indent all lines in between, last line `}` as is
if len(lines) > 2:
indented_block = "\n".join(
[lines[0]] + ["\t" + line for line in lines[1:-1]] + [lines[-1]]
)
else:
# Blocks with only {} or {} plus one line
indented_block = "\n".join(lines)
f.write(indented_block + "\n\n")

print(f"Original number of suppressions: {total_blocks}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(f"Original number of suppressions: {total_blocks}")
print(f"Number of suppressions (including duplicates): {total_blocks}")

print(f"Number of unique suppressions: {len(unique_blocks)}")
Loading
Loading