forked from AliceO2Group/Control
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathallocate.py
More file actions
executable file
·265 lines (217 loc) · 9.25 KB
/
allocate.py
File metadata and controls
executable file
·265 lines (217 loc) · 9.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/usr/bin/env python3
import argparse
import mmap
import os
import sys
import time
def parse_args():
p = argparse.ArgumentParser(
description="Memory load generator: private allocation + shmem(tmpfs file) mmap read/write."
)
# Private memory
p.add_argument("--private-rate-bps", type=int, default=0,
help="Private memory allocation+fill rate in bytes/sec. 0 disables.")
p.add_argument("--private-bytes", type=int, default=0,
help="Optional cap for private bytes to allocate. 0 means no cap (grow forever).")
p.add_argument("--private-chunk-bytes", type=int, default=4 * 1024 * 1024,
help="Chunk size for private allocations (bytes). Default 4MiB.")
p.add_argument("--page-touch-bytes", type=int, default=4096,
help="Stride for touching/filling memory to force residency. Default 4096.")
# Shmem mmap activity
p.add_argument("--shmem-path", type=str, default="",
help="Path to a shmem/tmpfs-backed file (e.g. /dev/shm/segment.bin).")
p.add_argument("--shmem-bytes", type=int, default=0,
help="Ensure shmem file size is at least this many bytes (create/truncate). 0 = don't change size.")
p.add_argument("--shmem-read-rate-bps", type=int, default=0,
help="Read rate from shmem mapping in bytes/sec. Only reads if > 0.")
p.add_argument("--shmem-write-rate-bps", type=int, default=0,
help="Write rate to shmem mapping in bytes/sec. Only writes if > 0.")
# Loop control
p.add_argument("--tick-ms", type=int, default=50,
help="Control loop tick in milliseconds. Default 50ms.")
p.add_argument("--report-every-s", type=float, default=2.0,
help="Print status every N seconds. Default 2s.")
return p.parse_args()
def touch_buffer(buf: bytearray, stride: int):
"""Touch/dirty memory with given stride to force page allocation."""
n = len(buf)
if n == 0:
return
v = int(time.time_ns() & 0xFF)
for i in range(0, n, stride):
buf[i] = (buf[i] + v + (i & 0xFF)) & 0xFF
def ensure_file_size(path: str, size: int):
fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o666)
try:
os.ftruncate(fd, size)
finally:
os.close(fd)
def main():
args = parse_args()
for name, v in [
("--private-rate-bps", args.private_rate_bps),
("--shmem-read-rate-bps", args.shmem_read_rate_bps),
("--shmem-write-rate-bps", args.shmem_write_rate_bps),
]:
if v < 0:
print(f"{name} must be >= 0", file=sys.stderr)
return 2
if args.private_chunk_bytes <= 0:
print("--private-chunk-bytes must be > 0", file=sys.stderr)
return 2
if args.page_touch_bytes <= 0:
print("--page-touch-bytes must be > 0", file=sys.stderr)
return 2
tick = args.tick_ms / 1000.0
report_every = args.report_every_s
# Private memory state
private_chunks: list[bytearray] = []
private_allocated = 0
# Shmem state
shmem_fd = None
shmem_mm = None
shmem_size = 0
shmem_r_off = 0
shmem_w_off = 0
shmem_read_total = 0
shmem_written_total = 0
need_shmem = (args.shmem_read_rate_bps > 0) or (args.shmem_write_rate_bps > 0)
if need_shmem:
if not args.shmem_path:
print("shmem activity requested but --shmem-path is empty", file=sys.stderr)
return 2
if args.shmem_bytes > 0:
try:
ensure_file_size(args.shmem_path, args.shmem_bytes)
except Exception as e:
print(f"Failed to create/size shmem file: {e}", file=sys.stderr)
return 2
# For mmap write, we need RDWR; for read-only, RDONLY is enough
open_flags = os.O_RDONLY if args.shmem_write_rate_bps == 0 else os.O_RDWR
try:
shmem_fd = os.open(args.shmem_path, open_flags)
st = os.fstat(shmem_fd)
shmem_size = st.st_size
if shmem_size <= 0:
raise RuntimeError("shmem file size is 0; set --shmem-bytes to something > 0")
access = mmap.ACCESS_READ if args.shmem_write_rate_bps == 0 else mmap.ACCESS_WRITE
shmem_mm = mmap.mmap(shmem_fd, shmem_size, access=access)
except Exception as e:
if shmem_fd is not None:
try:
os.close(shmem_fd)
except Exception:
pass
print(f"Failed to open/mmap shmem file: {e}", file=sys.stderr)
return 2
last = time.monotonic()
last_report = last
private_budget = 0.0
shmem_read_budget = 0.0
shmem_write_budget = 0.0
# A tiny accumulator so reads can't be optimized away
checksum = 0
try:
while True:
now = time.monotonic()
dt = now - last
last = now
private_budget += args.private_rate_bps * dt
shmem_read_budget += args.shmem_read_rate_bps * dt
shmem_write_budget += args.shmem_write_rate_bps * dt
# --- Private allocation (allocate + touch) ---
if args.private_rate_bps > 0:
cap = args.private_bytes
while private_budget >= 1:
if cap > 0 and private_allocated >= cap:
private_budget = 0
break
chunk = args.private_chunk_bytes
if cap > 0:
remaining = cap - private_allocated
if remaining <= 0:
private_budget = 0
break
if remaining < chunk:
chunk = remaining
if private_budget < chunk:
chunk = int(private_budget)
if chunk < 64 * 1024:
break
buf = bytearray(chunk)
touch_buffer(buf, args.page_touch_bytes)
private_chunks.append(buf)
private_allocated += chunk
private_budget -= chunk
# --- Shmem mmap write: touches mapped pages, should increase RES/SHR ---
if shmem_mm is not None and args.shmem_write_rate_bps > 0:
while shmem_write_budget >= 1:
to_write = int(shmem_write_budget)
if to_write <= 0:
break
if shmem_w_off >= shmem_size:
shmem_w_off = 0
n = min(to_write, shmem_size - shmem_w_off)
if n <= 0:
shmem_w_off = 0
break
# Touch each page once (or more if n is large) to fault pages in.
stride = 4096
v = int(time.time_ns() & 0xFF)
end = shmem_w_off + n
for off in range(shmem_w_off, end, stride):
shmem_mm[off] = (shmem_mm[off] + v) & 0xFF
shmem_w_off = end
shmem_written_total += n
shmem_write_budget -= n
if shmem_w_off >= shmem_size:
shmem_w_off = 0
# --- Shmem mmap read: faults pages in (RES/SHR can still rise), no dirtying ---
if shmem_mm is not None and args.shmem_read_rate_bps > 0:
while shmem_read_budget >= 1:
to_read = int(shmem_read_budget)
if to_read <= 0:
break
if shmem_r_off >= shmem_size:
shmem_r_off = 0
n = min(to_read, shmem_size - shmem_r_off)
if n <= 0:
shmem_r_off = 0
break
stride = 4096
end = shmem_r_off + n
for off in range(shmem_r_off, end, stride):
checksum ^= shmem_mm[off]
shmem_r_off = end
shmem_read_total += n
shmem_read_budget -= n
if shmem_r_off >= shmem_size:
shmem_r_off = 0
# --- Reporting ---
if now - last_report >= report_every:
last_report = now
parts = [f"private_allocated={private_allocated}B"]
if shmem_mm is not None:
parts.append(f"shmem_size={shmem_size}B")
parts.append(f"shmem_written_total={shmem_written_total}B")
parts.append(f"shmem_read_total={shmem_read_total}B")
parts.append(f"checksum={checksum}")
parts.append(f"shmem_path={args.shmem_path}")
print(" ".join(parts), flush=True)
time.sleep(tick)
except KeyboardInterrupt:
print("\nExiting.", file=sys.stderr)
finally:
if shmem_mm is not None:
try:
shmem_mm.close()
except Exception:
pass
if shmem_fd is not None:
try:
os.close(shmem_fd)
except Exception:
pass
return 0
if __name__ == "__main__":
raise SystemExit(main())