|
4 | 4 | # SPDX-License-Identifier: BSD-3-Clause |
5 | 5 |
|
6 | 6 | import contextlib |
7 | | -import math |
| 7 | +import random |
8 | 8 | import sys |
9 | 9 | import time |
10 | 10 |
|
11 | 11 | import reframe.core.runtime as rt |
12 | 12 | import reframe.utility as util |
13 | 13 | import reframe.utility.color as color |
14 | | -from reframe.core.exceptions import (FailureLimitError, |
| 14 | +from reframe.core.exceptions import (ConfigError, |
| 15 | + FailureLimitError, |
15 | 16 | RunSessionTimeout, |
16 | 17 | TaskExit) |
17 | 18 | from reframe.core.logging import getlogger, level_from_str |
@@ -69,33 +70,83 @@ def _print_pipeline_timings(task): |
69 | 70 |
|
70 | 71 |
|
71 | 72 | class _PollController: |
72 | | - SLEEP_MIN = 0.1 |
73 | | - SLEEP_MAX = 10 |
74 | | - SLEEP_INC_RATE = 1.1 |
| 73 | + def _validate_poll_params(self): |
| 74 | + def _check_positive(x, name): |
| 75 | + if x <= 0: |
| 76 | + raise ConfigError(f'{name} must be a positive number') |
| 77 | + |
| 78 | + _check_positive(self._poll_rate_min, 'minimum poll rate') |
| 79 | + _check_positive(self._poll_rate_max, 'maximum poll rate') |
| 80 | + if self._poll_rate_max < self._poll_rate_min: |
| 81 | + raise ConfigError('maximum poll rate must be greater or equal to ' |
| 82 | + 'minimum poll rate') |
| 83 | + |
| 84 | + if self._poll_rate_decay < 0 or self._poll_rate_decay > 1: |
| 85 | + raise ConfigError('poll rate decay must be in range [0,1]') |
| 86 | + |
| 87 | + if self._poll_randomize_range_ms is not None: |
| 88 | + left, right = self._poll_randomize_range_ms |
| 89 | + if left > 0: |
| 90 | + raise ConfigError('left boundary of poll randomization range ' |
| 91 | + 'must be a negative integer or zero') |
| 92 | + |
| 93 | + if right < 0: |
| 94 | + raise ConfigError('right boundary of poll randomization range ' |
| 95 | + 'must be a positive integer or zero') |
75 | 96 |
|
76 | 97 | def __init__(self): |
77 | | - self._num_polls = 0 |
78 | | - self._sleep_duration = None |
79 | | - self._t_init = None |
80 | | - |
81 | | - def reset_snooze_time(self): |
82 | | - self._sleep_duration = self.SLEEP_MIN |
| 98 | + get_option = rt.runtime().get_option |
| 99 | + self._poll_rate_max = get_option('general/0/poll_rate_max') |
| 100 | + self._poll_rate_min = get_option('general/0/poll_rate_min') |
| 101 | + self._poll_rate_decay = get_option('general/0/poll_rate_decay') |
| 102 | + self._poll_randomize_range_ms = get_option( |
| 103 | + 'general/0/poll_randomize_ms' |
| 104 | + ) |
| 105 | + self._poll_count_total = 0 |
| 106 | + self._poll_count_interval = 0 |
| 107 | + self._validate_poll_params() |
| 108 | + self._t_start = None |
| 109 | + self._t_last_poll = None |
| 110 | + self._desired_poll_rate = self._poll_rate_max |
| 111 | + |
| 112 | + def reset_poll_rate(self): |
| 113 | + getlogger().debug2('[P] reset poll rate') |
| 114 | + self._poll_count_interval = 0 |
| 115 | + self._desired_poll_rate = self._poll_rate_max |
| 116 | + |
| 117 | + def _poll_rate(self): |
| 118 | + now = time.time() |
| 119 | + return (self._poll_count_total / (now - self._t_start), |
| 120 | + self._poll_count_interval / (now - self._t_last_poll)) |
83 | 121 |
|
84 | 122 | def snooze(self): |
85 | | - if self._num_polls == 0: |
86 | | - self._t_init = time.time() |
87 | | - |
88 | | - t_elapsed = time.time() - self._t_init |
89 | | - self._num_polls += 1 |
90 | | - poll_rate = self._num_polls / t_elapsed if t_elapsed else math.inf |
91 | | - getlogger().debug2( |
92 | | - f'Poll rate control: sleeping for {self._sleep_duration}s ' |
93 | | - f'(current poll rate: {poll_rate} polls/s)' |
94 | | - ) |
95 | | - time.sleep(self._sleep_duration) |
96 | | - self._sleep_duration = min( |
97 | | - self._sleep_duration*self.SLEEP_INC_RATE, self.SLEEP_MAX |
| 123 | + if self._poll_count_total == 0: |
| 124 | + self._t_start = time.time() |
| 125 | + self._t_last_poll = self._t_start |
| 126 | + dt_sleep = 1. / self._desired_poll_rate |
| 127 | + else: |
| 128 | + dt_next_interval = time.time() - self._t_last_poll |
| 129 | + dt_sleep = (self._poll_count_interval + 1) / self._desired_poll_rate - dt_next_interval # noqa: E501 |
| 130 | + |
| 131 | + if self._poll_randomize_range_ms: |
| 132 | + sleep_eps = random.randrange(*self._poll_randomize_range_ms) |
| 133 | + dt_sleep += sleep_eps / 1000 |
| 134 | + |
| 135 | + # Make sure sleep time positive |
| 136 | + dt_sleep = max(0, dt_sleep) |
| 137 | + time.sleep(dt_sleep) |
| 138 | + |
| 139 | + self._poll_count_total += 1 |
| 140 | + self._poll_count_interval += 1 |
| 141 | + poll_rate_global, poll_rate_curr = self._poll_rate() |
| 142 | + getlogger().debug2(f'[P] sleep_time={dt_sleep:.6f}, ' |
| 143 | + f'pr_desired={self._desired_poll_rate:.6f}, ' |
| 144 | + f'pr_current={poll_rate_curr:.6f}, ' |
| 145 | + f'pr_global={poll_rate_global:.6f}') |
| 146 | + self._desired_poll_rate = max( |
| 147 | + self._desired_poll_rate*self._poll_rate_decay, self._poll_rate_min |
98 | 148 | ) |
| 149 | + self._t_last_poll = time.time() |
99 | 150 |
|
100 | 151 |
|
101 | 152 | class _PolicyEventListener(TaskEventListener): |
@@ -227,7 +278,7 @@ def runcase(self, case): |
227 | 278 | else: |
228 | 279 | sched = partition.scheduler |
229 | 280 |
|
230 | | - self._pollctl.reset_snooze_time() |
| 281 | + self._pollctl.reset_poll_rate() |
231 | 282 | while True: |
232 | 283 | if not self.dry_run_mode: |
233 | 284 | sched.poll(task.check.job) |
@@ -366,7 +417,7 @@ def exit(self): |
366 | 417 | if self._pipeline_statistics: |
367 | 418 | self._init_pipeline_progress(len(self._current_tasks)) |
368 | 419 |
|
369 | | - self._pollctl.reset_snooze_time() |
| 420 | + self._pollctl.reset_poll_rate() |
370 | 421 | while self._current_tasks: |
371 | 422 | try: |
372 | 423 | self._poll_tasks() |
@@ -607,7 +658,7 @@ def _abortall(self, cause): |
607 | 658 | task.abort(cause) |
608 | 659 |
|
609 | 660 | def on_task_exit(self, task): |
610 | | - self._pollctl.reset_snooze_time() |
| 661 | + self._pollctl.reset_poll_rate() |
611 | 662 |
|
612 | 663 | def on_task_compile_exit(self, task): |
613 | | - self._pollctl.reset_snooze_time() |
| 664 | + self._pollctl.reset_poll_rate() |
0 commit comments