Skip to content

Commit a33cb81

Browse files
authored
Merge pull request #1354 from stratosphereips/alya/fix_performance_profilers
Fix CPU profiler "dev" mode
2 parents da34942 + 46f07f9 commit a33cb81

File tree

12 files changed

+114
-89
lines changed

12 files changed

+114
-89
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Slips v1.1.7
2323
[![License](https://img.shields.io/badge/Blog-Stratosphere-cyan)](https://www.stratosphereips.org/blog/tag/slips)
2424
[![Discord](https://img.shields.io/discord/761894295376494603?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/zu5HwMFy5C)
2525
![Twitter Follow](https://img.shields.io/twitter/follow/StratosphereIPS?style=social)
26+
2627
<hr>
2728

2829

config/slips.yaml

+13-6
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ Docker:
455455
#############################
456456
Profiling:
457457
# CPU profiling
458-
# enable cpu profiling [yes,no]
458+
# enable cpu profiling [true/false]
459+
# NOTE: the cpu profiler uses port 9001 to show the results.
459460
cpu_profiler_enable: false
460461

461462
# Available options are [dev,live]
@@ -466,25 +467,31 @@ Profiling:
466467
# time. it is accessible from web interface
467468
cpu_profiler_mode: dev
468469

469-
# profile all subprocesses in dev mode [yes,no].
470-
cpu_profiler_multiprocess: true
470+
# decides whether the profiler tracks all processes or only one.
471+
# only used in dev mode [true,false].
472+
cpu_profiler_multiprocess: false
471473

472474
# set number of tracer entries (dev mode only)
473-
cpu_profiler_dev_mode_entries: 1000000
475+
# VizTracer uses a circular buffer to store the entries.
476+
# When there are too many entries, it will only store the latest ones
477+
# so you know what happened recently.
478+
# the more the entries, the more RAM viztracer is going to use.
479+
# https://viztracer.readthedocs.io/en/latest/basic_usage.html#circular-buffer-size
480+
cpu_profiler_dev_mode_entries: 500000
474481

475482
# set maximum output lines (live mode only)
476483
cpu_profiler_output_limit: 20
477484

478485
# set the wait time between sampling sequences in seconds (live mode only)
479486
cpu_profiler_sampling_interval: 20
480487

481-
# enable memory profiling [yes,no]
488+
# enable memory profiling [true,false]
482489
memory_profiler_enable: false
483490

484491
# set profiling mode [dev,live]
485492
memory_profiler_mode: live
486493

487-
# profile all subprocesses [yes,no]
494+
# profile all subprocesses [true,false]
488495
memory_profiler_multiprocess: true
489496

490497
#############################

docker/Dockerfile

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ ENV NVM_DIR=/root/.nvm
3030
SHELL ["/bin/bash", "-c"]
3131

3232

33-
# Install wget and add Zeek and redis repositories to our sources.
3433
RUN apt update && apt install -y --no-install-recommends \
3534
wget \
3635
ca-certificates \

docs/profiling_slips.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Use WASD to zoom in and move left/right.
8585
### CPU Profiler Live Mode
8686
#### Step 1
8787
Go to slips.yaml and make sure the settings are set correctly.
88-
```cpu_profiler_enable = yes```
88+
```cpu_profiler_enable = True```
8989
```cpu_profiler_mode = live```
9090

9191
You can also set maximum output lines (live mode only) to adjust profiler behavior.
@@ -112,15 +112,15 @@ If we print out the data getting sent to the “cpu_profile” redis channel, it
112112

113113
The memory profiler settings are much simpler.
114114
in slips.yaml, first, enable memory profiling
115-
```memory_profiler_enable = yes```
115+
```memory_profiler_enable = True```
116116

117117

118118
and set profiling mode:
119119

120120
```memory_profiler_mode = dev```
121121

122122
now, profile all subprocesses
123-
```memory_profiler_multiprocess = yes```
123+
```memory_profiler_multiprocess = True```
124124

125125
#### Step 2
126126

@@ -158,9 +158,9 @@ Under the table directory, the files are much simpler. They just show a table of
158158
Go to ```slips.yaml``` and use the following settings
159159

160160
```
161-
memory_profiler_enable = yes
162-
memory_profiler_mode = yes
163-
memory_profiler_multiprocess = yes
161+
memory_profiler_enable = True
162+
memory_profiler_mode = True
163+
memory_profiler_multiprocess = True
164164
```
165165

166166
### Step 2

managers/process_manager.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -721,9 +721,6 @@ def shutdown_gracefully(self):
721721
format_ = self.main.conf.export_labeled_flows_to().lower()
722722
self.main.db.export_labeled_flows(format_)
723723

724-
self.main.profilers_manager.cpu_profiler_release()
725-
self.main.profilers_manager.memory_profiler_release()
726-
727724
# if store_a_copy_of_zeek_files is set to yes in slips.yaml
728725
# copy the whole zeek_files dir to the output dir
729726
self.main.store_zeek_dir_copy()
@@ -740,6 +737,9 @@ def shutdown_gracefully(self):
740737
f"finished in {analysis_time:.2f} minutes"
741738
)
742739

740+
self.main.profilers_manager.cpu_profiler_release()
741+
self.main.profilers_manager.memory_profiler_release()
742+
743743
self.main.db.close()
744744
if graceful_shutdown:
745745
print(

managers/profilers_manager.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import os
44
import subprocess
55
import sys
6+
from slips_files.common.style import green
67

78

89
class ProfilersManager:
910
def __init__(self, main):
1011
self.main = main
12+
self.args = self.main.args
1113
self.read_configurations()
1214

1315
def read_configurations(self):
@@ -50,28 +52,38 @@ def cpu_profiler_init(self):
5052
args = sys.argv
5153
if args[-1] != "--no-recurse":
5254
tracer_entries = str(self.cpu_profiler_dev_mode_entries)
55+
output_file = str(
56+
os.path.join(
57+
self.args.output,
58+
"cpu_profiling_result.json",
59+
)
60+
)
5361
viz_args = [
5462
"viztracer",
5563
"--tracer_entries",
5664
tracer_entries,
5765
"--max_stack_depth",
58-
"10",
66+
"5",
5967
"-o",
60-
str(
61-
os.path.join(
62-
self.args.output,
63-
"cpu_profiling_result.json",
64-
)
65-
),
68+
output_file,
69+
# viztracer takes -- as a separator between arguments
70+
# to viztracer and positional arguments to your script.
71+
"--",
6672
]
73+
# add slips args
6774
viz_args.extend(args)
75+
# add --no-recurse to avoid infinite recursion
6876
viz_args.append("--no-recurse")
6977
print(
70-
"Starting multiprocess profiling recursive subprocess"
78+
f"Starting multiprocess profiling recursive "
79+
f"subprocess using command: "
80+
f"{green(' '.join(viz_args))}"
7181
)
7282
subprocess.run(viz_args)
7383
exit(0)
7484
else:
85+
# reaching here means slips is now running using the vistracer
86+
# command
7587
self.cpu_profiler = CPUProfiler(
7688
db=self.main.db,
7789
output=self.args.output,

slips/main.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ def __init__(self, testing=False):
4545
self.conf = ConfigParser()
4646
self.metadata_man = MetadataManager(self)
4747
self.ui_man = UIManager(self)
48-
self.profilers_manager = ProfilersManager(self)
4948
self.version = utils.get_slips_version()
5049
# will be filled later
5150
self.commit = "None"
@@ -57,6 +56,7 @@ def __init__(self, testing=False):
5756
# TODO use mocks instead of this testing param
5857
if not testing:
5958
self.args = self.conf.get_args()
59+
self.profilers_manager = ProfilersManager(self)
6060
self.pid = os.getpid()
6161
self.checker.check_given_flags()
6262

slips_files/common/performance_profilers/cpu_profiler.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
class CPUProfiler(IPerformanceProfiler):
1717
def __init__(self, db, output, mode="dev", limit=20, interval=20):
18+
"""
19+
:param output: the currently used slips output directory
20+
"""
1821
valid_modes = ["dev", "live"]
1922
if mode not in valid_modes:
2023
print(
@@ -46,6 +49,9 @@ def print(self):
4649

4750
class DevProfiler(IPerformanceProfiler):
4851
def __init__(self, output):
52+
"""
53+
:param output: the currently used slips output directory
54+
"""
4955
self.profiler = self._create_profiler()
5056
self.output = output
5157

@@ -59,8 +65,12 @@ def stop(self):
5965
self.profiler.stop()
6066

6167
def print(self):
68+
"""
69+
Prints the profiling result to cpu_profiling_result in slips output
70+
directory
71+
"""
6272
result_path = os.path.join(self.output, "cpu_profiling_result.json")
63-
self.profiler.save(result_path)
73+
self.profiler.save(output_file=result_path)
6474

6575

6676
class LiveProfiler(IPerformanceProfiler):

slips_files/core/database/sqlite_db/database.py

-9
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,3 @@ def execute(self, query: str, params=None) -> None:
434434

435435
elif "database is locked" in str(err):
436436
sleep(5)
437-
else:
438-
self.print(
439-
f"Re-trying to execute query "
440-
f"({query} - {params}) - {err}. "
441-
f"Trial number: {trial}.",
442-
0,
443-
1,
444-
log_to_logfiles_only=True,
445-
)

tests/module_factory.py

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from managers.host_ip_manager import HostIPManager
1414
from managers.metadata_manager import MetadataManager
15+
from managers.profilers_manager import ProfilersManager
1516
from modules.flowalerts.conn import Conn
1617
from modules.threat_intelligence.circl_lu import Circllu
1718
from modules.threat_intelligence.spamhaus import Spamhaus
@@ -364,6 +365,13 @@ def create_redis_manager_obj(self, mock_db):
364365
main.args = Mock()
365366
return RedisManager(main)
366367

368+
@patch(MODULE_DB_MANAGER, name="mock_db")
369+
def create_profilers_manager_obj(self, mock_db):
370+
main = self.create_main_obj()
371+
main.db = mock_db
372+
main.args = Mock()
373+
return ProfilersManager(main)
374+
367375
@patch(MODULE_DB_MANAGER, name="mock_db")
368376
def create_host_ip_manager_obj(self, mock_db):
369377
main = self.create_main_obj()

tests/test_main.py

-54
Original file line numberDiff line numberDiff line change
@@ -60,60 +60,6 @@ def test_is_total_flows_unknown(args, input_type, expected_result):
6060
assert main.is_total_flows_unknown() == expected_result
6161

6262

63-
@pytest.mark.parametrize(
64-
"cpu_profiler_multiprocess, expected_stop_calls, expected_print_calls",
65-
[ # Testcase1: CPU profiler enabled, not multiprocess
66-
(False, 1, 1),
67-
# Testcase2: CPU profiler enabled, multiprocess
68-
(True, 0, 0),
69-
],
70-
)
71-
def test_cpu_profiler_release_enabled(
72-
cpu_profiler_multiprocess,
73-
expected_stop_calls,
74-
expected_print_calls,
75-
):
76-
main = ModuleFactory().create_main_obj()
77-
main.profilers_manager.cpu_profiler_enabled = True
78-
main.profilers_manager.cpu_profiler_multiprocess = (
79-
cpu_profiler_multiprocess
80-
)
81-
main.profilers_manager.cpu_profiler = MagicMock()
82-
main.profilers_manager.cpu_profiler_release()
83-
84-
assert (
85-
main.profilers_manager.cpu_profiler.stop.call_count
86-
== expected_stop_calls
87-
)
88-
assert (
89-
main.profilers_manager.cpu_profiler.print.call_count
90-
== expected_print_calls
91-
)
92-
93-
94-
def test_cpu_profiler_release_disabled():
95-
main = ModuleFactory().create_main_obj()
96-
main.profilers_manager.cpu_profiler_enabled = False
97-
main.profilers_manager.cpu_profiler_release()
98-
assert not hasattr(main.profilers_manager, "memory_profiler")
99-
100-
101-
def test_memory_profiler_release_enabled():
102-
main = ModuleFactory().create_main_obj()
103-
main.profilers_manager.memory_profiler_enabled = True
104-
main.profilers_manager.memory_profiler = MagicMock()
105-
main.profilers_manager.memory_profiler_release()
106-
main.profilers_manager.memory_profiler.stop.assert_called_once()
107-
108-
109-
def test_memory_profiler_release_disabled():
110-
main = ModuleFactory().create_main_obj()
111-
main.profilers_manager.memory_profiler_enabled = False
112-
main.profilers_manager.memory_profiler_release()
113-
114-
assert not hasattr(main.profilers_manager, "memory_profiler")
115-
116-
11763
@pytest.mark.parametrize(
11864
"mode, time_diff, expected_calls",
11965
[ # Testcase1: Should update stats

tests/test_profilers_manager.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# SPDX-FileCopyrightText: 2021 Sebastian Garcia <[email protected]>
2+
# SPDX-License-Identifier: GPL-2.0-only
3+
from unittest.mock import MagicMock
4+
import pytest
5+
from tests.module_factory import ModuleFactory
6+
7+
8+
@pytest.mark.parametrize(
9+
"cpu_profiler_multiprocess, expected_stop_calls, expected_print_calls",
10+
[ # Testcase1: CPU profiler enabled, not multiprocess
11+
(False, 1, 1),
12+
# Testcase2: CPU profiler enabled, multiprocess
13+
(True, 0, 0),
14+
],
15+
)
16+
def test_cpu_profiler_release_enabled(
17+
cpu_profiler_multiprocess,
18+
expected_stop_calls,
19+
expected_print_calls,
20+
):
21+
handler = ModuleFactory().create_profilers_manager_obj()
22+
handler.cpu_profiler_enabled = True
23+
handler.cpu_profiler_multiprocess = cpu_profiler_multiprocess
24+
handler.cpu_profiler = MagicMock()
25+
handler.cpu_profiler_release()
26+
27+
assert handler.cpu_profiler.stop.call_count == expected_stop_calls
28+
assert handler.cpu_profiler.print.call_count == expected_print_calls
29+
30+
31+
def test_cpu_profiler_release_disabled():
32+
handler = ModuleFactory().create_profilers_manager_obj()
33+
handler.cpu_profiler_enabled = False
34+
handler.cpu_profiler_release()
35+
assert not hasattr(handler, "memory_profiler")
36+
37+
38+
def test_memory_profiler_release_enabled():
39+
handler = ModuleFactory().create_profilers_manager_obj()
40+
handler.memory_profiler_enabled = True
41+
handler.memory_profiler = MagicMock()
42+
handler.memory_profiler_release()
43+
handler.memory_profiler.stop.assert_called_once()
44+
45+
46+
def test_memory_profiler_release_disabled():
47+
handler = ModuleFactory().create_profilers_manager_obj()
48+
handler.memory_profiler_enabled = False
49+
handler.memory_profiler_release()
50+
51+
assert not hasattr(handler, "memory_profiler")

0 commit comments

Comments
 (0)