Repository navigation
Expand file tree
/
Copy pathdb.py
More file actions
377 lines (334 loc) · 15.6 KB
/
Copy pathdb.py
File metadata and controls
377 lines (334 loc) · 15.6 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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
"""Shared database schema and read/write interface."""
import json
import sqlite3
import time
from config import DB_PATH
def _harden_connection(conn):
"""Best-effort per-connection pragmas. journal_mode is a one-time, whole-file
switch — see ensure_wal_mode() for the race-free way to enable it up front."""
conn.execute("PRAGMA busy_timeout=10000")
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
return conn
def ensure_wal_mode(db_path=DB_PATH):
"""Switch the DB file to WAL mode once, via a single connection, before any
concurrent writers open their own connections. Doing this per-connection from
multiple threads/processes at once races on the initial (non-WAL -> WAL) file
header rewrite and can raise 'database is locked' even with busy_timeout set."""
conn = sqlite3.connect(db_path)
conn.execute("PRAGMA busy_timeout=10000")
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
try:
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(layer1_sys)")
columns = [row[1] for row in cursor.fetchall()]
needed = {"num_threads": "INTEGER", "udp_tcp_ratio": "REAL", **LAYER1_NEW_COLUMNS}
for col, col_type in needed.items():
if col not in columns:
cursor.execute(f"ALTER TABLE layer1_sys ADD COLUMN {col} {col_type}")
conn.commit()
except Exception:
pass
conn.close()
# layer 2 db code starts here
def create_layer2_connection(db_path=DB_PATH):
conn = sqlite3.connect(db_path, check_same_thread=False)
_harden_connection(conn)
return conn
def init_layer2_db(conn):
"""Initializes the unified layer2_proc table for Layer 2 telemetry."""
conn.execute("""
CREATE TABLE IF NOT EXISTS layer2_proc (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
cpu_1_pid INTEGER, cpu_1_ppid INTEGER, cpu_1_name TEXT, cpu_1_status TEXT, cpu_1_cpu_peak REAL,
cpu_2_pid INTEGER, cpu_2_ppid INTEGER, cpu_2_name TEXT, cpu_2_status TEXT, cpu_2_cpu_peak REAL,
cpu_3_pid INTEGER, cpu_3_ppid INTEGER, cpu_3_name TEXT, cpu_3_status TEXT, cpu_3_cpu_peak REAL,
cpu_4_pid INTEGER, cpu_4_ppid INTEGER, cpu_4_name TEXT, cpu_4_status TEXT, cpu_4_cpu_peak REAL,
cpu_5_pid INTEGER, cpu_5_ppid INTEGER, cpu_5_name TEXT, cpu_5_status TEXT, cpu_5_cpu_peak REAL,
ram_1_pid INTEGER, ram_1_ppid INTEGER, ram_1_name TEXT, ram_1_status TEXT, ram_1_peak REAL, ram_1_open_fds REAL,
ram_2_pid INTEGER, ram_2_ppid INTEGER, ram_2_name TEXT, ram_2_status TEXT, ram_2_peak REAL, ram_2_open_fds REAL,
ram_3_pid INTEGER, ram_3_ppid INTEGER, ram_3_name TEXT, ram_3_status TEXT, ram_3_peak REAL, ram_3_open_fds REAL,
ram_4_pid INTEGER, ram_4_ppid INTEGER, ram_4_name TEXT, ram_4_status TEXT, ram_4_peak REAL, ram_4_open_fds REAL,
ram_5_pid INTEGER, ram_5_ppid INTEGER, ram_5_name TEXT, ram_5_status TEXT, ram_5_peak REAL, ram_5_open_fds REAL
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_layer2_proc_timestamp ON layer2_proc (timestamp)")
conn.commit()
def write_layer2(conn, top_cpu, top_mem):
"""Store one fixed-width Layer 2 snapshot, padding short process lists."""
values = [time.time()]
for processes, fields in (
(top_cpu, ("pid", "ppid", "name", "status", "cpu_peak")),
(top_mem, ("pid", "ppid", "name", "status", "ram_peak", "open_fds")),
):
for index in range(5):
process = processes[index] if index < len(processes) else {}
values.extend(
process.get(field, "") if field in {"name", "status"} else process.get(field)
for field in fields
)
placeholders = ", ".join("?" for _ in values)
conn.execute(f"INSERT INTO layer2_proc VALUES (NULL, {placeholders})", values)
conn.commit()
def init_db():
"""Initializes the unified process_snapshot table for Layer 2 telemetry."""
conn = None
try:
conn = sqlite3.connect(DB_PATH, timeout=10.0)
conn.execute("""
CREATE TABLE IF NOT EXISTS process_snapshot (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
top_cpu_processes TEXT NOT NULL,
top_ram_processes TEXT NOT NULL
)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_snapshot_ts
ON process_snapshot (timestamp)
""")
conn.commit()
finally:
if conn is not None:
conn.close()
def insert_process_snapshot(top_cpu, top_ram):
"""Writes one unified row per 5-second poll — timestamp + two compact JSON arrays."""
row = (
time.time(),
json.dumps(top_cpu, separators=(',', ':')),
json.dumps(top_ram, separators=(',', ':'))
)
conn = None
try:
conn = sqlite3.connect(DB_PATH, timeout=10.0)
conn.execute(
"INSERT INTO process_snapshot (timestamp, top_cpu_processes, top_ram_processes) VALUES (?, ?, ?)",
row
)
conn.commit()
finally:
if conn is not None:
conn.close()
# layer 2 db code ends here
# layer 1 db code starts here
db_path = DB_PATH
# New raw columns for the OS Doctor 24-feature vector.
# The names here are ALSO the keys in collect_layer1_metrics()'s dict and the
# keyword names accepted by write_layer1(), so one list controls all three.
LAYER1_NEW_COLUMNS = {
"nr_running_per_core": "REAL", # /proc/stat procs_running / cores
"involuntary_context_switch_rate": "REAL", # switches per second
"uninterruptible_d_state_count": "INTEGER", # processes in D state
"cpu_iowait_percent": "REAL", # % of CPU time in iowait
"cpu_psi_some_avg10": "REAL", # /proc/pressure/cpu
"memory_psi_full_avg10": "REAL", # /proc/pressure/memory
"io_psi_some_avg10": "REAL", # /proc/pressure/io
"io_psi_full_avg10": "REAL", # /proc/pressure/io
"direct_reclaim_rate": "REAL", # allocstall_* per second
"major_page_fault_rate": "REAL", # pgmajfault per second
"swap_out_rate": "REAL", # MB/s swapped out
"io_latency": "REAL", # ms per disk operation
"system_open_fds": "INTEGER", # /proc/sys/fs/file-nr
"thermal_throttling_events": "REAL", # throttle events per second
"tcp_retrans_rate": "REAL", # TCP retransmits per second
}
def create_connection(db_path):
conn = sqlite3.connect(db_path, check_same_thread=False)
_harden_connection(conn)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS layer1_sys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
cpu_usage_percent REAL,
cpu_freq REAL,
cpu_user_time REAL,
cpu_system_time REAL,
cpu_idle_time REAL,
cpu_iowait_time REAL,
cpu_busy_time REAL,
cpu_ctx_switches REAL,
memory_percent REAL,
memory_used INTEGER,
memory_available INTEGER,
memory_cached INTEGER,
memory_buffers INTEGER,
swap_percent REAL,
swap_sin INTEGER,
swap_sout INTEGER,
disk_usage_percent REAL,
disk_read_mb_s REAL,
disk_write_mb_s REAL,
disk_read_time INTEGER,
disk_write_time INTEGER,
net_rate_mb_s REAL,
net_bytes_sent INTEGER,
net_bytes_recv INTEGER,
net_packets_sent INTEGER,
net_packets_recv INTEGER,
net_errs INTEGER,
net_drops INTEGER,
load_avg_1 REAL,
load_avg_5 REAL,
load_avg_15 REAL,
total_processes INTEGER,
running_processes INTEGER,
sleeping_processes INTEGER,
zombie_processes INTEGER,
avg_temp REAL,
max_temp REAL,
battery_percent REAL,
process_data TEXT,
num_threads INTEGER
)
''')
# CREATE TABLE IF NOT EXISTS does nothing on an existing DB, so add any
# missing columns here (num_threads + the OS Doctor columns + udp_tcp_ratio).
cursor.execute("PRAGMA table_info(layer1_sys)")
columns = [row[1] for row in cursor.fetchall()]
needed = {"num_threads": "INTEGER", "udp_tcp_ratio": "REAL", **LAYER1_NEW_COLUMNS}
for col, col_type in needed.items():
if col not in columns:
try:
cursor.execute(f"ALTER TABLE layer1_sys ADD COLUMN {col} {col_type}")
except sqlite3.OperationalError:
pass
conn.commit()
return conn
# Function to write the collected metrics into the database
def write_layer1(conn, timestamp, cpu_usage_percent, cpu_freq, cpu_user_time, cpu_system_time, cpu_idle_time, cpu_iowait_time, cpu_busy_time, cpu_ctx_switches, memory_percent, memory_used, memory_available, memory_cached, memory_buffers, swap_percent, swap_sin, swap_sout, disk_usage_percent, disk_read_mb_s, disk_write_mb_s, disk_read_time, disk_write_time, load_avg_1, load_avg_5, load_avg_15, total_processes, running_processes, sleeping_processes, zombie_processes, avg_temp, max_temp, battery_percent, net_rate_mb_s, net_bytes_sent, net_bytes_recv, net_packets_sent, net_packets_recv, net_errs, net_drops, process_data, num_threads, **new_columns):
# new_columns: the OS Doctor values + udp_tcp_ratio, passed by name.
# Missing ones are stored as NULL. An unknown name is a typo, so fail loudly.
_all_extra_columns = {**LAYER1_NEW_COLUMNS, "udp_tcp_ratio": "REAL"}
unknown = set(new_columns) - set(_all_extra_columns)
if unknown:
raise ValueError(f"write_layer1: unknown column(s) {sorted(unknown)}")
column_names = [
"timestamp", "cpu_usage_percent", "cpu_freq", "cpu_user_time", "cpu_system_time",
"cpu_idle_time", "cpu_iowait_time", "cpu_busy_time", "cpu_ctx_switches", "memory_percent",
"memory_used", "memory_available", "memory_cached", "memory_buffers", "swap_percent",
"swap_sin", "swap_sout", "disk_usage_percent", "disk_read_mb_s", "disk_write_mb_s",
"disk_read_time", "disk_write_time", "load_avg_1", "load_avg_5", "load_avg_15",
"total_processes", "running_processes", "sleeping_processes", "zombie_processes", "avg_temp",
"max_temp", "battery_percent", "net_rate_mb_s", "net_bytes_sent", "net_bytes_recv",
"net_packets_sent", "net_packets_recv", "net_errs", "net_drops", "process_data",
"num_threads",
] + list(_all_extra_columns)
values = (timestamp,
cpu_usage_percent,
cpu_freq, cpu_user_time,
cpu_system_time,
cpu_idle_time,
cpu_iowait_time,
cpu_busy_time,
cpu_ctx_switches,
memory_percent,
memory_used,
memory_available,
memory_cached,
memory_buffers,
swap_percent,
swap_sin,
swap_sout,
disk_usage_percent,
disk_read_mb_s,
disk_write_mb_s,
disk_read_time,
disk_write_time,
load_avg_1,
load_avg_5,
load_avg_15,
total_processes,
running_processes,
sleeping_processes,
zombie_processes,
avg_temp,
max_temp,
battery_percent,
net_rate_mb_s,
net_bytes_sent,
net_bytes_recv,
net_packets_sent,
net_packets_recv,
net_errs,
net_drops,
process_data,
num_threads) + tuple(new_columns.get(col) for col in _all_extra_columns)
placeholders = ", ".join("?" for _ in column_names)
cursor = conn.cursor()
cursor.execute(
f"INSERT INTO layer1_sys ({', '.join(column_names)}) VALUES ({placeholders})",
values,
)
conn.commit()
# Rule Engine database functions
def init_workload_events_table(conn):
"""Initializes the workload_events table for deterministic rule engine telemetry."""
conn.execute("""
CREATE TABLE IF NOT EXISTS workload_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
workload TEXT NOT NULL,
state TEXT NOT NULL,
cpu_attribution REAL,
ram_attribution REAL,
workload_score REAL,
system_cpu REAL,
system_memory REAL,
top_process TEXT,
evidence_json TEXT,
persistence INTEGER,
ml_confidence REAL,
ml_workload TEXT,
ml_probabilities TEXT
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_workload_events_ts ON workload_events (timestamp)")
# Ensure backwards compatibility for existing databases by safely adding new columns
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(workload_events)")
existing_cols = {c[1] for c in cursor.fetchall()}
for col, col_type in [("ml_confidence", "REAL"), ("ml_workload", "TEXT"), ("ml_probabilities", "TEXT")]:
if col not in existing_cols:
try:
conn.execute(f"ALTER TABLE workload_events ADD COLUMN {col} {col_type}")
except Exception:
pass
conn.commit()
def write_workload_event(conn, timestamp, workload, state, cpu_attribution, ram_attribution, workload_score, system_cpu, system_memory, top_process, evidence_json, persistence, ml_confidence=0.0, ml_workload="IDLE", ml_probabilities="{}"):
"""Writes a workload evaluation snapshot to workload_events."""
conn.execute("""
INSERT INTO workload_events (
timestamp, workload, state, cpu_attribution, ram_attribution,
workload_score, system_cpu, system_memory, top_process, evidence_json, persistence,
ml_confidence, ml_workload, ml_probabilities
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (timestamp, workload, state, cpu_attribution, ram_attribution, workload_score, system_cpu, system_memory, top_process, evidence_json, persistence, ml_confidence, ml_workload, ml_probabilities))
conn.commit()
def init_optimization_events_table(conn):
"""Initializes the optimization_events table for safe resource attribution actions."""
conn.execute("""
CREATE TABLE IF NOT EXISTS optimization_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
pid INTEGER,
process_name TEXT,
workload TEXT,
action TEXT,
old_value TEXT,
new_value TEXT,
reason TEXT,
success INTEGER
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_opt_events_ts ON optimization_events (timestamp)")
conn.commit()
def write_optimization_event(conn, timestamp, pid, process_name, workload, action, old_value, new_value, reason, success):
"""Writes an optimization action log to optimization_events."""
conn.execute("""
INSERT INTO optimization_events (
timestamp, pid, process_name, workload, action, old_value, new_value, reason, success
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (timestamp, pid, process_name, workload, action, old_value, new_value, reason, int(success)))
conn.commit()