Skip to content

Commit beea70e

Browse files
Add AddressSanitizer and LeakSanitizer to ci/local dev (#322)
* Add AddressSanitizer and LeakSanitizer to ci/local dev * Removed spurious mbdirector reference. * Fixed uri detected leak. * Fixed uri detected leak.
1 parent f3545b0 commit beea70e

File tree

9 files changed

+228
-8
lines changed

9 files changed

+228
-8
lines changed

.github/workflows/asan.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: ASAN (AddressSanitizer & LeakSanitizer)
2+
3+
# Memory error and leak detection using AddressSanitizer and LeakSanitizer
4+
# This workflow builds memtier_benchmark with sanitizers enabled and runs
5+
# the full test suite to detect memory leaks and address errors.
6+
7+
on: [push, pull_request]
8+
9+
jobs:
10+
test-with-sanitizers:
11+
runs-on: ubuntu-latest
12+
name: Memory leak detection (ASAN/LSAN)
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Install build dependencies
18+
run: |
19+
sudo apt-get -qq update
20+
sudo apt-get install -y \
21+
build-essential \
22+
autoconf \
23+
automake \
24+
pkg-config \
25+
libevent-dev \
26+
zlib1g-dev \
27+
libssl-dev
28+
29+
- name: Build with sanitizers
30+
run: |
31+
autoreconf -ivf
32+
./configure --enable-sanitizers
33+
make -j
34+
35+
- name: Verify ASAN is enabled
36+
run: |
37+
ldd ./memtier_benchmark | grep asan
38+
echo "✓ AddressSanitizer is linked"
39+
40+
- name: Setup Python
41+
uses: actions/setup-python@v2
42+
with:
43+
python-version: '3.10'
44+
architecture: x64
45+
46+
- name: Install Python test dependencies
47+
run: pip install -r ./tests/test_requirements.txt
48+
49+
- name: Install Redis
50+
run: |
51+
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
52+
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
53+
sudo apt-get -qq update
54+
sudo apt-get install redis
55+
sudo service redis-server stop
56+
57+
- name: Increase connection limit
58+
run: |
59+
sudo sysctl -w net.ipv4.tcp_fin_timeout=10
60+
sudo sysctl -w net.ipv4.tcp_tw_reuse=1
61+
ulimit -n 40960
62+
63+
- name: Generate TLS test certificates
64+
run: ./tests/gen-test-certs.sh
65+
66+
- name: Test OSS TCP with ASAN
67+
timeout-minutes: 10
68+
run: |
69+
ASAN_OPTIONS=detect_leaks=1 ./tests/run_tests.sh
70+
71+
- name: Test OSS TCP TLS with ASAN
72+
timeout-minutes: 10
73+
run: |
74+
ASAN_OPTIONS=detect_leaks=1 TLS=1 ./tests/run_tests.sh
75+
76+
- name: Test OSS TCP TLS v1.2 with ASAN
77+
timeout-minutes: 10
78+
run: |
79+
ASAN_OPTIONS=detect_leaks=1 TLS_PROTOCOLS='TLSv1.2' TLS=1 ./tests/run_tests.sh
80+
81+
- name: Test OSS TCP TLS v1.3 with ASAN
82+
timeout-minutes: 10
83+
run: |
84+
ASAN_OPTIONS=detect_leaks=1 TLS_PROTOCOLS='TLSv1.3' TLS=1 ./tests/run_tests.sh
85+
86+
- name: Test OSS-CLUSTER TCP with ASAN
87+
timeout-minutes: 10
88+
run: |
89+
ASAN_OPTIONS=detect_leaks=1 OSS_STANDALONE=0 OSS_CLUSTER=1 ./tests/run_tests.sh
90+
91+
- name: Test OSS-CLUSTER TCP TLS with ASAN
92+
timeout-minutes: 10
93+
run: |
94+
ASAN_OPTIONS=detect_leaks=1 OSS_STANDALONE=0 OSS_CLUSTER=1 TLS=1 ./tests/run_tests.sh
95+

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,27 @@ To understand what test options are available simply run:
136136

137137
$ ./tests/run_tests.sh --help
138138

139+
140+
**Memory leak detection with sanitizers**
141+
142+
143+
memtier_benchmark supports building with AddressSanitizer (ASAN) and LeakSanitizer (LSAN) to detect memory errors and leaks during testing.
144+
145+
To build with sanitizers enabled:
146+
147+
$ ./configure --enable-sanitizers
148+
$ make
149+
150+
To run tests with leak detection:
151+
152+
$ ASAN_OPTIONS=detect_leaks=1 ./tests/run_tests.sh
153+
154+
If memory leaks or errors are detected, tests will fail with detailed error messages showing the location of the issue.
155+
156+
To verify ASAN is enabled:
157+
158+
$ ldd ./memtier_benchmark | grep asan
159+
139160
## Using Docker
140161

141162
Use available images on Docker Hub:

configure.ac

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ AS_IF([test "x$enable_tls" != "xno"], [
6969
AC_SUBST(LIBCRYPTO_CFLAGS) AC_SUBST(LIBCRYPTO_LIBS))
7070
], [])
7171

72+
# Sanitizers support (ASAN/LSAN) is optional.
73+
AC_ARG_ENABLE([sanitizers],
74+
[AS_HELP_STRING([--enable-sanitizers],
75+
[Enable AddressSanitizer and LeakSanitizer for memory error detection])])
76+
AS_IF([test "x$enable_sanitizers" = "xyes"], [
77+
AC_MSG_NOTICE([Enabling AddressSanitizer and LeakSanitizer])
78+
CXXFLAGS="$CXXFLAGS -fsanitize=address -fsanitize=leak -fno-omit-frame-pointer -O1"
79+
LDFLAGS="$LDFLAGS -fsanitize=address -fsanitize=leak"
80+
], [])
81+
7282
# clock_gettime requires -lrt on old glibc only.
7383
AC_SEARCH_LIBS([clock_gettime], [rt], , AC_MSG_ERROR([rt is required libevent.]))
7484

memtier_benchmark.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,16 @@ int main(int argc, char *argv[])
19561956
delete cfg.arbitrary_commands;
19571957
}
19581958

1959+
// Clean up dynamically allocated strings from URI parsing
1960+
if (cfg.uri) {
1961+
if (cfg.server) {
1962+
free((void*)cfg.server);
1963+
}
1964+
if (cfg.authenticate) {
1965+
free((void*)cfg.authenticate);
1966+
}
1967+
}
1968+
19591969
#ifdef USE_TLS
19601970
if(cfg.tls) {
19611971
if (cfg.openssl_ctx) {

tests/mb.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
Simple replacement for mbdirector package.
3+
Contains only the Benchmark and RunConfig classes needed for tests.
4+
"""
5+
import os
6+
import subprocess
7+
import logging
8+
9+
10+
class RunConfig(object):
11+
"""Configuration for a benchmark run."""
12+
next_id = 1
13+
14+
def __init__(self, base_results_dir, name, config, benchmark_config):
15+
self.id = RunConfig.next_id
16+
RunConfig.next_id += 1
17+
18+
self.redis_process_port = config.get('redis_process_port', 6379)
19+
20+
mbconfig = config.get('memtier_benchmark', {})
21+
mbconfig.update(benchmark_config)
22+
self.mb_binary = mbconfig.get('binary', 'memtier_benchmark')
23+
self.mb_threads = mbconfig.get('threads')
24+
self.mb_clients = mbconfig.get('clients')
25+
self.mb_pipeline = mbconfig.get('pipeline')
26+
self.mb_requests = mbconfig.get('requests')
27+
self.mb_test_time = mbconfig.get('test_time')
28+
self.explicit_connect_args = bool(
29+
mbconfig.get('explicit_connect_args'))
30+
31+
self.results_dir = os.path.join(base_results_dir,
32+
'{:04}_{}'.format(self.id, name))
33+
34+
def __repr__(self):
35+
return '<RunConfig id={}>'.format(self.id)
36+
37+
38+
class Benchmark(object):
39+
"""Benchmark runner for memtier_benchmark."""
40+
41+
def __init__(self, config, **kwargs):
42+
self.config = config
43+
self.binary = self.config.mb_binary
44+
self.name = kwargs['name']
45+
46+
# Configure
47+
self.args = [self.binary]
48+
if not self.config.explicit_connect_args:
49+
self.args += ['--server', '127.0.0.1',
50+
'--port', str(self.config.redis_process_port)
51+
]
52+
self.args += ['--out-file', os.path.join(config.results_dir,
53+
'mb.stdout'),
54+
'--json-out-file', os.path.join(config.results_dir,
55+
'mb.json')]
56+
57+
if self.config.mb_threads is not None:
58+
self.args += ['--threads', str(self.config.mb_threads)]
59+
if self.config.mb_clients is not None:
60+
self.args += ['--clients', str(self.config.mb_clients)]
61+
if self.config.mb_pipeline is not None:
62+
self.args += ['--pipeline', str(self.config.mb_pipeline)]
63+
if self.config.mb_requests is not None:
64+
self.args += ['--requests', str(self.config.mb_requests)]
65+
if self.config.mb_test_time is not None:
66+
self.args += ['--test-time', str(self.config.mb_test_time)]
67+
68+
self.args += kwargs['args']
69+
70+
@classmethod
71+
def from_json(cls, config, json):
72+
return cls(config, **json)
73+
74+
def write_file(self, name, data):
75+
with open(os.path.join(self.config.results_dir, name), 'wb') as outfile:
76+
outfile.write(data)
77+
78+
def run(self):
79+
logging.debug(' Command: %s', ' '.join(self.args))
80+
process = subprocess.Popen(
81+
stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
82+
executable=self.binary, args=self.args)
83+
_stdout, _stderr = process.communicate()
84+
if _stderr:
85+
logging.debug(' >>> stderr <<<\n%s\n', _stderr)
86+
self.write_file('mb.stderr', _stderr)
87+
return process.wait() == 0
88+

tests/test_requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
redis>=3.0.0
2-
rltest==0.6.0
3-
git+https://github.com/RedisLabs/mbdirector.git@master
2+
rltest>=0.7.17

tests/tests_oss_simple_flow.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import tempfile
22
import json
33
from include import *
4-
from mbdirector.benchmark import Benchmark
5-
from mbdirector.runner import RunConfig
4+
from mb import Benchmark, RunConfig
65

76

87
def test_preload_and_set_get(env):

tests/tests_oss_zipfian_distribution.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
agg_info_commandstats,
1414
assert_minimum_memtier_outcomes
1515
)
16-
from mbdirector.benchmark import Benchmark
17-
from mbdirector.runner import RunConfig
16+
from mb import Benchmark, RunConfig
1817

1918

2019
def correlation_coeficient(x: list[float], y: list[float]) -> float:

tests/zipfian_benchmark_runner.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
assert_minimum_memtier_outcomes,
1313
get_expected_request_count,
1414
)
15-
from mbdirector.benchmark import Benchmark
16-
from mbdirector.runner import RunConfig
15+
from mb import Benchmark, RunConfig
1716

1817

1918
class MonitorThread(threading.Thread):

0 commit comments

Comments
 (0)