Skip to content

Commit 335a9ab

Browse files
author
Colman Yau
committed
Add test for new ProfilerDisabler implementation
1 parent 5014202 commit 335a9ab

File tree

4 files changed

+120
-54
lines changed

4 files changed

+120
-54
lines changed

codeguru_profiler_agent/profiler_disabler.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import time
33
import logging
44
from codeguru_profiler_agent.reporter.agent_configuration import AgentConfiguration
5-
from codeguru_profiler_agent.utils.time import current_milli_time
65

76
logger = logging.getLogger(__name__)
87
CHECK_KILLSWITCH_FILE_INTERVAL_SECONDS = 60
@@ -21,17 +20,17 @@ def __init__(self, environment, clock=time.time):
2120
self.memory_limit_bytes = environment['memory_limit_bytes']
2221

2322
def should_stop_sampling(self, profile=None):
24-
return (self.killswitch.is_killswitch_on()
25-
or self.cpu_usage_check.is_cpu_usage_limit_reached(profile)
26-
or profile is not None and self._is_memory_limit_reached(profile))
23+
return self.killswitch.is_killswitch_on() \
24+
or self.cpu_usage_check.is_sampling_cpu_usage_limit_reached(profile) \
25+
or self._is_memory_limit_reached(profile)
2726

2827
def should_stop_profiling(self, profile=None):
29-
return self.killswitch.is_killswitch_on() or \
30-
(profile is not None and self.cpu_usage_check.is_overall_cpu_limit_reached(profile)
31-
and self._is_memory_limit_reached(profile))
28+
return self.killswitch.is_killswitch_on() \
29+
or self.cpu_usage_check.is_overall_cpu_usage_limit_reached(profile) \
30+
or self._is_memory_limit_reached(profile)
3231

3332
def _is_memory_limit_reached(self, profile):
34-
return profile.get_memory_usage_bytes() > self.memory_limit_bytes
33+
return False if profile is None else profile.get_memory_usage_bytes() > self.memory_limit_bytes
3534

3635

3736
class CpuUsageCheck:
@@ -44,25 +43,25 @@ def __init__(self, timer):
4443
self.timer = timer
4544

4645
# This function carries out an overall cpu limit check that covers the cpu overhead caused for the full
47-
# sampling cycle: sample -> aggregate -> report -> refresh config. This has to be called with a profile
48-
# which captured the total cycle cpu time usage. hnhg
49-
def is_overall_cpu_limit_reached(self, profile):
46+
# sampling cycle: sample -> aggregate -> report -> refresh config. We expect this function to be called after
47+
# configuration refresh and profile submission.
48+
def is_overall_cpu_usage_limit_reached(self, profile=None):
5049
profiler_metric = self.timer.metrics.get("runProfiler")
5150
if not profile or not profiler_metric or profiler_metric.counter < MINIMUM_MEASURES_IN_DURATION_METRICS:
5251
return False
5352

54-
used_time_percentage = 100 * profiler_metric.total/profile.get_active_millis_since_start()
53+
used_time_percentage = 100 * profiler_metric.total/(profile.get_active_millis_since_start()/1000)
5554

5655
if used_time_percentage >= AgentConfiguration.get().cpu_limit_percentage:
5756
logger.debug(self.timer.metrics)
5857
logger.info(
59-
"Profiler cpu usage limit reached: {:.2f} % (limit: {:.2f} %), will stop CodeGuru Profiler.".format(
60-
used_time_percentage, AgentConfiguration.get().cpu_limit_percentage))
58+
"Profiler overall cpu usage limit reached: {:.2f} % (limit: {:.2f} %), will stop CodeGuru Profiler."
59+
.format(used_time_percentage, AgentConfiguration.get().cpu_limit_percentage))
6160
return True
6261
else:
6362
return False
6463

65-
def is_cpu_usage_limit_reached(self, profile=None):
64+
def is_sampling_cpu_usage_limit_reached(self, profile=None):
6665
sample_and_aggregate_metric = self.timer.metrics.get("sampleAndAggregate")
6766
if not sample_and_aggregate_metric or \
6867
sample_and_aggregate_metric.counter < MINIMUM_MEASURES_IN_DURATION_METRICS:
@@ -74,8 +73,8 @@ def is_cpu_usage_limit_reached(self, profile=None):
7473
if used_time_percentage >= AgentConfiguration.get().cpu_limit_percentage:
7574
logger.debug(self.timer.metrics)
7675
logger.info(
77-
"Profiler cpu usage limit reached: {:.2f} % (limit: {:.2f} %), will stop CodeGuru Profiler.".format(
78-
used_time_percentage, AgentConfiguration.get().cpu_limit_percentage))
76+
"Profiler sampling cpu usage limit reached: {:.2f} % (limit: {:.2f} %), will stop CodeGuru Profiler."
77+
.format(used_time_percentage, AgentConfiguration.get().cpu_limit_percentage))
7978
return True
8079
else:
8180
return False

codeguru_profiler_agent/profiler_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def start(self):
5151
5252
:return: True if the profiler was started successfully; False otherwise.
5353
"""
54-
if self.profiler_disabler.should_stop_sampling():
54+
if self.profiler_disabler.should_stop_profiling():
5555
logger.info("Profiler will not start.")
5656
return False
5757
self.scheduler.start()

test/unit/test_profiler_disabler.py

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ def set_agent_config(sampling_interval_seconds=1, cpu_limit_percentage=DEFAULT_C
2828

2929

3030
def assert_config_sampling_interval_used(process_duration_check, profile):
31-
assert process_duration_check.is_cpu_usage_limit_reached(profile)
31+
assert process_duration_check.is_sampling_cpu_usage_limit_reached(profile)
3232

3333
set_agent_config(sampling_interval_seconds=42, cpu_limit_percentage=80)
34-
assert not process_duration_check.is_cpu_usage_limit_reached(profile)
34+
assert not process_duration_check.is_sampling_cpu_usage_limit_reached(profile)
3535

3636

3737
class TestProfilerDisabler:
@@ -59,28 +59,57 @@ def test_it_sets_all_parameters(self):
5959
assert AgentConfiguration.get().cpu_limit_percentage == DEFAULT_CPU_LIMIT_PERCENTAGE
6060

6161

62-
class TestWhenAnyFails(TestProfilerDisabler):
63-
@before
64-
def before(self):
65-
super().before()
66-
self.profiler = Mock()
67-
self.disabler.killswitch = Mock()
68-
self.disabler.cpu_usage_check = Mock()
69-
self.disabler._is_memory_limit_reached = Mock(return_value=False)
70-
self.disabler.killswitch.is_killswitch_on = Mock(return_value=False)
71-
self.disabler.killswitch.is_process_duration_limit_reached = Mock(return_value=False)
62+
class TestShouldStopSampling:
63+
class TestWhenAnyFails(TestProfilerDisabler):
64+
@before
65+
def before(self):
66+
super().before()
67+
self.disabler.killswitch = Mock()
68+
self.disabler.cpu_usage_check = Mock()
69+
self.disabler.cpu_usage_check.is_sampling_cpu_usage_limit_reached = Mock(return_value=False)
70+
self.disabler._is_memory_limit_reached = Mock(return_value=False)
71+
self.disabler.killswitch.is_killswitch_on = Mock(return_value=False)
72+
self.disabler.killswitch.is_process_duration_limit_reached = Mock(return_value=False)
73+
assert not self.disabler.should_stop_sampling()
74+
75+
def test_it_stops_profiling_if_killswitch_is_on(self):
76+
self.disabler.killswitch.is_killswitch_on = Mock(return_value=True)
77+
assert self.disabler.should_stop_sampling()
78+
79+
def test_it_stops_profiling_if_memory_limit_is_reached(self):
80+
self.disabler._is_memory_limit_reached = Mock(return_value=True)
81+
assert self.disabler.should_stop_sampling()
82+
83+
def test_it_stops_profiling_if_process_duration_is_reached(self):
84+
self.disabler.cpu_usage_check.is_sampling_cpu_usage_limit_reached = Mock(return_value=True)
85+
assert self.disabler.should_stop_sampling()
86+
87+
88+
class TestShouldStopProfiling:
89+
class TestWhenAnyFails(TestProfilerDisabler):
90+
@before
91+
def before(self):
92+
super().before()
93+
self.profiler = Mock()
94+
self.disabler.killswitch = Mock()
95+
self.disabler.cpu_usage_check = Mock()
96+
self.disabler.cpu_usage_check.is_overall_cpu_usage_limit_reached = Mock(return_value=False)
97+
self.disabler._is_memory_limit_reached = Mock(return_value=False)
98+
self.disabler.killswitch.is_killswitch_on = Mock(return_value=False)
99+
self.disabler.killswitch.is_process_duration_limit_reached = Mock(return_value=False)
100+
assert not self.disabler.should_stop_profiling()
72101

73-
def test_it_stops_profiling_if_killswitch_is_on(self):
74-
self.disabler.killswitch.is_killswitch_on = Mock(return_value=True)
75-
assert self.disabler.should_stop_sampling(self.profiler)
102+
def test_it_stops_profiling_if_killswitch_is_on(self):
103+
self.disabler.killswitch.is_killswitch_on = Mock(return_value=True)
104+
assert self.disabler.should_stop_profiling()
76105

77-
def test_it_stops_profiling_if_memory_limit_is_reached(self):
78-
self.disabler._is_memory_limit_reached = Mock(return_value=True)
79-
assert self.disabler.should_stop_sampling(self.profiler)
106+
def test_it_stops_profiling_if_memory_limit_is_reached(self):
107+
self.disabler._is_memory_limit_reached = Mock(return_value=True)
108+
assert self.disabler.should_stop_profiling()
80109

81-
def test_it_stops_profiling_if_process_duration_is_reached(self):
82-
self.disabler.cpu_usage_check.is_cpu_usage_limit_reached = Mock(return_value=True)
83-
assert self.disabler.should_stop_sampling(self.profiler)
110+
def test_it_stops_profiling_if_process_duration_is_reached(self):
111+
self.disabler.cpu_usage_check.is_overall_cpu_usage_limit_reached = Mock(return_value=True)
112+
assert self.disabler.should_stop_profiling()
84113

85114

86115
class TestKillSwitch:
@@ -145,7 +174,7 @@ def test_it_returns_false_after_a_minute(self):
145174
assert not self.killswitch.is_killswitch_on()
146175

147176

148-
class TestCpuUsageCheck:
177+
class TestSamplingCpuUsageCheck:
149178
def before(self):
150179
self.timer = Timer()
151180
self.profile = Mock(spec=Profile)
@@ -155,7 +184,7 @@ def before(self):
155184
self.process_duration_check = CpuUsageCheck(self.timer)
156185

157186

158-
class TestGetAverageSamplingIntervalSeconds(TestCpuUsageCheck):
187+
class TestGetAverageSamplingIntervalSeconds(TestSamplingCpuUsageCheck):
159188
@before
160189
def before(self):
161190
super().before()
@@ -176,7 +205,7 @@ def test_when_profiler_sample_count_less_than_min_samples_in_profile_it_returns_
176205
assert CpuUsageCheck._get_average_sampling_interval_seconds(self.profile) == 23
177206

178207

179-
class TestIsCpuUsageLimitReached(TestCpuUsageCheck):
208+
class TestIsSamplingCpuUsageLimitReached(TestSamplingCpuUsageCheck):
180209
@before
181210
def before(self):
182211
super().before()
@@ -187,43 +216,70 @@ def before(self):
187216
yield
188217

189218
def test_it_calls_get_average_sampling_interval_with_profile(self):
190-
self.process_duration_check.is_cpu_usage_limit_reached(self.profile)
219+
self.process_duration_check.is_sampling_cpu_usage_limit_reached(self.profile)
191220
self.get_average_sampling_interval_mock.assert_called_once_with(self.profile)
192221

193222
def test_when_average_duration_exceeds_limit_it_returns_true(self):
194223
# timer: (0.5/4) * 100= 12.5%
195-
assert self.process_duration_check.is_cpu_usage_limit_reached()
224+
assert self.process_duration_check.is_sampling_cpu_usage_limit_reached()
196225

197-
def test_when_average_duragtion_is_below_limit_it_returns_false(self):
226+
def test_when_average_duration_is_below_limit_it_returns_false(self):
198227
# timer: (0.5/4) * 100= 12.5%
199228
set_agent_config(cpu_limit_percentage=13)
200-
assert not self.process_duration_check.is_cpu_usage_limit_reached()
229+
assert not self.process_duration_check.is_sampling_cpu_usage_limit_reached()
201230

202231
def test_when_profile_is_none_it_calls_get_average_sampling_interval_without_profile(self):
203-
self.process_duration_check.is_cpu_usage_limit_reached()
232+
self.process_duration_check.is_sampling_cpu_usage_limit_reached()
204233
self.get_average_sampling_interval_mock.assert_called_once_with(None)
205234

206235

207-
class TestWhenTimerDoesNotHaveTheKey(TestCpuUsageCheck):
236+
class TestIsOverallCpuUsageLimitReached():
237+
@before
238+
def before(self):
239+
self.timer = Timer()
240+
self.profile = Mock(spec=Profile)
241+
for i in range(20):
242+
self.timer.record('runProfiler', 0.5)
243+
set_agent_config(cpu_limit_percentage=9)
244+
self.process_duration_check = CpuUsageCheck(self.timer)
245+
self.profile.get_active_millis_since_start = Mock(return_value=100*1000)
246+
247+
def test_when_average_duration_exceeds_limit_it_returns_true(self):
248+
# timer: (0.5*20/100) * 100= 10%
249+
assert self.process_duration_check.is_overall_cpu_usage_limit_reached(self.profile)
250+
251+
def test_when_average_duragtion_is_below_limit_it_returns_false(self):
252+
# timer: (0.5*20/100) * 100= 10%
253+
set_agent_config(cpu_limit_percentage=11)
254+
assert not self.process_duration_check.is_overall_cpu_usage_limit_reached(self.profile)
255+
256+
def test_when_profile_is_none_it_returns_false(self):
257+
assert not self.process_duration_check.is_overall_cpu_usage_limit_reached()
258+
259+
260+
class TestWhenTimerDoesNotHaveTheKey(TestSamplingCpuUsageCheck):
208261
@before
209262
def before(self):
210263
super().before()
211264

212265
def test_it_returns_false(self):
213266
self.process_duration_check.timer = Timer()
214-
assert not self.process_duration_check.is_cpu_usage_limit_reached()
267+
assert not self.process_duration_check.is_sampling_cpu_usage_limit_reached()
215268

216269

217-
class TestWhenTimerDoesNotHaveEnoughMeasures(TestCpuUsageCheck):
270+
class TestWhenTimerDoesNotHaveEnoughMeasures(TestSamplingCpuUsageCheck):
218271
@before
219272
def before(self):
220273
super().before()
221-
222-
def test_it_returns_false(self):
223274
self.timer.reset()
224275
for i in range(4):
225276
self.timer.record('sampleAndAggregate', 0.5)
226-
assert not self.process_duration_check.is_cpu_usage_limit_reached()
277+
278+
def test_sampling_cpu_usage_limit_reached_returns_false(self):
279+
assert not self.process_duration_check.is_sampling_cpu_usage_limit_reached()
280+
281+
def test_overall_cpu_usage_limit_reached_returns_false(self):
282+
assert not self.process_duration_check.is_overall_cpu_usage_limit_reached()
227283

228284

229285
class TestMemoryLimitCheck:

test/unit/test_profiler_runner.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def before(self):
1717
self.mock_collector = MagicMock(name="collector", spec=LocalAggregator)
1818
self.mock_disabler = MagicMock(name="profile", spec=ProfilerDisabler)
1919
self.mock_disabler.should_stop_profiling.return_value = False
20+
self.mock_disabler.should_stop_sampling.return_value = False
2021
self.mock_sampler = MagicMock(name="sampler", spec=Sampler)
2122

2223
self.environment = {
@@ -80,11 +81,21 @@ def test_when_it_reports_it_does_not_sample(self):
8081
self.mock_collector.flush.assert_called_once()
8182
self.mock_collector.add.assert_not_called()
8283

83-
def test_when_disabler_say_to_stop(self):
84+
def test_when_disabler_says_to_stop_profiling_it_does_not_start(self):
8485
self.mock_disabler.should_stop_profiling.return_value = True
86+
87+
assert not self.profiler_runner.start()
88+
89+
self.mock_collector.refresh_configuration.assert_not_called()
90+
self.mock_collector.add.assert_not_called()
91+
92+
def test_when_disabler_says_to_stop_sampling_it_does_not_do_anything(self):
93+
self.mock_disabler.should_stop_sampling.return_value = True
8594
self.profiler_runner._profiling_command()
8695

96+
# As disabler.stop_sampling() returns True, _profiling_command() should not attempt to carry out any action.
8797
self.mock_collector.refresh_configuration.assert_not_called()
98+
self.mock_sampler.sample.assert_not_called()
8899
self.mock_collector.add.assert_not_called()
89100

90101
def test_when_orchestrator_says_no_to_profiler(self):

0 commit comments

Comments
 (0)