maj
This commit is contained in:
@@ -49,71 +49,88 @@ async def submit_benchmark(
|
||||
# Update device timestamp
|
||||
device.updated_at = datetime.utcnow()
|
||||
|
||||
# 2. Create hardware snapshot
|
||||
# 2. Get or create hardware snapshot
|
||||
hw = payload.hardware
|
||||
snapshot = HardwareSnapshot(
|
||||
device_id=device.id,
|
||||
captured_at=datetime.utcnow(),
|
||||
|
||||
# CPU
|
||||
cpu_vendor=hw.cpu.vendor if hw.cpu else None,
|
||||
cpu_model=hw.cpu.model if hw.cpu else None,
|
||||
cpu_microarchitecture=hw.cpu.microarchitecture if hw.cpu else None,
|
||||
cpu_cores=hw.cpu.cores if hw.cpu else None,
|
||||
cpu_threads=hw.cpu.threads if hw.cpu else None,
|
||||
cpu_base_freq_ghz=hw.cpu.base_freq_ghz if hw.cpu else None,
|
||||
cpu_max_freq_ghz=hw.cpu.max_freq_ghz if hw.cpu else None,
|
||||
cpu_cache_l1_kb=hw.cpu.cache_l1_kb if hw.cpu else None,
|
||||
cpu_cache_l2_kb=hw.cpu.cache_l2_kb if hw.cpu else None,
|
||||
cpu_cache_l3_kb=hw.cpu.cache_l3_kb if hw.cpu else None,
|
||||
cpu_flags=json.dumps(hw.cpu.flags) if hw.cpu and hw.cpu.flags else None,
|
||||
cpu_tdp_w=hw.cpu.tdp_w if hw.cpu else None,
|
||||
# Check if we have an existing snapshot for this device
|
||||
existing_snapshot = db.query(HardwareSnapshot).filter(
|
||||
HardwareSnapshot.device_id == device.id
|
||||
).order_by(HardwareSnapshot.captured_at.desc()).first()
|
||||
|
||||
# RAM
|
||||
ram_total_mb=hw.ram.total_mb if hw.ram else None,
|
||||
ram_used_mb=hw.ram.used_mb if hw.ram else None, # NEW
|
||||
ram_free_mb=hw.ram.free_mb if hw.ram else None, # NEW
|
||||
ram_shared_mb=hw.ram.shared_mb if hw.ram else None, # NEW
|
||||
ram_slots_total=hw.ram.slots_total if hw.ram else None,
|
||||
ram_slots_used=hw.ram.slots_used if hw.ram else None,
|
||||
ram_ecc=hw.ram.ecc if hw.ram else None,
|
||||
ram_layout_json=json.dumps([slot.dict() for slot in hw.ram.layout]) if hw.ram and hw.ram.layout else None,
|
||||
# If we have an existing snapshot, update it instead of creating a new one
|
||||
if existing_snapshot:
|
||||
snapshot = existing_snapshot
|
||||
snapshot.captured_at = datetime.utcnow() # Update timestamp
|
||||
else:
|
||||
# Create new snapshot if none exists
|
||||
snapshot = HardwareSnapshot(
|
||||
device_id=device.id,
|
||||
captured_at=datetime.utcnow()
|
||||
)
|
||||
|
||||
# GPU
|
||||
gpu_summary=f"{hw.gpu.vendor} {hw.gpu.model}" if hw.gpu and hw.gpu.model else None,
|
||||
gpu_vendor=hw.gpu.vendor if hw.gpu else None,
|
||||
gpu_model=hw.gpu.model if hw.gpu else None,
|
||||
gpu_driver_version=hw.gpu.driver_version if hw.gpu else None,
|
||||
gpu_memory_dedicated_mb=hw.gpu.memory_dedicated_mb if hw.gpu else None,
|
||||
gpu_memory_shared_mb=hw.gpu.memory_shared_mb if hw.gpu else None,
|
||||
gpu_api_support=json.dumps(hw.gpu.api_support) if hw.gpu and hw.gpu.api_support else None,
|
||||
# Update all fields (whether new or existing snapshot)
|
||||
# CPU
|
||||
snapshot.cpu_vendor = hw.cpu.vendor if hw.cpu else None
|
||||
snapshot.cpu_model = hw.cpu.model if hw.cpu else None
|
||||
snapshot.cpu_microarchitecture = hw.cpu.microarchitecture if hw.cpu else None
|
||||
snapshot.cpu_cores = hw.cpu.cores if hw.cpu else None
|
||||
snapshot.cpu_threads = hw.cpu.threads if hw.cpu else None
|
||||
snapshot.cpu_base_freq_ghz = hw.cpu.base_freq_ghz if hw.cpu else None
|
||||
snapshot.cpu_max_freq_ghz = hw.cpu.max_freq_ghz if hw.cpu else None
|
||||
snapshot.cpu_cache_l1_kb = hw.cpu.cache_l1_kb if hw.cpu else None
|
||||
snapshot.cpu_cache_l2_kb = hw.cpu.cache_l2_kb if hw.cpu else None
|
||||
snapshot.cpu_cache_l3_kb = hw.cpu.cache_l3_kb if hw.cpu else None
|
||||
snapshot.cpu_flags = json.dumps(hw.cpu.flags) if hw.cpu and hw.cpu.flags else None
|
||||
snapshot.cpu_tdp_w = hw.cpu.tdp_w if hw.cpu else None
|
||||
|
||||
# Storage
|
||||
storage_summary=f"{len(hw.storage.devices)} device(s)" if hw.storage and hw.storage.devices else None,
|
||||
storage_devices_json=json.dumps([d.dict() for d in hw.storage.devices]) if hw.storage and hw.storage.devices else None,
|
||||
partitions_json=json.dumps([p.dict() for p in hw.storage.partitions]) if hw.storage and hw.storage.partitions else None,
|
||||
# RAM
|
||||
snapshot.ram_total_mb = hw.ram.total_mb if hw.ram else None
|
||||
snapshot.ram_used_mb = hw.ram.used_mb if hw.ram else None
|
||||
snapshot.ram_free_mb = hw.ram.free_mb if hw.ram else None
|
||||
snapshot.ram_shared_mb = hw.ram.shared_mb if hw.ram else None
|
||||
snapshot.ram_slots_total = hw.ram.slots_total if hw.ram else None
|
||||
snapshot.ram_slots_used = hw.ram.slots_used if hw.ram else None
|
||||
snapshot.ram_ecc = hw.ram.ecc if hw.ram else None
|
||||
snapshot.ram_layout_json = json.dumps([slot.dict() for slot in hw.ram.layout]) if hw.ram and hw.ram.layout else None
|
||||
|
||||
# Network
|
||||
network_interfaces_json=json.dumps([i.dict() for i in hw.network.interfaces]) if hw.network and hw.network.interfaces else None,
|
||||
# GPU
|
||||
snapshot.gpu_summary = f"{hw.gpu.vendor} {hw.gpu.model}" if hw.gpu and hw.gpu.model else None
|
||||
snapshot.gpu_vendor = hw.gpu.vendor if hw.gpu else None
|
||||
snapshot.gpu_model = hw.gpu.model if hw.gpu else None
|
||||
snapshot.gpu_driver_version = hw.gpu.driver_version if hw.gpu else None
|
||||
snapshot.gpu_memory_dedicated_mb = hw.gpu.memory_dedicated_mb if hw.gpu else None
|
||||
snapshot.gpu_memory_shared_mb = hw.gpu.memory_shared_mb if hw.gpu else None
|
||||
snapshot.gpu_api_support = json.dumps(hw.gpu.api_support) if hw.gpu and hw.gpu.api_support else None
|
||||
|
||||
# OS / Motherboard
|
||||
os_name=hw.os.name if hw.os else None,
|
||||
os_version=hw.os.version if hw.os else None,
|
||||
kernel_version=hw.os.kernel_version if hw.os else None,
|
||||
architecture=hw.os.architecture if hw.os else None,
|
||||
virtualization_type=hw.os.virtualization_type if hw.os else None,
|
||||
motherboard_vendor=hw.motherboard.vendor if hw.motherboard else None,
|
||||
motherboard_model=hw.motherboard.model if hw.motherboard else None,
|
||||
bios_version=hw.motherboard.bios_version if hw.motherboard else None,
|
||||
bios_date=hw.motherboard.bios_date if hw.motherboard else None,
|
||||
# Storage
|
||||
snapshot.storage_summary = f"{len(hw.storage.devices)} device(s)" if hw.storage and hw.storage.devices else None
|
||||
snapshot.storage_devices_json = json.dumps([d.dict() for d in hw.storage.devices]) if hw.storage and hw.storage.devices else None
|
||||
snapshot.partitions_json = json.dumps([p.dict() for p in hw.storage.partitions]) if hw.storage and hw.storage.partitions else None
|
||||
|
||||
# Misc
|
||||
sensors_json=json.dumps(hw.sensors.dict()) if hw.sensors else None,
|
||||
raw_info_json=json.dumps(hw.raw_info.dict()) if hw.raw_info else None
|
||||
)
|
||||
# Network
|
||||
snapshot.network_interfaces_json = json.dumps([i.dict() for i in hw.network.interfaces]) if hw.network and hw.network.interfaces else None
|
||||
|
||||
db.add(snapshot)
|
||||
db.flush() # Get snapshot.id
|
||||
# OS / Motherboard
|
||||
snapshot.os_name = hw.os.name if hw.os else None
|
||||
snapshot.os_version = hw.os.version if hw.os else None
|
||||
snapshot.kernel_version = hw.os.kernel_version if hw.os else None
|
||||
snapshot.architecture = hw.os.architecture if hw.os else None
|
||||
snapshot.virtualization_type = hw.os.virtualization_type if hw.os else None
|
||||
snapshot.motherboard_vendor = hw.motherboard.vendor if hw.motherboard else None
|
||||
snapshot.motherboard_model = hw.motherboard.model if hw.motherboard else None
|
||||
snapshot.bios_vendor = hw.motherboard.bios_vendor if hw.motherboard and hasattr(hw.motherboard, 'bios_vendor') else None
|
||||
snapshot.bios_version = hw.motherboard.bios_version if hw.motherboard else None
|
||||
snapshot.bios_date = hw.motherboard.bios_date if hw.motherboard else None
|
||||
|
||||
# Misc
|
||||
snapshot.sensors_json = json.dumps(hw.sensors.dict()) if hw.sensors else None
|
||||
snapshot.raw_info_json = json.dumps(hw.raw_info.dict()) if hw.raw_info else None
|
||||
|
||||
# Add to session only if it's a new snapshot
|
||||
if not existing_snapshot:
|
||||
db.add(snapshot)
|
||||
|
||||
db.flush() # Get snapshot.id for new snapshots
|
||||
|
||||
# 3. Create benchmark
|
||||
results = payload.results
|
||||
|
||||
@@ -11,9 +11,11 @@ from app.db.session import get_db
|
||||
from app.schemas.device import DeviceListResponse, DeviceDetail, DeviceSummary, DeviceUpdate
|
||||
from app.schemas.benchmark import BenchmarkSummary
|
||||
from app.schemas.hardware import HardwareSnapshotResponse
|
||||
from app.schemas.document import DocumentResponse
|
||||
from app.models.device import Device
|
||||
from app.models.benchmark import Benchmark
|
||||
from app.models.hardware_snapshot import HardwareSnapshot
|
||||
from app.models.document import Document
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -160,6 +162,24 @@ async def get_device(
|
||||
motherboard_model=last_snapshot.motherboard_model
|
||||
)
|
||||
|
||||
# Get documents for this device
|
||||
documents = db.query(Document).filter(
|
||||
Document.device_id == device.id
|
||||
).all()
|
||||
|
||||
documents_list = [
|
||||
DocumentResponse(
|
||||
id=doc.id,
|
||||
device_id=doc.device_id,
|
||||
doc_type=doc.doc_type,
|
||||
filename=doc.filename,
|
||||
mime_type=doc.mime_type,
|
||||
size_bytes=doc.size_bytes,
|
||||
uploaded_at=doc.uploaded_at.isoformat()
|
||||
)
|
||||
for doc in documents
|
||||
]
|
||||
|
||||
return DeviceDetail(
|
||||
id=device.id,
|
||||
hostname=device.hostname,
|
||||
@@ -172,7 +192,8 @@ async def get_device(
|
||||
created_at=device.created_at.isoformat(),
|
||||
updated_at=device.updated_at.isoformat(),
|
||||
last_benchmark=last_bench_summary,
|
||||
last_hardware_snapshot=last_snapshot_data
|
||||
last_hardware_snapshot=last_snapshot_data,
|
||||
documents=documents_list
|
||||
)
|
||||
|
||||
|
||||
@@ -246,7 +267,9 @@ async def update_device(
|
||||
for key, value in update_dict.items():
|
||||
setattr(device, key, value)
|
||||
|
||||
device.updated_at = db.query(Device).filter(Device.id == device_id).first().updated_at
|
||||
# Update timestamp
|
||||
from datetime import datetime
|
||||
device.updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
db.refresh(device)
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
Linux BenchTools - Main Application
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from app.db.init_db import init_db
|
||||
from app.db.session import get_db
|
||||
from app.api import benchmark, devices, links, docs
|
||||
|
||||
|
||||
@@ -68,37 +70,28 @@ async def health_check():
|
||||
|
||||
# Stats endpoint (for dashboard)
|
||||
@app.get(f"{settings.API_PREFIX}/stats")
|
||||
async def get_stats():
|
||||
async def get_stats(db: Session = Depends(get_db)):
|
||||
"""Get global statistics"""
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.session import get_db
|
||||
from app.models.device import Device
|
||||
from app.models.benchmark import Benchmark
|
||||
from sqlalchemy import func
|
||||
|
||||
db: Session = next(get_db())
|
||||
total_devices = db.query(Device).count()
|
||||
total_benchmarks = db.query(Benchmark).count()
|
||||
|
||||
try:
|
||||
total_devices = db.query(Device).count()
|
||||
total_benchmarks = db.query(Benchmark).count()
|
||||
# Get average score
|
||||
avg_score = db.query(func.avg(Benchmark.global_score)).scalar()
|
||||
|
||||
# Get average score
|
||||
avg_score = db.query(Benchmark).with_entities(
|
||||
db.func.avg(Benchmark.global_score)
|
||||
).scalar()
|
||||
# Get last benchmark date
|
||||
last_bench = db.query(Benchmark).order_by(Benchmark.run_at.desc()).first()
|
||||
last_bench_date = last_bench.run_at.isoformat() if last_bench else None
|
||||
|
||||
# Get last benchmark date
|
||||
last_bench = db.query(Benchmark).order_by(Benchmark.run_at.desc()).first()
|
||||
last_bench_date = last_bench.run_at.isoformat() if last_bench else None
|
||||
|
||||
return {
|
||||
"total_devices": total_devices,
|
||||
"total_benchmarks": total_benchmarks,
|
||||
"avg_global_score": round(avg_score, 2) if avg_score else 0,
|
||||
"last_benchmark_at": last_bench_date
|
||||
}
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
return {
|
||||
"total_devices": total_devices,
|
||||
"total_benchmarks": total_benchmarks,
|
||||
"avg_global_score": round(avg_score, 2) if avg_score else 0,
|
||||
"last_benchmark_at": last_bench_date
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -67,6 +67,7 @@ class HardwareSnapshot(Base):
|
||||
virtualization_type = Column(String(50), nullable=True)
|
||||
motherboard_vendor = Column(String(100), nullable=True)
|
||||
motherboard_model = Column(String(255), nullable=True)
|
||||
bios_vendor = Column(String(100), nullable=True)
|
||||
bios_version = Column(String(100), nullable=True)
|
||||
bios_date = Column(String(50), nullable=True)
|
||||
|
||||
|
||||
@@ -9,41 +9,41 @@ from app.schemas.hardware import HardwareData
|
||||
|
||||
class CPUResults(BaseModel):
|
||||
"""CPU benchmark results"""
|
||||
events_per_sec: Optional[float] = None
|
||||
duration_s: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
events_per_sec: Optional[float] = Field(None, ge=0)
|
||||
duration_s: Optional[float] = Field(None, ge=0)
|
||||
score: Optional[float] = Field(None, ge=0, le=10000)
|
||||
|
||||
|
||||
class MemoryResults(BaseModel):
|
||||
"""Memory benchmark results"""
|
||||
throughput_mib_s: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
throughput_mib_s: Optional[float] = Field(None, ge=0)
|
||||
score: Optional[float] = Field(None, ge=0, le=10000)
|
||||
|
||||
|
||||
class DiskResults(BaseModel):
|
||||
"""Disk benchmark results"""
|
||||
read_mb_s: Optional[float] = None
|
||||
write_mb_s: Optional[float] = None
|
||||
iops_read: Optional[int] = None
|
||||
iops_write: Optional[int] = None
|
||||
latency_ms: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
read_mb_s: Optional[float] = Field(None, ge=0)
|
||||
write_mb_s: Optional[float] = Field(None, ge=0)
|
||||
iops_read: Optional[int] = Field(None, ge=0)
|
||||
iops_write: Optional[int] = Field(None, ge=0)
|
||||
latency_ms: Optional[float] = Field(None, ge=0)
|
||||
score: Optional[float] = Field(None, ge=0, le=10000)
|
||||
|
||||
|
||||
class NetworkResults(BaseModel):
|
||||
"""Network benchmark results"""
|
||||
upload_mbps: Optional[float] = None
|
||||
download_mbps: Optional[float] = None
|
||||
ping_ms: Optional[float] = None
|
||||
jitter_ms: Optional[float] = None
|
||||
packet_loss_percent: Optional[float] = None
|
||||
score: Optional[float] = None
|
||||
upload_mbps: Optional[float] = Field(None, ge=0)
|
||||
download_mbps: Optional[float] = Field(None, ge=0)
|
||||
ping_ms: Optional[float] = Field(None, ge=0)
|
||||
jitter_ms: Optional[float] = Field(None, ge=0)
|
||||
packet_loss_percent: Optional[float] = Field(None, ge=0, le=100)
|
||||
score: Optional[float] = Field(None, ge=0, le=10000)
|
||||
|
||||
|
||||
class GPUResults(BaseModel):
|
||||
"""GPU benchmark results"""
|
||||
glmark2_score: Optional[int] = None
|
||||
score: Optional[float] = None
|
||||
glmark2_score: Optional[int] = Field(None, ge=0)
|
||||
score: Optional[float] = Field(None, ge=0, le=10000)
|
||||
|
||||
|
||||
class BenchmarkResults(BaseModel):
|
||||
@@ -53,7 +53,7 @@ class BenchmarkResults(BaseModel):
|
||||
disk: Optional[DiskResults] = None
|
||||
network: Optional[NetworkResults] = None
|
||||
gpu: Optional[GPUResults] = None
|
||||
global_score: float = Field(..., ge=0, le=100, description="Global score (0-100)")
|
||||
global_score: float = Field(..., ge=0, le=10000, description="Global score (0-10000)")
|
||||
|
||||
|
||||
class BenchmarkPayload(BaseModel):
|
||||
|
||||
@@ -6,6 +6,7 @@ from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from app.schemas.benchmark import BenchmarkSummary
|
||||
from app.schemas.hardware import HardwareSnapshotResponse
|
||||
from app.schemas.document import DocumentResponse
|
||||
|
||||
|
||||
class DeviceBase(BaseModel):
|
||||
@@ -53,6 +54,7 @@ class DeviceDetail(DeviceBase):
|
||||
updated_at: str
|
||||
last_benchmark: Optional[BenchmarkSummary] = None
|
||||
last_hardware_snapshot: Optional[HardwareSnapshotResponse] = None
|
||||
documents: List[DocumentResponse] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -89,6 +89,7 @@ class NetworkInterface(BaseModel):
|
||||
ip: Optional[str] = None
|
||||
speed_mbps: Optional[int] = None
|
||||
driver: Optional[str] = None
|
||||
wake_on_lan: Optional[bool] = None
|
||||
|
||||
|
||||
class NetworkInfo(BaseModel):
|
||||
@@ -100,6 +101,7 @@ class MotherboardInfo(BaseModel):
|
||||
"""Motherboard information schema"""
|
||||
vendor: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
bios_vendor: Optional[str] = None
|
||||
bios_version: Optional[str] = None
|
||||
bios_date: Optional[str] = None
|
||||
|
||||
|
||||
9
backend/migrations/add_bios_vendor.sql
Normal file
9
backend/migrations/add_bios_vendor.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- Migration: Add bios_vendor column to hardware_snapshots
|
||||
-- Date: 2025-12-14
|
||||
-- Description: Add missing bios_vendor field to store BIOS manufacturer information
|
||||
|
||||
-- Add bios_vendor column (nullable)
|
||||
ALTER TABLE hardware_snapshots ADD COLUMN bios_vendor VARCHAR(100);
|
||||
|
||||
-- Note: No need to populate existing rows since the field was not collected before
|
||||
-- Future benchmarks will populate this field automatically
|
||||
Reference in New Issue
Block a user