forked from Role1776/netmon
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsqlite.py
More file actions
196 lines (171 loc) · 6.74 KB
/
Copy pathsqlite.py
File metadata and controls
196 lines (171 loc) · 6.74 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
import json
import models
from contextlib import closing, contextmanager
from contextvars import ContextVar
import sqlite3
import uuid
from datetime import datetime
from uuid_extensions import uuid7str
_tx_depth: ContextVar[int] = ContextVar("tx_depth", default=0)
class DB:
def __init__(self, conn: sqlite3.Connection):
self.conn: sqlite3.Connection = conn
def __enter__(self) -> "DB":
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
@classmethod
def init(cls, path: str) -> "DB":
if not path.strip():
raise ValueError("Database path cannot be empty")
try:
conn = sqlite3.connect(path)
conn.execute("PRAGMA foreign_keys = ON")
db = cls(conn)
with db.transaction():
db._create_schema()
return db
except sqlite3.Error as e:
raise RuntimeError(f"Failed to open database connection: {e}")
def _create_schema(self):
self.conn.execute("""
CREATE TABLE IF NOT EXISTS metrics (
id TEXT PRIMARY KEY,
download REAL NOT NULL,
upload REAL NOT NULL,
ping REAL NOT NULL,
share TEXT,
client TEXT NOT NULL,
server TEXT NOT NULL,
bytes_sent INTEGER NOT NULL,
bytes_received INTEGER NOT NULL,
timestamp DATETIME NOT NULL DEFAULT (datetime('now'))
);
""")
self.conn.execute("""
CREATE TABLE IF NOT EXISTS device_scans (
id TEXT PRIMARY KEY,
ips TEXT NOT NULL,
latencies TEXT NOT NULL
);
""")
self.conn.execute("""
CREATE TABLE IF NOT EXISTS speedtest (
id TEXT PRIMARY KEY,
device_scans_id TEXT UNIQUE REFERENCES device_scans(id) ON DELETE CASCADE,
metrics_id TEXT UNIQUE REFERENCES metrics(id) ON DELETE CASCADE
);
""")
@contextmanager
def transaction(self):
depth = _tx_depth.get()
_tx_depth.set(depth + 1)
try:
if depth == 0:
with self.conn:
yield
else:
yield
except sqlite3.Error as e:
raise RuntimeError(f"Database transaction failed: {e}")
finally:
_tx_depth.set(depth)
def add_metric(self, metric: models.NetworkMetric):
with self.transaction():
self.conn.execute("""
INSERT INTO metrics (
id, download, upload, ping, timestamp, share, client, server,
bytes_sent, bytes_received
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
str(metric.id), metric.download, metric.upload,
metric.ping, metric.timestamp, metric.share,
metric.client, metric.server, metric.bytes_sent,
metric.bytes_received
))
def add_devices(self, devices: list[models.NetworkDevice]) -> uuid.UUID:
scan_id = uuid.UUID(uuid7str())
ips = json.dumps([d.ip for d in devices])
latencies = json.dumps([d.latency_ms for d in devices])
with self.transaction():
self.conn.execute("""
INSERT INTO device_scans (id, ips, latencies)
VALUES (?, ?, ?)
""", (str(scan_id), ips, latencies))
return scan_id
def add_speedtest(self, speedtest: models.SpeedTest):
with self.transaction():
self.conn.execute("""
INSERT INTO speedtest (id, metrics_id, device_scans_id)
VALUES (?, ?, ?)
""", (str(speedtest.id), str(speedtest.metric_id), str(speedtest.device_scan_id)))
def get_metrics(self) -> list[models.NetworkMetric]:
try:
with closing(self.conn.cursor()) as cursor:
cursor.execute("""
SELECT * FROM (
SELECT id, download, upload, ping, timestamp, share, client, server, bytes_sent, bytes_received
FROM metrics
WHERE timestamp > DATETIME('now', '-24 hours')
ORDER BY timestamp DESC
LIMIT 24
) ORDER BY timestamp ASC;
""")
rows = cursor.fetchall()
except sqlite3.Error as e:
raise RuntimeError(f"Failed to get metrics: {e}")
if not rows:
return []
return [
models.NetworkMetric(
id=uuid.UUID(row[0]),
download=row[1],
upload=row[2],
ping=row[3],
timestamp=datetime.fromisoformat(row[4]),
share=row[5],
client=row[6],
server=row[7],
bytes_sent=row[8],
bytes_received=row[9]
)
for row in rows
]
def get_metrics_with_device_counts(self) -> tuple[list[models.NetworkMetric], list[int]]:
try:
with closing(self.conn.cursor()) as cursor:
cursor.execute("""
SELECT * FROM (
SELECT m.id, m.download, m.upload, m.ping, m.timestamp, m.share, m.client, m.server,
m.bytes_sent, m.bytes_received, ds.ips
FROM metrics m
JOIN speedtest st ON st.metrics_id = m.id
JOIN device_scans ds ON ds.id = st.device_scans_id
WHERE m.timestamp > DATETIME('now', '-24 hours')
ORDER BY m.timestamp DESC
LIMIT 24
) ORDER BY timestamp ASC;
""")
rows = cursor.fetchall()
except sqlite3.Error as e:
raise RuntimeError(f"Failed to get metrics with device counts: {e}")
metrics: list[models.NetworkMetric] = []
device_counts: list[int] = []
for row in rows:
metrics.append(models.NetworkMetric(
id=uuid.UUID(row[0]),
download=row[1],
upload=row[2],
ping=row[3],
timestamp=datetime.fromisoformat(row[4]),
share=row[5],
client=row[6],
server=row[7],
bytes_sent=row[8],
bytes_received=row[9]
))
device_counts.append(len(json.loads(row[10])))
return metrics, device_counts
def close(self):
self.conn.close()