187 lines
4.6 KiB
Python
187 lines
4.6 KiB
Python
import argparse
|
|
import os
|
|
import random
|
|
import sqlite3
|
|
import string
|
|
import time
|
|
import uuid
|
|
from typing import Iterable, Tuple
|
|
|
|
SCHEMA = """
|
|
CREATE TABLE IF NOT EXISTS clients (
|
|
id TEXT PRIMARY KEY,
|
|
hwid TEXT,
|
|
role TEXT,
|
|
host TEXT,
|
|
os TEXT,
|
|
arch TEXT,
|
|
version TEXT,
|
|
user TEXT,
|
|
monitors INTEGER,
|
|
country TEXT,
|
|
last_seen INTEGER,
|
|
online INTEGER,
|
|
ping_ms INTEGER
|
|
);
|
|
"""
|
|
|
|
ROLES = ["client", "viewer"]
|
|
OSES = ["windows", "linux", "darwin", "ubuntu", "debian", "arch", "kali", "fedora"]
|
|
ARCHES = ["amd64", "arm64", "x86", "arm"]
|
|
COUNTRIES = [
|
|
"US",
|
|
"GB",
|
|
"DE",
|
|
"FR",
|
|
"ES",
|
|
"CA",
|
|
"AU",
|
|
"IN",
|
|
"BR",
|
|
"ZA",
|
|
"JP",
|
|
"KR",
|
|
"CN",
|
|
"SG",
|
|
"SE",
|
|
"NO",
|
|
"DK",
|
|
"FI",
|
|
"PL",
|
|
"MX",
|
|
]
|
|
|
|
|
|
def random_host() -> str:
|
|
prefix = random.choice(["desk", "laptop", "vm", "srv", "pc"])
|
|
suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
|
return f"{prefix}-{suffix}"
|
|
|
|
|
|
def random_user() -> str:
|
|
first = random.choice(
|
|
["alice", "bob", "carol", "dave", "erin", "frank", "grace", "heidi"]
|
|
)
|
|
num = random.randint(1, 9999)
|
|
return f"{first}{num}"
|
|
|
|
|
|
def random_version() -> str:
|
|
return f"{random.randint(0, 5)}.{random.randint(0, 20)}.{random.randint(0, 9)}"
|
|
|
|
|
|
def random_row(now_ms: int, online_rate: float) -> Tuple:
|
|
client_id = uuid.uuid4().hex
|
|
hwid = uuid.uuid4().hex
|
|
role = random.choice(ROLES)
|
|
host = random_host()
|
|
os_name = random.choice(OSES)
|
|
arch = random.choice(ARCHES)
|
|
version = random_version()
|
|
user = random_user()
|
|
monitors = random.randint(1, 3)
|
|
country = random.choice(COUNTRIES)
|
|
last_seen = now_ms - random.randint(0, 7 * 24 * 60 * 60 * 1000)
|
|
online = 1 if random.random() < online_rate else 0
|
|
ping_ms = random.choice([None, random.randint(10, 400), random.randint(400, 2000)])
|
|
return (
|
|
client_id,
|
|
hwid,
|
|
role,
|
|
host,
|
|
os_name,
|
|
arch,
|
|
version,
|
|
user,
|
|
monitors,
|
|
country,
|
|
last_seen,
|
|
online,
|
|
ping_ms,
|
|
)
|
|
|
|
|
|
def batched_rows(
|
|
count: int, batch_size: int = 500, online_rate: float = 0.6
|
|
) -> Iterable[Tuple[Tuple, ...]]:
|
|
now_ms = int(time.time() * 1000)
|
|
batch = []
|
|
for _ in range(count):
|
|
batch.append(random_row(now_ms, online_rate))
|
|
if len(batch) >= batch_size:
|
|
yield tuple(batch)
|
|
batch = []
|
|
if batch:
|
|
yield tuple(batch)
|
|
|
|
|
|
def ensure_schema(conn: sqlite3.Connection) -> None:
|
|
conn.execute(SCHEMA)
|
|
|
|
|
|
def seed(db_path: str, count: int, truncate: bool, online_rate: float) -> None:
|
|
conn = sqlite3.connect(db_path)
|
|
try:
|
|
ensure_schema(conn)
|
|
if truncate:
|
|
conn.execute("DELETE FROM clients")
|
|
conn.commit()
|
|
sql = (
|
|
"INSERT INTO clients (id, hwid, role, host, os, arch, version, user, monitors, country, last_seen, online, ping_ms) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
)
|
|
total = 0
|
|
for batch in batched_rows(count, online_rate=online_rate):
|
|
conn.executemany(sql, batch)
|
|
conn.commit()
|
|
total += len(batch)
|
|
print(f"Inserted {total}/{count}...")
|
|
print(f"Done. Inserted {total} rows into {db_path}")
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def resolve_default_db() -> str:
|
|
data_dir = os.getenv("DATA_DIR", "").strip()
|
|
if not data_dir:
|
|
if os.name == "nt" and os.getenv("APPDATA"):
|
|
data_dir = os.path.join(os.environ["APPDATA"], "Overlord")
|
|
else:
|
|
data_dir = "./data"
|
|
return os.path.abspath(os.path.join(data_dir, "overlord.db"))
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(
|
|
description="Seed fake clients into overlord.db for load testing."
|
|
)
|
|
parser.add_argument(
|
|
"--db",
|
|
default=resolve_default_db(),
|
|
help="Path to overlord.db (default follows server logic: DATA_DIR, then APPDATA/Overlord on Windows, else ./data)",
|
|
)
|
|
parser.add_argument(
|
|
"--count",
|
|
type=int,
|
|
default=100000,
|
|
help="How many rows to insert (default: 10000)",
|
|
)
|
|
parser.add_argument(
|
|
"--truncate", action="store_true", help="Delete existing rows before seeding"
|
|
)
|
|
parser.add_argument(
|
|
"--online-rate",
|
|
type=float,
|
|
default=0.6,
|
|
help="Probability a seeded client is online (0-1, default: 0.6)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
db_path = os.path.abspath(args.db)
|
|
print(f"Seeding {args.count} clients into {db_path} (truncate={args.truncate})")
|
|
seed(db_path, args.count, args.truncate, args.online_rate)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|