desna-interval 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. desna_interval-0.0.1/CHANGELOG.md +29 -0
  2. desna_interval-0.0.1/CITATION.cff +35 -0
  3. desna_interval-0.0.1/LICENSE +21 -0
  4. desna_interval-0.0.1/PKG-INFO +175 -0
  5. desna_interval-0.0.1/README.md +139 -0
  6. desna_interval-0.0.1/README.ru.md +138 -0
  7. desna_interval-0.0.1/docs/en/api.md +69 -0
  8. desna_interval-0.0.1/docs/en/cli.md +49 -0
  9. desna_interval-0.0.1/docs/en/publishing.md +19 -0
  10. desna_interval-0.0.1/docs/en/quickstart.md +72 -0
  11. desna_interval-0.0.1/docs/en/theory.md +104 -0
  12. desna_interval-0.0.1/docs/ru/api.md +68 -0
  13. desna_interval-0.0.1/docs/ru/cli.md +49 -0
  14. desna_interval-0.0.1/docs/ru/publishing.md +19 -0
  15. desna_interval-0.0.1/docs/ru/quickstart.md +72 -0
  16. desna_interval-0.0.1/docs/ru/theory.md +101 -0
  17. desna_interval-0.0.1/examples/01_quickstart.py +24 -0
  18. desna_interval-0.0.1/examples/02_expected_cost.py +35 -0
  19. desna_interval-0.0.1/examples/03_replay_trace.py +42 -0
  20. desna_interval-0.0.1/examples/04_kalman_adapter.py +24 -0
  21. desna_interval-0.0.1/experiments/README.md +402 -0
  22. desna_interval-0.0.1/experiments/README.ru.md +395 -0
  23. desna_interval-0.0.1/experiments/_sim/__init__.py +19 -0
  24. desna_interval-0.0.1/experiments/_sim/bootstrap.py +50 -0
  25. desna_interval-0.0.1/experiments/_sim/cases.py +80 -0
  26. desna_interval-0.0.1/experiments/_sim/config.py +195 -0
  27. desna_interval-0.0.1/experiments/_sim/families.py +168 -0
  28. desna_interval-0.0.1/experiments/_sim/live.py +207 -0
  29. desna_interval-0.0.1/experiments/_sim/metrics.py +117 -0
  30. desna_interval-0.0.1/experiments/_sim/plotstyle.py +94 -0
  31. desna_interval-0.0.1/experiments/_sim/replay.py +230 -0
  32. desna_interval-0.0.1/experiments/_sim/strategies.py +143 -0
  33. desna_interval-0.0.1/experiments/_sim/synthetic.py +158 -0
  34. desna_interval-0.0.1/experiments/_sim/traces.py +81 -0
  35. desna_interval-0.0.1/experiments/build_traces.py +108 -0
  36. desna_interval-0.0.1/experiments/config.toml +197 -0
  37. desna_interval-0.0.1/experiments/day_report.py +709 -0
  38. desna_interval-0.0.1/experiments/day_run.py +857 -0
  39. desna_interval-0.0.1/experiments/day_schedule.py +313 -0
  40. desna_interval-0.0.1/experiments/e1_theory.py +200 -0
  41. desna_interval-0.0.1/experiments/e2_compare.py +134 -0
  42. desna_interval-0.0.1/experiments/e3_lag.py +131 -0
  43. desna_interval-0.0.1/experiments/e4_live.py +355 -0
  44. desna_interval-0.0.1/experiments/e5_sensitivity.py +119 -0
  45. desna_interval-0.0.1/experiments/guest_access.py +238 -0
  46. desna_interval-0.0.1/experiments/run_all.ps1 +29 -0
  47. desna_interval-0.0.1/experiments/run_all.sh +25 -0
  48. desna_interval-0.0.1/experiments/run_day.ps1 +32 -0
  49. desna_interval-0.0.1/experiments/run_day.sh +41 -0
  50. desna_interval-0.0.1/experiments/testbed/README.txt +4 -0
  51. desna_interval-0.0.1/experiments/testbed/burst_load.py +184 -0
  52. desna_interval-0.0.1/experiments/testbed/guest_recorder.py +191 -0
  53. desna_interval-0.0.1/experiments/testbed/make_vms.sh +118 -0
  54. desna_interval-0.0.1/pyproject.toml +252 -0
  55. desna_interval-0.0.1/pyproject.toml.orig +160 -0
  56. desna_interval-0.0.1/scripts/check_commit_msg.py +78 -0
  57. desna_interval-0.0.1/scripts/commit-msg +4 -0
  58. desna_interval-0.0.1/scripts/install_hooks.sh +7 -0
  59. desna_interval-0.0.1/src/desna_interval/__init__.py +77 -0
  60. desna_interval-0.0.1/src/desna_interval/cli.py +96 -0
  61. desna_interval-0.0.1/src/desna_interval/estimators.py +270 -0
  62. desna_interval-0.0.1/src/desna_interval/libvirt_loop.py +202 -0
  63. desna_interval-0.0.1/src/desna_interval/py.typed +0 -0
  64. desna_interval-0.0.1/src/desna_interval/replay.py +192 -0
  65. desna_interval-0.0.1/src/desna_interval/rule.py +166 -0
  66. desna_interval-0.0.1/src/desna_interval/scheduler.py +110 -0
  67. desna_interval-0.0.1/tests/data/synthetic_reference.json +258 -0
  68. desna_interval-0.0.1/tests/test_core.py +279 -0
  69. desna_interval-0.0.1/tests/test_day_schedule.py +230 -0
  70. desna_interval-0.0.1/tests/test_docs.py +41 -0
  71. desna_interval-0.0.1/tests/test_examples.py +22 -0
  72. desna_interval-0.0.1/tests/test_experiments.py +204 -0
  73. desna_interval-0.0.1/tests/test_guest_access.py +302 -0
  74. desna_interval-0.0.1/tests/test_replay_cli.py +123 -0
  75. desna_interval-0.0.1/tests/test_sim_reference.py +45 -0
  76. desna_interval-0.0.1/tests/test_two_days.py +396 -0
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ All notable changes of desna-interval are documented here. The project follows
4
+ [Semantic Versioning](https://semver.org). Russian summary: the same entries
5
+ apply; the documentation is in `docs/ru/`.
6
+
7
+ ## 0.0.1 — 2026-11-11
8
+
9
+ First release: the monitoring-interval rule of article 1 (A. Ferubko, O. Kazakov, BGITU).
10
+ Renamed from `deficit-interval` (module `deficit_interval`) before publication.
11
+
12
+ * Rule `delta* = s/v + sqrt(2 eps/v)` with the age of the snapshot
13
+ (`interval_unclipped`, `next_interval`, `next_interval_aged`, `min_slack`).
14
+ * Expected-cost optimum `x^3/3 + s x^2/2 = c v^2 / rho` (`expected_cost_rate`,
15
+ `optimal_interval_expected`), claim 1.5.
16
+ * Bounds on the growth rate `v`: `history_quantile`, `pooled_quantile`,
17
+ `WindowQuantile`, `FixedRate`, the scalar Kalman filter `KalmanRate` and the
18
+ adapter `RateFilterAdapter` for any filter with `rate()`; `burst_rate`.
19
+ * Per-object or common schedule (`MonitorScheduler`) and the protocol
20
+ `IntervalScheduler` for other schedules.
21
+ * Offline replay of policies on a trace (`replay`, `FixedPolicy`, `BackoffPolicy`,
22
+ `DeficitRulePolicy`, `ExpectedCostPolicy`) and the command line `desna-interval`.
23
+ * Live loop for libvirt guests (`libvirt_loop.run`, with an optional `scheduler`)
24
+ with snapshot age and an optional adaptation of the statistics period; it imports
25
+ without libvirt.
26
+ * Experiments E1–E5 of article 1 in `experiments/` (optional group `experiments`):
27
+ scripts with `--smoke`, one configuration file, outputs as CSV and PDF/PNG,
28
+ a synthetic generator bit-identical with the prototype, held-out scenario D.
29
+ * Documentation in English and Russian, four examples, tests.
@@ -0,0 +1,35 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ type: software
4
+ title: "desna-interval: monitoring interval from a tolerance on accumulated deficit"
5
+ abstract: >-
6
+ A Python library that chooses the polling interval of a virtual machine's
7
+ memory from a tolerance on the accumulated deficit: the rule
8
+ delta* = s/v + sqrt(2 eps/v), the correction for the age of a late snapshot,
9
+ the expected-cost optimum, the Jensen bound on the benefit of adaptation,
10
+ estimators of the growth rate (including a Kalman adapter), and a live loop
11
+ for KVM guests through libvirt.
12
+ version: 0.0.1
13
+ date-released: 2026-11-11
14
+ license: MIT
15
+ repository-code: "https://github.com/89605502155/desna-interval"
16
+ url: "https://github.com/89605502155/desna-interval"
17
+ keywords:
18
+ - memory ballooning
19
+ - monitoring interval
20
+ - accumulated deficit
21
+ - virtual machines
22
+ - KVM
23
+ - libvirt
24
+ - virtio-balloon
25
+ authors:
26
+ - family-names: Ferubko
27
+ given-names: Andrey
28
+ email: ferubko1999@yandex.ru
29
+ orcid: "https://orcid.org/0009-0006-5625-137X"
30
+ affiliation: "Bryansk State Engineering-Technological University (BGITU)"
31
+ - family-names: Kazakov
32
+ given-names: Oleg
33
+ email: kazakov@bgitu.ru
34
+ orcid: "https://orcid.org/0000-0001-9665-8138"
35
+ affiliation: "Bryansk State Engineering-Technological University (BGITU)"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrey Ferubko, Oleg Kazakov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: desna-interval
3
+ Version: 0.0.1
4
+ Summary: Monitoring interval from a tolerance on accumulated deficit: the rule delta* = s/v + sqrt(2 eps/v), aged snapshots, expected-cost optimum and a libvirt loop for KVM memory ballooning
5
+ Keywords: kvm,libvirt,virtio-balloon,memory-ballooning,monitoring-interval,adaptive-sampling,accumulated-deficit,virtual-machines,polling
6
+ Author: Andrey Ferubko, Oleg Kazakov
7
+ Author-email: Andrey Ferubko <ferubko1999@yandex.ru>, Oleg Kazakov <kazakov@bgitu.ru>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
23
+ Classifier: Topic :: System :: Monitoring
24
+ Classifier: Topic :: System :: Operating System Kernels :: Linux
25
+ Classifier: Typing :: Typed
26
+ Requires-Dist: numpy>=1.26
27
+ Requires-Dist: libvirt-python>=8.0 ; extra == 'libvirt'
28
+ Requires-Python: >=3.11
29
+ Project-URL: Homepage, https://github.com/89605502155/desna-interval
30
+ Project-URL: Documentation, https://github.com/89605502155/desna-interval/tree/main/docs
31
+ Project-URL: Repository, https://github.com/89605502155/desna-interval
32
+ Project-URL: Issues, https://github.com/89605502155/desna-interval/issues
33
+ Project-URL: Changelog, https://github.com/89605502155/desna-interval/blob/main/CHANGELOG.md
34
+ Provides-Extra: libvirt
35
+ Description-Content-Type: text/markdown
36
+
37
+ # desna-interval
38
+
39
+ [![PyPI](https://img.shields.io/pypi/v/desna-interval.svg)](https://pypi.org/project/desna-interval/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/desna-interval.svg)](https://pypi.org/project/desna-interval/)
41
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/89605502155/desna-interval/blob/main/LICENSE)
42
+
43
+ **How often to poll a resource so that the deficit accumulated between two polls stays below a tolerance.**
44
+
45
+ desna-interval chooses the monitoring interval of a virtual machine from a
46
+ tolerance on its accumulated memory deficit. Built for memory ballooning of KVM
47
+ guests through libvirt; the rule is generic for any quantity whose growth rate
48
+ is bounded.
49
+
50
+ If demand grows no faster than `v` and the slack after a decision is `s`, the
51
+ deficit over a blind gap of length `Δ` is at most `(vΔ − s)⁺² / (2v)`. The
52
+ largest interval that keeps this below the tolerance `ε` is
53
+
54
+ Δ* = s/v + √(2ε/v)
55
+
56
+ With a snapshot that is `a` seconds old (virtio-balloon statistics arrive late),
57
+ the guarantee holds for `Δ* − a`.
58
+
59
+ Русская версия: [README.ru.md](https://github.com/89605502155/desna-interval/blob/main/README.ru.md).
60
+ Documentation: [English](https://github.com/89605502155/desna-interval/tree/main/docs/en) ·
61
+ [Русский](https://github.com/89605502155/desna-interval/tree/main/docs/ru) ·
62
+ [Changelog](https://github.com/89605502155/desna-interval/blob/main/CHANGELOG.md) ·
63
+ [Issues](https://github.com/89605502155/desna-interval/issues)
64
+
65
+ The library accompanies article 1 of the series by Andrey Ferubko, Bryansk State
66
+ Engineering-Technological University (BGITU). Theory:
67
+ [docs/en/theory.md](https://github.com/89605502155/desna-interval/blob/main/docs/en/theory.md).
68
+
69
+ ## Installation
70
+
71
+ ```sh
72
+ pip install desna-interval # core: numpy only
73
+ pip install "desna-interval[libvirt]" # live loop on a KVM host
74
+ ```
75
+
76
+ Requires Python 3.11 or newer.
77
+
78
+ ## Quick start
79
+
80
+ ```python
81
+ from desna_interval import MonitorScheduler, WindowQuantile, next_interval_aged
82
+
83
+ next_interval_aged(s=256, v=128, eps=128, age=0.0) # -> 3.414 s, fresh snapshot
84
+ next_interval_aged(s=256, v=128, eps=128, age=1.0) # -> 2.414 s, snapshot 1 s old
85
+
86
+ sched = MonitorScheduler(["vm1", "vm2"], eps=128, estimator_factory=WindowQuantile)
87
+ sched.observe(now=0.0, demand={"vm1": 1000, "vm2": 3000}, slack={"vm1": 256, "vm2": 64})
88
+ ```
89
+
90
+ Command line:
91
+
92
+ desna-interval interval --slack 256 --rate 128 --eps 128 --age 1
93
+ desna-interval optimal --slack 0 --rate 128 --rho 0.01 --cost 64
94
+ desna-interval replay trace.csv --eps 64 --slack 256
95
+
96
+ ## How it differs from existing packages
97
+
98
+ Packages on PyPI that poll or sample adaptively, such as `python-adaptive`,
99
+ choose sample points to reduce the error of a function or a metric. Retry
100
+ packages such as `backoff` and `tenacity` implement back-off for failed calls,
101
+ and scheduling packages such as `APScheduler` only run jobs at given times.
102
+ None of them maps a tolerance on a resource deficit to a polling interval.
103
+
104
+ | Feature | desna-interval | generic adaptive sampling / polling packages |
105
+ |---|---|---|
106
+ | Interval from a tolerance on accumulated deficit, `Δ* = s/v + √(2ε/v)` | yes | no, they target metric or function error |
107
+ | Correction for the age of a late snapshot | yes | no |
108
+ | Expected-cost optimum for a known burst rate and poll price | yes | no |
109
+ | Bound on the growth rate from history, pooled over similar guests, or from a Kalman filter | yes | no |
110
+ | Live loop for libvirt guests (`memoryStats`, balloon targets) | yes | no |
111
+ | Replay of policies on a trace with the deficit as the metric | yes | no |
112
+
113
+ The rule is only as good as the bound `v`: see the theory document for when
114
+ it holds and when it does not.
115
+
116
+ ## When it helps and when it does not
117
+
118
+ The rule bounds the deficit of each blind gap. The gain over a constant interval
119
+ depends on the workload:
120
+
121
+ - For identical machines with memoryless bursts no adaptive schedule beats a
122
+ constant interval with the same poll budget (claim 1.4, Jensen). The library does not
123
+ pretend otherwise.
124
+ - The gain comes from differences between machines, and the rule states a bound on
125
+ the deficit of each gap, which generic back-off does not.
126
+ - The guarantee is only as good as the bound on `v`. Use a long-history quantile
127
+ or a pooled bound for similar machines.
128
+
129
+ The comparison of the strategies (synthetic traces now, testbed traces for the
130
+ article) is the experiment E2 in [experiments/README.md](experiments/README.md).
131
+ Synthetic results show behaviour only.
132
+
133
+ ## Authors
134
+
135
+ - Andrey Ferubko (Андрей Ферубко), ORCID [0009-0006-5625-137X](https://orcid.org/0009-0006-5625-137X)
136
+ - Oleg Kazakov (Олег Дмитриевич Казаков), ORCID [0000-0001-9665-8138](https://orcid.org/0000-0001-9665-8138)
137
+
138
+ Bryansk State Engineering-Technological University (BGITU).
139
+
140
+ ## Modules
141
+
142
+ | Module | Contents |
143
+ |---|---|
144
+ | `rule` | `blind_deficit_bound`, `interval_unclipped`, `next_interval`, `next_interval_aged`, `min_slack`, `expected_cost_rate`, `optimal_interval_expected` |
145
+ | `estimators` | bounds on `v`: `history_quantile`, `pooled_quantile`, `WindowQuantile`, `FixedRate`, `KalmanRate`, `RateFilterAdapter`; `burst_rate` |
146
+ | `scheduler` | `MonitorScheduler`, one interval per object or a common one; the `IntervalScheduler` protocol for other schedules |
147
+ | `replay` | `replay` and the policies `FixedPolicy`, `BackoffPolicy`, `DeficitRulePolicy`, `ExpectedCostPolicy` |
148
+ | `libvirt_loop` | `run` (optional `scheduler`), `read_state`, `reserve_controller`: the live loop |
149
+
150
+ ## Development
151
+
152
+ ```sh
153
+ uv sync
154
+ uv run ruff check . && uv run ruff format --check .
155
+ uv run mypy
156
+ uv run pytest
157
+ bash scripts/install_hooks.sh # once per clone: commit message limits
158
+ ```
159
+
160
+ The experiments of article 1 need the optional group `experiments` (matplotlib):
161
+ `uv sync --group experiments`. Their smoke modes run in `pytest`; the full runs are
162
+ described in [experiments/README.md](experiments/README.md).
163
+
164
+ Commit messages in this repository have a subject of at most 8 characters and
165
+ no body. The `commit-msg` hook installed by `scripts/install_hooks.sh` enforces it
166
+ (`scripts/check_commit_msg.py`). There is no CI and no automatic publishing.
167
+
168
+ ## Citation
169
+
170
+ See [CITATION.cff](https://github.com/89605502155/desna-interval/blob/main/CITATION.cff)
171
+ or the "Cite this repository" button on GitHub.
172
+
173
+ ## License
174
+
175
+ MIT. Copyright (c) 2026 Andrey Ferubko, Oleg Kazakov. See [LICENSE](https://github.com/89605502155/desna-interval/blob/main/LICENSE).
@@ -0,0 +1,139 @@
1
+ # desna-interval
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/desna-interval.svg)](https://pypi.org/project/desna-interval/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/desna-interval.svg)](https://pypi.org/project/desna-interval/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/89605502155/desna-interval/blob/main/LICENSE)
6
+
7
+ **How often to poll a resource so that the deficit accumulated between two polls stays below a tolerance.**
8
+
9
+ desna-interval chooses the monitoring interval of a virtual machine from a
10
+ tolerance on its accumulated memory deficit. Built for memory ballooning of KVM
11
+ guests through libvirt; the rule is generic for any quantity whose growth rate
12
+ is bounded.
13
+
14
+ If demand grows no faster than `v` and the slack after a decision is `s`, the
15
+ deficit over a blind gap of length `Δ` is at most `(vΔ − s)⁺² / (2v)`. The
16
+ largest interval that keeps this below the tolerance `ε` is
17
+
18
+ Δ* = s/v + √(2ε/v)
19
+
20
+ With a snapshot that is `a` seconds old (virtio-balloon statistics arrive late),
21
+ the guarantee holds for `Δ* − a`.
22
+
23
+ Русская версия: [README.ru.md](https://github.com/89605502155/desna-interval/blob/main/README.ru.md).
24
+ Documentation: [English](https://github.com/89605502155/desna-interval/tree/main/docs/en) ·
25
+ [Русский](https://github.com/89605502155/desna-interval/tree/main/docs/ru) ·
26
+ [Changelog](https://github.com/89605502155/desna-interval/blob/main/CHANGELOG.md) ·
27
+ [Issues](https://github.com/89605502155/desna-interval/issues)
28
+
29
+ The library accompanies article 1 of the series by Andrey Ferubko, Bryansk State
30
+ Engineering-Technological University (BGITU). Theory:
31
+ [docs/en/theory.md](https://github.com/89605502155/desna-interval/blob/main/docs/en/theory.md).
32
+
33
+ ## Installation
34
+
35
+ ```sh
36
+ pip install desna-interval # core: numpy only
37
+ pip install "desna-interval[libvirt]" # live loop on a KVM host
38
+ ```
39
+
40
+ Requires Python 3.11 or newer.
41
+
42
+ ## Quick start
43
+
44
+ ```python
45
+ from desna_interval import MonitorScheduler, WindowQuantile, next_interval_aged
46
+
47
+ next_interval_aged(s=256, v=128, eps=128, age=0.0) # -> 3.414 s, fresh snapshot
48
+ next_interval_aged(s=256, v=128, eps=128, age=1.0) # -> 2.414 s, snapshot 1 s old
49
+
50
+ sched = MonitorScheduler(["vm1", "vm2"], eps=128, estimator_factory=WindowQuantile)
51
+ sched.observe(now=0.0, demand={"vm1": 1000, "vm2": 3000}, slack={"vm1": 256, "vm2": 64})
52
+ ```
53
+
54
+ Command line:
55
+
56
+ desna-interval interval --slack 256 --rate 128 --eps 128 --age 1
57
+ desna-interval optimal --slack 0 --rate 128 --rho 0.01 --cost 64
58
+ desna-interval replay trace.csv --eps 64 --slack 256
59
+
60
+ ## How it differs from existing packages
61
+
62
+ Packages on PyPI that poll or sample adaptively, such as `python-adaptive`,
63
+ choose sample points to reduce the error of a function or a metric. Retry
64
+ packages such as `backoff` and `tenacity` implement back-off for failed calls,
65
+ and scheduling packages such as `APScheduler` only run jobs at given times.
66
+ None of them maps a tolerance on a resource deficit to a polling interval.
67
+
68
+ | Feature | desna-interval | generic adaptive sampling / polling packages |
69
+ |---|---|---|
70
+ | Interval from a tolerance on accumulated deficit, `Δ* = s/v + √(2ε/v)` | yes | no, they target metric or function error |
71
+ | Correction for the age of a late snapshot | yes | no |
72
+ | Expected-cost optimum for a known burst rate and poll price | yes | no |
73
+ | Bound on the growth rate from history, pooled over similar guests, or from a Kalman filter | yes | no |
74
+ | Live loop for libvirt guests (`memoryStats`, balloon targets) | yes | no |
75
+ | Replay of policies on a trace with the deficit as the metric | yes | no |
76
+
77
+ The rule is only as good as the bound `v`: see the theory document for when
78
+ it holds and when it does not.
79
+
80
+ ## When it helps and when it does not
81
+
82
+ The rule bounds the deficit of each blind gap. The gain over a constant interval
83
+ depends on the workload:
84
+
85
+ - For identical machines with memoryless bursts no adaptive schedule beats a
86
+ constant interval with the same poll budget (claim 1.4, Jensen). The library does not
87
+ pretend otherwise.
88
+ - The gain comes from differences between machines, and the rule states a bound on
89
+ the deficit of each gap, which generic back-off does not.
90
+ - The guarantee is only as good as the bound on `v`. Use a long-history quantile
91
+ or a pooled bound for similar machines.
92
+
93
+ The comparison of the strategies (synthetic traces now, testbed traces for the
94
+ article) is the experiment E2 in [experiments/README.md](experiments/README.md).
95
+ Synthetic results show behaviour only.
96
+
97
+ ## Authors
98
+
99
+ - Andrey Ferubko (Андрей Ферубко), ORCID [0009-0006-5625-137X](https://orcid.org/0009-0006-5625-137X)
100
+ - Oleg Kazakov (Олег Дмитриевич Казаков), ORCID [0000-0001-9665-8138](https://orcid.org/0000-0001-9665-8138)
101
+
102
+ Bryansk State Engineering-Technological University (BGITU).
103
+
104
+ ## Modules
105
+
106
+ | Module | Contents |
107
+ |---|---|
108
+ | `rule` | `blind_deficit_bound`, `interval_unclipped`, `next_interval`, `next_interval_aged`, `min_slack`, `expected_cost_rate`, `optimal_interval_expected` |
109
+ | `estimators` | bounds on `v`: `history_quantile`, `pooled_quantile`, `WindowQuantile`, `FixedRate`, `KalmanRate`, `RateFilterAdapter`; `burst_rate` |
110
+ | `scheduler` | `MonitorScheduler`, one interval per object or a common one; the `IntervalScheduler` protocol for other schedules |
111
+ | `replay` | `replay` and the policies `FixedPolicy`, `BackoffPolicy`, `DeficitRulePolicy`, `ExpectedCostPolicy` |
112
+ | `libvirt_loop` | `run` (optional `scheduler`), `read_state`, `reserve_controller`: the live loop |
113
+
114
+ ## Development
115
+
116
+ ```sh
117
+ uv sync
118
+ uv run ruff check . && uv run ruff format --check .
119
+ uv run mypy
120
+ uv run pytest
121
+ bash scripts/install_hooks.sh # once per clone: commit message limits
122
+ ```
123
+
124
+ The experiments of article 1 need the optional group `experiments` (matplotlib):
125
+ `uv sync --group experiments`. Their smoke modes run in `pytest`; the full runs are
126
+ described in [experiments/README.md](experiments/README.md).
127
+
128
+ Commit messages in this repository have a subject of at most 8 characters and
129
+ no body. The `commit-msg` hook installed by `scripts/install_hooks.sh` enforces it
130
+ (`scripts/check_commit_msg.py`). There is no CI and no automatic publishing.
131
+
132
+ ## Citation
133
+
134
+ See [CITATION.cff](https://github.com/89605502155/desna-interval/blob/main/CITATION.cff)
135
+ or the "Cite this repository" button on GitHub.
136
+
137
+ ## License
138
+
139
+ MIT. Copyright (c) 2026 Andrey Ferubko, Oleg Kazakov. See [LICENSE](https://github.com/89605502155/desna-interval/blob/main/LICENSE).
@@ -0,0 +1,138 @@
1
+ # desna-interval
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/desna-interval.svg)](https://pypi.org/project/desna-interval/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/desna-interval.svg)](https://pypi.org/project/desna-interval/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/89605502155/desna-interval/blob/main/LICENSE)
6
+
7
+ **Как часто опрашивать ресурс, чтобы накопленный между опросами дефицит оставался ниже допуска.**
8
+
9
+ desna-interval выбирает интервал мониторинга виртуальной машины по допуску на
10
+ накопленный дефицит памяти. Создан для баллонирования памяти ВМ KVM через libvirt;
11
+ правило общее для любой величины с ограниченной скоростью роста.
12
+
13
+ Если потребность растёт не быстрее `v`, а запас после решения равен `s`, то дефицит
14
+ за слепой промежуток длины `Δ` не превышает `(vΔ − s)⁺² / (2v)`. Наибольший интервал,
15
+ при котором дефицит остаётся ниже допуска `ε`, равен
16
+
17
+ Δ* = s/v + √(2ε/v)
18
+
19
+ Если снимок статистики старше на `a` секунд (статистика virtio-balloon приходит с
20
+ опозданием), гарантия сохраняется для интервала `Δ* − a`.
21
+
22
+ English version: [README.md](https://github.com/89605502155/desna-interval/blob/main/README.md).
23
+ Документация: [English](https://github.com/89605502155/desna-interval/tree/main/docs/en) ·
24
+ [Русский](https://github.com/89605502155/desna-interval/tree/main/docs/ru) ·
25
+ [Журнал изменений](https://github.com/89605502155/desna-interval/blob/main/CHANGELOG.md) ·
26
+ [Задачи](https://github.com/89605502155/desna-interval/issues)
27
+
28
+ Библиотека сопровождает статью 1 серии А. Ферубко, Брянский государственный
29
+ инженерно-технологический университет (БГИТУ). Теория:
30
+ [docs/ru/theory.md](https://github.com/89605502155/desna-interval/blob/main/docs/ru/theory.md).
31
+
32
+ ## Установка
33
+
34
+ ```sh
35
+ pip install desna-interval # ядро: только numpy
36
+ pip install "desna-interval[libvirt]" # живой цикл на хосте KVM
37
+ ```
38
+
39
+ Нужен Python 3.11 или новее.
40
+
41
+ ## Быстрый старт
42
+
43
+ ```python
44
+ from desna_interval import MonitorScheduler, WindowQuantile, next_interval_aged
45
+
46
+ next_interval_aged(s=256, v=128, eps=128, age=0.0) # -> 3.414 с, свежий снимок
47
+ next_interval_aged(s=256, v=128, eps=128, age=1.0) # -> 2.414 с, снимок старше на 1 с
48
+
49
+ sched = MonitorScheduler(["vm1", "vm2"], eps=128, estimator_factory=WindowQuantile)
50
+ sched.observe(now=0.0, demand={"vm1": 1000, "vm2": 3000}, slack={"vm1": 256, "vm2": 64})
51
+ ```
52
+
53
+ Командная строка:
54
+
55
+ desna-interval interval --slack 256 --rate 128 --eps 128 --age 1
56
+ desna-interval optimal --slack 0 --rate 128 --rho 0.01 --cost 64
57
+ desna-interval replay trace.csv --eps 64 --slack 256
58
+
59
+ ## Отличия от существующих пакетов
60
+
61
+ Пакеты на PyPI, которые адаптивно опрашивают или выбирают точки выборки, например
62
+ `python-adaptive`, подбирают точки так, чтобы уменьшить ошибку функции или метрики.
63
+ Пакеты повторных попыток (`backoff`, `tenacity`) реализуют откат для неудачных
64
+ вызовов, а пакеты расписаний (`APScheduler`) лишь запускают задачи в заданное время.
65
+ Ни один из них не переводит допуск на дефицит ресурса в интервал опроса.
66
+
67
+ | Возможность | desna-interval | общие пакеты адаптивной выборки и опроса |
68
+ |---|---|---|
69
+ | Интервал из допуска на накопленный дефицит, `Δ* = s/v + √(2ε/v)` | да | нет, они целятся в ошибку метрики или функции |
70
+ | Поправка на возраст запаздывающего снимка | да | нет |
71
+ | Оптимум ожидаемых затрат при известной интенсивности всплесков и цене опроса | да | нет |
72
+ | Граница скорости роста по истории, по группе однотипных ВМ или фильтром Калмана | да | нет |
73
+ | Живой цикл для ВМ libvirt (`memoryStats`, цели баллона) | да | нет |
74
+ | Проигрывание политик на трассе с дефицитом как метрикой | да | нет |
75
+
76
+ Правило работает настолько, насколько верна граница `v`: когда оно верно и когда нет,
77
+ описано в теории.
78
+
79
+ ## Когда это помогает и когда нет
80
+
81
+ Правило ограничивает дефицит каждого слепого промежутка. Выигрыш перед постоянным
82
+ интервалом зависит от нагрузки:
83
+
84
+ - Для одинаковых машин со всплесками без памяти никакой адаптивный график не лучше
85
+ постоянного интервала при том же бюджете опросов (утверждение 1.4, Йенсен). Библиотека
86
+ этого не скрывает.
87
+ - Выигрыш даёт различие между машинами, а правило задаёт границу дефицита каждого
88
+ промежутка, чего нет у общего отката.
89
+ - Гарантия не лучше границы `v`. Для однотипных машин берите квантиль по длинной
90
+ истории или общую оценку.
91
+
92
+ Сравнение стратегий (сейчас на синтетике, для статьи — на трассах стенда) — это
93
+ эксперимент E2 в [experiments/README.ru.md](experiments/README.ru.md). Синтетические
94
+ результаты показывают поведение, а не измерения.
95
+
96
+ ## Авторы
97
+
98
+ - Андрей Ферубко (Andrey Ferubko), ORCID [0009-0006-5625-137X](https://orcid.org/0009-0006-5625-137X)
99
+ - Олег Дмитриевич Казаков (Oleg Kazakov), ORCID [0000-0001-9665-8138](https://orcid.org/0000-0001-9665-8138)
100
+
101
+ Брянский государственный инженерно-технологический университет (БГИТУ).
102
+
103
+ ## Модули
104
+
105
+ | Модуль | Содержимое |
106
+ |---|---|
107
+ | `rule` | `blind_deficit_bound`, `interval_unclipped`, `next_interval`, `next_interval_aged`, `min_slack`, `expected_cost_rate`, `optimal_interval_expected` |
108
+ | `estimators` | границы `v`: `history_quantile`, `pooled_quantile`, `WindowQuantile`, `FixedRate`, `KalmanRate`, `RateFilterAdapter`; `burst_rate` |
109
+ | `scheduler` | `MonitorScheduler`: интервал у каждой ВМ или общий; протокол `IntervalScheduler` для других расписаний |
110
+ | `replay` | `replay` и политики `FixedPolicy`, `BackoffPolicy`, `DeficitRulePolicy`, `ExpectedCostPolicy` |
111
+ | `libvirt_loop` | `run` (необязательный `scheduler`), `read_state`, `reserve_controller`: живой цикл |
112
+
113
+ ## Разработка
114
+
115
+ ```sh
116
+ uv sync
117
+ uv run ruff check . && uv run ruff format --check .
118
+ uv run mypy
119
+ uv run pytest
120
+ bash scripts/install_hooks.sh # один раз в клоне: ограничения сообщений коммитов
121
+ ```
122
+
123
+ Эксперименты статьи 1 требуют необязательную группу `experiments` (matplotlib):
124
+ `uv sync --group experiments`. Их режимы `--smoke` выполняются в `pytest`; полные
125
+ прогоны описаны в [experiments/README.ru.md](experiments/README.ru.md).
126
+
127
+ Сообщения коммитов в этом репозитории: тема не длиннее 8 символов, тела нет.
128
+ Хук `commit-msg`, который ставит `scripts/install_hooks.sh`, проверяет это правило
129
+ (`scripts/check_commit_msg.py`). CI и автоматической публикации нет.
130
+
131
+ ## Цитирование
132
+
133
+ См. [CITATION.cff](https://github.com/89605502155/desna-interval/blob/main/CITATION.cff)
134
+ или кнопку «Cite this repository» на GitHub.
135
+
136
+ ## Лицензия
137
+
138
+ MIT. Copyright (c) 2026 Andrey Ferubko, Oleg Kazakov. См. [LICENSE](https://github.com/89605502155/desna-interval/blob/main/LICENSE).
@@ -0,0 +1,69 @@
1
+ # API
2
+
3
+ All names are re-exported from the package `desna_interval`. Units: memory in
4
+ MiB, time in seconds, rate in MiB/s, deficit in MiB·s.
5
+
6
+ ## Rule (`desna_interval.rule`)
7
+
8
+ | Function | Meaning |
9
+ |---|---|
10
+ | `blind_deficit_bound(s, v, delta)` | Upper bound on the deficit over a blind gap, claim 1.1 |
11
+ | `interval_unclipped(s, v, eps)` | `s/v + sqrt(2 eps/v)`, claim 1.2; needs `v > 0` |
12
+ | `next_interval(s, v, eps, dmin=1, dmax=10)` | The interval clipped to `[dmin, dmax]` |
13
+ | `next_interval_aged(s, v, eps, age=0, dmin=1, dmax=10)` | Same, minus the snapshot age, claim 1.3 |
14
+ | `min_slack(delta, v, eps)` | Smallest slack that admits interval `delta` |
15
+ | `burst_deficit_integral(delta, s, v)` | `G(delta)` of claim 1.4 |
16
+ | `expected_cost_rate(delta, s, v, rho, c)` | `(rho G(delta) + c) / delta` |
17
+ | `optimal_interval_expected(s, v, rho, c, dmin, dmax)` | Minimiser of the expected cost, claim 1.5 |
18
+
19
+ ## Estimators (`desna_interval.estimators`)
20
+
21
+ An online estimator has `update(t, d)` and `upper() -> float`. A filter
22
+ (`RateFilter`) has `update(t, d)` and `rate() -> (rate, variance)`.
23
+
24
+ | Name | Meaning |
25
+ |---|---|
26
+ | `WindowQuantile(window=300, q=1, v_floor=2)` | Quantile of growth over the last `window` seconds |
27
+ | `FixedRate(v)` | Constant bound, e.g. from `history_quantile` |
28
+ | `KalmanRate(q=1, r=25)` | Scalar Kalman filter of level and growth rate |
29
+ | `RateFilterAdapter(filt, z=2.33, v_floor=2)` | `rate + z·sqrt(variance)` of any `RateFilter` |
30
+ | `growth_rates(t, d, step=1)` | Non-negative growth rates on a regular grid |
31
+ | `history_quantile(t, d, q=0.99, step=1, v_floor=2)` | Robust bound from one history |
32
+ | `pooled_quantile(series, q, step, v_floor)` | Common bound over `(t, d)` pairs |
33
+ | `per_series_quantiles(t, rows, ...)` | `history_quantile` for each row |
34
+ | `burst_rate(t, d, step, rise, window, min_gap)` | Rate of burst onsets `rho`, 1/s |
35
+
36
+ ## Scheduler (`desna_interval.scheduler`)
37
+
38
+ `MonitorScheduler(vms, eps, estimator_factory, dmin=1, dmax=10, mode="per_vm")`.
39
+
40
+ - `due(now)`: the objects to poll now.
41
+ - `next_wakeup()`: time of the next poll.
42
+ - `observe(now, demand, slack, age=None)`: updates the estimators of the polled
43
+ objects and returns their intervals. With `mode="global"` all intervals equal
44
+ the smallest one.
45
+
46
+ ## Replay (`desna_interval.replay`)
47
+
48
+ `replay(t, d, policy, slack) -> ReplayResult`. A policy has
49
+ `next_interval(t, d) -> float`. The result has `polls`, `duration`, `deficit`,
50
+ `gap_deficits`, `intervals`, `mean_interval` and `violation_share(eps)`.
51
+
52
+ Policies: `FixedPolicy(delta)`, `BackoffPolicy(dmin, dmax, factor, threshold)`,
53
+ `DeficitRulePolicy(eps, slack, estimator, dmin, dmax)`,
54
+ `ExpectedCostPolicy(slack, v, rho, c, dmin, dmax)`.
55
+
56
+ ## Live loop (`desna_interval.libvirt_loop`)
57
+
58
+ Requires `libvirt-python`; the module imports without it, so tests use fake
59
+ domains.
60
+
61
+ - `read_state(dom, reserve_mib, balloon_in_total=False, wall=time.time)`
62
+ returns `(allocation MiB, demand MiB, snapshot age s)`.
63
+ - `reserve_controller(s_target=256, deadband=64)`: example controller.
64
+ - `run(domains, controller, eps, reserve_mib, duration_s, log_path, ...)`
65
+ polls the domains on the schedule, applies targets when `apply=True`, writes a
66
+ CSV log and returns the number of polls. `adapt_stats_period=True` sets each
67
+ guest's statistics period to the whole part of its interval. The optional
68
+ `scheduler` replaces `MonitorScheduler` with any object that implements
69
+ `IntervalScheduler` (`due`, `next_wakeup`, `observe`), for example a heuristic.
@@ -0,0 +1,49 @@
1
+ # Command line
2
+
3
+ The program is `desna-interval`. Every subcommand prints one line or a table.
4
+
5
+ desna-interval --version
6
+
7
+ ## `interval`
8
+
9
+ Interval `delta* = s/v + sqrt(2 eps/v)` minus the snapshot age, clipped.
10
+
11
+ desna-interval interval --slack 256 --rate 128 --eps 128 --age 1
12
+ # 2.414
13
+
14
+ | Option | Meaning | Default |
15
+ |---|---|---|
16
+ | `--slack` | slack `s`, MiB | required |
17
+ | `--rate` | growth bound `v`, MiB/s | required |
18
+ | `--eps` | tolerance, MiB·s | required |
19
+ | `--age` | snapshot age, s | 0 |
20
+ | `--dmin`, `--dmax` | clipping bounds, s | 1, 10 |
21
+
22
+ ## `min-slack`
23
+
24
+ Smallest slack that admits an interval.
25
+
26
+ desna-interval min-slack --delta 5 --rate 128 --eps 128
27
+ # 458.980664
28
+
29
+ ## `optimal`
30
+
31
+ Interval that minimises the expected cost.
32
+
33
+ desna-interval optimal --slack 0 --rate 128 --rho 0.01 --cost 64
34
+ # 5.3133
35
+
36
+ Options: `--slack`, `--rate`, `--rho` (burst onsets per second), `--cost` (price
37
+ of one poll in MiB·s), `--dmin`, `--dmax`.
38
+
39
+ ## `replay`
40
+
41
+ Compares policies on a CSV trace with the header `t,d` (time in s, demand in MiB).
42
+
43
+ desna-interval replay trace.csv --eps 64 --slack 256
44
+
45
+ Prints the bound `v` (0.99 quantile of the history), the burst rate `rho` and one
46
+ line per policy: `policy,polls,mean_interval_s,deficit_mib_s,violation_share`.
47
+ Options: `--eps`, `--slack`, `--fixed` (list of fixed intervals, s).
48
+
49
+ Exit status is 0 on success.