Skip to content

Commit d429081

Browse files
authored
Merge pull request #49 from MoseleyBioinformaticsLab/ram
Measures combined RAM and threads by summing the measurements of the main and descendant processes that were measured beforehand rather than re-measuring
2 parents fb137b0 + 311bb82 commit d429081

File tree

2 files changed

+32
-32
lines changed

2 files changed

+32
-32
lines changed

src/gpu_tracker/tracker.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ def run(self):
107107
"""
108108
start_time = time.time()
109109
self._extraneous_process_ids.add(self.pid)
110+
get_memory_maps = lambda process: process.memory_maps(grouped=False)
111+
get_rss = lambda process: process.memory_info().rss
112+
get_n_threads = lambda process: process.num_threads()
110113
get_cpu_percent = lambda process: process.cpu_percent()
111114
try:
112115
main_process = psutil.Process(self._main_process_id)
@@ -124,15 +127,19 @@ def run(self):
124127
try:
125128
descendent_processes = [
126129
process for process in main_process.children(recursive=True) if process.pid not in self._extraneous_process_ids]
127-
combined_processes = [main_process] + descendent_processes
128130
# The first call to cpu_percent returns a meaningless value of 0.0 and should be ignored.
129131
# And it's recommended to wait a specified amount of time after the first call to cpu_percent.
130132
# See https://psutil.readthedocs.io/en/latest/#psutil.Process.cpu_percent
131-
self._map_processes(processes=combined_processes, map_func=get_cpu_percent)
133+
self._map_processes(processes=[main_process] + descendent_processes, map_func=get_cpu_percent)
132134
# Get the maximum RAM usage.
133-
self._update_ram(rss_values=self._resource_usage.max_ram.main, processes=[main_process])
134-
self._update_ram(rss_values=self._resource_usage.max_ram.descendents, processes=descendent_processes)
135-
self._update_ram(rss_values=self._resource_usage.max_ram.combined, processes=combined_processes)
135+
ram_map_func = get_memory_maps if self._is_linux else get_rss
136+
main_ram = self._map_processes([main_process], map_func=ram_map_func)
137+
descendents_ram = self._map_processes(descendent_processes, map_func=ram_map_func)
138+
combined_ram = main_ram + descendents_ram
139+
kwarg = 'memory_maps_list' if self._is_linux else 'rss_list'
140+
self._update_ram(rss_values=self._resource_usage.max_ram.main, **{kwarg: main_ram})
141+
self._update_ram(rss_values=self._resource_usage.max_ram.descendents, **{kwarg: descendents_ram})
142+
self._update_ram(rss_values=self._resource_usage.max_ram.combined, **{kwarg: combined_ram})
136143
self._resource_usage.max_ram.system = max(
137144
self._resource_usage.max_ram.system, psutil.virtual_memory().used * self._ram_coefficient)
138145
# Get the maximum GPU RAM usage if available.
@@ -156,26 +163,28 @@ def run(self):
156163
n_hardware_units=self._resource_usage.gpu_utilization.n_expected_gpus)
157164

158165
# Get the mean and maximum CPU usages.
159-
self._update_n_threads(processes=[main_process], attr='main')
160-
self._update_n_threads(processes=descendent_processes, attr='descendents')
161-
self._update_n_threads(processes=combined_processes, attr='combined')
166+
main_n_threads = self._map_processes([main_process], map_func=get_n_threads)
167+
descendent_n_threads = self._map_processes(descendent_processes, map_func=get_n_threads)
168+
self._update_n_threads(n_threads_list=main_n_threads, attr='main')
169+
self._update_n_threads(n_threads_list=descendent_n_threads, attr='descendents')
170+
self._update_n_threads(n_threads_list=main_n_threads + descendent_n_threads, attr='combined')
162171
# noinspection PyTypeChecker
163172
system_core_percentages: list[float] = psutil.cpu_percent(percpu=True)
164173
cpu_utilization = self._resource_usage.cpu_utilization
165174
self._update_processing_unit_utilization(
166175
current_percentages=system_core_percentages, processing_unit_percentages=cpu_utilization.system,
167176
percent_key='cpu_system', n_hardware_units=cpu_utilization.system_core_count)
168177
time.sleep(_TrackingProcess._CPU_PERCENT_INTERVAL)
169-
main_percentage = main_process.cpu_percent()
178+
main_percentage = self._map_processes([main_process], map_func=get_cpu_percent)
170179
descendent_percentages = self._map_processes(processes=descendent_processes, map_func=get_cpu_percent)
171180
self._update_processing_unit_utilization(
172-
current_percentages=[main_percentage], processing_unit_percentages=cpu_utilization.main, percent_key='cpu_main',
181+
current_percentages=main_percentage, processing_unit_percentages=cpu_utilization.main, percent_key='cpu_main',
173182
n_hardware_units=cpu_utilization.n_expected_cores)
174183
self._update_processing_unit_utilization(
175184
current_percentages=descendent_percentages, processing_unit_percentages=cpu_utilization.descendents,
176185
percent_key='cpu_descendents', n_hardware_units=cpu_utilization.n_expected_cores)
177186
self._update_processing_unit_utilization(
178-
current_percentages=[main_percentage] + descendent_percentages, processing_unit_percentages=cpu_utilization.combined,
187+
current_percentages=main_percentage + descendent_percentages, processing_unit_percentages=cpu_utilization.combined,
179188
percent_key='cpu_combined', n_hardware_units=cpu_utilization.n_expected_cores)
180189
# Update compute time.
181190
self._resource_usage.compute_time.time = (time.time() - start_time) * self._time_coefficient
@@ -197,10 +206,8 @@ def _map_processes(self, processes: list[psutil.Process], map_func: typ.Callable
197206
self._log_warning('Attempted to obtain usage information of a process that no longer exists.') # pragma: nocover
198207
return mapped_list
199208

200-
def _update_ram(self, rss_values: RSSValues, processes: list[psutil.Process]):
201-
if self._is_linux:
202-
memory_maps_list: list[list] = self._map_processes(
203-
processes, map_func=lambda process: process.memory_maps(grouped=False))
209+
def _update_ram(self, rss_values: RSSValues, memory_maps_list: list[list] | None = None, rss_list: list[int] | None = None):
210+
if memory_maps_list is not None:
204211
private_rss = 0
205212
path_to_shared_rss = dict[str, float]()
206213
for memory_maps in memory_maps_list:
@@ -218,7 +225,7 @@ def _update_ram(self, rss_values: RSSValues, processes: list[psutil.Process]):
218225
rss_values.shared_rss = max(rss_values.shared_rss, shared_rss)
219226
total_rss = private_rss + shared_rss
220227
else:
221-
total_rss = sum(self._map_processes(processes, map_func=lambda process: process.memory_info().rss))
228+
total_rss = sum(rss_list)
222229
total_rss *= self._ram_coefficient
223230
rss_values.total_rss = max(rss_values.total_rss, total_rss)
224231

@@ -254,8 +261,7 @@ def _update_processing_unit_utilization(
254261
max_percent: float = getattr(processing_unit_percentages, f'max_{percent_type}_percent')
255262
setattr(processing_unit_percentages, f'max_{percent_type}_percent', max(max_percent, percent))
256263

257-
def _update_n_threads(self, processes: list[psutil.Process], attr: str):
258-
n_threads_list = self._map_processes(processes, map_func=lambda process: process.num_threads())
264+
def _update_n_threads(self, n_threads_list: list[int], attr: str):
259265
n_threads = sum(n_threads_list)
260266
attr = f'{attr}_n_threads'
261267
max_n_threads = getattr(self._resource_usage.cpu_utilization, attr)

tests/test_tracker.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ def get_use_context_manager(request) -> bool:
1919
yield request.param
2020

2121

22-
def multiply_list(_list: list, multiple=2) -> list:
23-
return [item for item in _list for _ in range(multiple)]
24-
25-
2622
test_tracker_data = [
2723
('bytes', 'megabytes', 'seconds', None, 3),
2824
('kilobytes', 'gigabytes', 'minutes', {'gpu-id1'}, 2),
@@ -83,10 +79,8 @@ def get_process_mock(
8379
private_dirty=private_dirty, private_clean=private_clean, shared_dirty=shared_dirty, shared_clean=shared_clean,
8480
path=path)
8581
memory_map_mocks.append(memory_map_mock)
86-
memory_maps_side_effect.extend([memory_map_mocks, memory_map_mocks])
87-
rams = multiply_list(rams)
88-
cpu_percentages = multiply_list(cpu_percentages)
89-
num_threads = multiply_list(num_threads)
82+
memory_maps_side_effect.append(memory_map_mocks)
83+
cpu_percentages = [percent for percent in cpu_percentages for percent in [0.0, percent]]
9084
return mocker.MagicMock(
9185
pid=pid,
9286
memory_info=mocker.MagicMock(side_effect=[mocker.MagicMock(rss=ram) for ram in rams]),
@@ -172,13 +166,13 @@ def start_mock(self):
172166
utils.assert_args_list(current_process_mock.children, [()] * 2)
173167
utils.assert_args_list(mock=main_process_mock.children, expected_args_list=[{'recursive': True}] * 3, use_kwargs=True)
174168
if operating_system == 'Linux':
175-
utils.assert_args_list(mock=main_process_mock.memory_maps, expected_args_list=[{'grouped': False}] * 6, use_kwargs=True)
176-
utils.assert_args_list(mock=child1_mock.memory_maps, expected_args_list=[{'grouped': False}] * 6, use_kwargs=True)
177-
utils.assert_args_list(mock=child2_mock.memory_maps, expected_args_list=[{'grouped': False}] * 6, use_kwargs=True)
169+
utils.assert_args_list(mock=main_process_mock.memory_maps, expected_args_list=[{'grouped': False}] * 3, use_kwargs=True)
170+
utils.assert_args_list(mock=child1_mock.memory_maps, expected_args_list=[{'grouped': False}] * 3, use_kwargs=True)
171+
utils.assert_args_list(mock=child2_mock.memory_maps, expected_args_list=[{'grouped': False}] * 3, use_kwargs=True)
178172
else:
179-
utils.assert_args_list(mock=main_process_mock.memory_info, expected_args_list=[()] * 6)
180-
utils.assert_args_list(mock=child1_mock.memory_info, expected_args_list=[()] * 6)
181-
utils.assert_args_list(mock=child2_mock.memory_info, expected_args_list=[()] * 6)
173+
utils.assert_args_list(mock=main_process_mock.memory_info, expected_args_list=[()] * 3)
174+
utils.assert_args_list(mock=child1_mock.memory_info, expected_args_list=[()] * 3)
175+
utils.assert_args_list(mock=child2_mock.memory_info, expected_args_list=[()] * 3)
182176
assert len(check_output_mock.call_args_list) == 8
183177
os_mock.getpid.assert_called_once_with()
184178
utils.assert_args_list(mock=time_mock.time, expected_args_list=[()] * 5)

0 commit comments

Comments
 (0)