Repository navigation
Expand file tree
/
Copy patharima_predictor.py
More file actions
111 lines (87 loc) · 2.89 KB
/
Copy patharima_predictor.py
File metadata and controls
111 lines (87 loc) · 2.89 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
"""
arima_predictor.py
ARIMA-based predictor server for edge-faas-cpp.
Algorithm:
- Maintains a sliding window of the last WINDOW_SIZE RPS observations.
- If history < MIN_SAMPLES: falls back to reactive (ceil(rps * T) + margin).
- Otherwise: fits ARIMA(2,1,2), forecasts next-window RPS, applies Little's Law.
- Reports inference latency per call so C++ can log it.
CSCI 599: Network Systems for Cloud Computing
University of Southern California
"""
import os
import sys
import math
import time
import socket
import threading
import warnings
from collections import deque
warnings.filterwarnings("ignore")
ARIMA_SOCK = "/tmp/faas_arima.sock"
WINDOW_SIZE = 30 # sliding history length
MIN_SAMPLES = 10 # minimum before ARIMA is reliable
SAFETY_MARGIN = 1 # matches Predictor._safety_margin
try:
from statsmodels.tsa.arima.model import ARIMA
ARIMA_AVAILABLE = True
except ImportError:
ARIMA_AVAILABLE = False
_history = deque(maxlen=WINDOW_SIZE)
_history_lock = threading.Lock()
def predict(rps: int, avg_service_time: float) -> tuple[int, float]:
"""
Record rps, fit ARIMA if enough data, return (target_workers, latency_ms).
Little's Law: target = ceil(predicted_lambda * T) + margin
"""
t0 = time.perf_counter()
with _history_lock:
_history.append(rps)
data = list(_history)
predicted_lambda = float(rps) # fallback: reactive
if ARIMA_AVAILABLE and len(data) >= MIN_SAMPLES:
try:
fit = ARIMA(data, order=(2, 1, 2)).fit()
fc = fit.forecast(steps=1)[0]
if fc > 0:
predicted_lambda = fc
except Exception:
pass # stay with reactive fallback
target = int(math.ceil(predicted_lambda * avg_service_time)) + SAFETY_MARGIN
latency_ms = (time.perf_counter() - t0) * 1000.0
return target, latency_ms
def handle_client(conn: socket.socket) -> None:
try:
buf = b""
while b"\n" not in buf:
chunk = conn.recv(64)
if not chunk:
return
buf += chunk
line = buf.decode().strip()
parts = line.split(",")
rps = int(parts[0])
avg_t = float(parts[1]) if len(parts) > 1 else 0.1
target, latency_ms = predict(rps, avg_t)
conn.sendall(f"{target},{latency_ms:.3f}\n".encode())
except Exception:
pass
finally:
conn.close()
def main() -> None:
ready_fd = int(sys.argv[1])
try:
os.unlink(ARIMA_SOCK)
except FileNotFoundError:
pass
srv = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
srv.bind(ARIMA_SOCK)
srv.listen(16)
# Signal C++ that the socket is up and ready
os.write(ready_fd, b"READY\n")
os.close(ready_fd)
while True:
conn, _ = srv.accept()
threading.Thread(target=handle_client, args=(conn,), daemon=True).start()
if __name__ == "__main__":
main()