mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 23:34:24 +01:00
* feat: win compatibility * fix * fix: uptime parse * opt.: linux uptime accuracy * fix: windows temperature fetching * opt. * opt.: powershell exec * refactor: address PR review feedback and improve code quality ### Major Improvements: - **Refactored Windows status parsing**: Broke down large `_getWindowsStatus` method into 13 smaller, focused helper methods for better maintainability and readability - **Extracted system detection logic**: Created dedicated `SystemDetector` helper class to separate OS detection concerns from ServerProvider - **Improved concurrency handling**: Implemented proper synchronization for server updates using Future-based locks to prevent race conditions ### Bug Fixes: - **Fixed CPU percentage parsing**: Removed incorrect '*100' multiplication in BSD CPU parsing (values were already percentages) - **Enhanced memory parsing**: Added validation and error handling to BSD memory fallback parsing with proper logging - **Improved uptime parsing**: Added support for multiple Windows date formats and robust error handling with validation - **Fixed division by zero**: Added safety checks in Swap.usedPercent getter ### Code Quality Enhancements: - **Added comprehensive documentation**: Documented Windows CPU counter limitations and approach - **Strengthened error handling**: Added detailed logging and validation throughout parsing methods - **Improved robustness**: Enhanced BSD CPU parsing with percentage validation and warnings - **Better separation of concerns**: Each parsing method now has single responsibility ### Files Changed: - `lib/data/helper/system_detector.dart` (new): System detection helper - `lib/data/model/server/cpu.dart`: Fixed percentage parsing and added validation - `lib/data/model/server/memory.dart`: Enhanced fallback parsing and division-by-zero protection - `lib/data/model/server/server_status_update_req.dart`: Refactored into 13 focused parsing methods - `lib/data/provider/server.dart`: Improved synchronization and extracted system detection 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: parse & shell fn struct --------- Co-authored-by: Claude <noreply@anthropic.com>
189 lines
5.1 KiB
Dart
189 lines
5.1 KiB
Dart
import 'dart:convert';
|
|
|
|
/// AMD GPU monitoring data structures
|
|
/// Supports both amd-smi and rocm-smi tools
|
|
/// Example JSON output:
|
|
/// [
|
|
/// {
|
|
/// "name": "AMD Radeon RX 7900 XTX",
|
|
/// "device_id": "0",
|
|
/// "temp": 45,
|
|
/// "power": "120W / 355W",
|
|
/// "memory": {
|
|
/// "total": 24576,
|
|
/// "used": 1024,
|
|
/// "unit": "MB",
|
|
/// "processes": [
|
|
/// {
|
|
/// "pid": 2456,
|
|
/// "name": "firefox",
|
|
/// "memory": 512
|
|
/// }
|
|
/// ]
|
|
/// },
|
|
/// "utilization": 75,
|
|
/// "fan_speed": 1200,
|
|
/// "clock_speed": 2400
|
|
/// }
|
|
/// ]
|
|
|
|
class AmdSmi {
|
|
static List<AmdSmiItem> fromJson(String raw) {
|
|
try {
|
|
final jsonData = json.decode(raw);
|
|
if (jsonData is! List) return [];
|
|
|
|
return jsonData
|
|
.map((gpu) => _parseGpuItem(gpu))
|
|
.where((item) => item != null)
|
|
.cast<AmdSmiItem>()
|
|
.toList();
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
static AmdSmiItem? _parseGpuItem(Map<String, dynamic> gpu) {
|
|
try {
|
|
final name = gpu['name'] ?? gpu['card_model'] ?? gpu['device_name'] ?? 'Unknown AMD GPU';
|
|
final deviceId = gpu['device_id']?.toString() ?? gpu['gpu_id']?.toString() ?? '0';
|
|
|
|
// Temperature parsing
|
|
final tempRaw = gpu['temperature'] ?? gpu['temp'] ?? gpu['gpu_temp'];
|
|
final temp = _parseIntValue(tempRaw);
|
|
|
|
// Power parsing
|
|
final powerDraw = gpu['power_draw'] ?? gpu['current_power'];
|
|
final powerCap = gpu['power_cap'] ?? gpu['power_limit'] ?? gpu['max_power'];
|
|
final power = _formatPower(powerDraw, powerCap);
|
|
|
|
// Memory parsing
|
|
final memory = _parseMemory(gpu['memory'] ?? gpu['vram'] ?? {});
|
|
|
|
// Utilization parsing
|
|
final utilization = _parseIntValue(gpu['utilization'] ?? gpu['gpu_util'] ?? gpu['activity']);
|
|
|
|
// Fan speed parsing
|
|
final fanSpeed = _parseIntValue(gpu['fan_speed'] ?? gpu['fan_rpm']);
|
|
|
|
// Clock speed parsing
|
|
final clockSpeed = _parseIntValue(gpu['clock_speed'] ?? gpu['gpu_clock'] ?? gpu['sclk']);
|
|
|
|
return AmdSmiItem(
|
|
deviceId: deviceId,
|
|
name: name,
|
|
temp: temp,
|
|
power: power,
|
|
memory: memory,
|
|
utilization: utilization,
|
|
fanSpeed: fanSpeed,
|
|
clockSpeed: clockSpeed,
|
|
);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static int _parseIntValue(dynamic value) {
|
|
if (value == null) return 0;
|
|
if (value is int) return value;
|
|
if (value is String) {
|
|
// Remove units and parse (e.g., "45°C" -> 45, "1200 RPM" -> 1200)
|
|
final cleanValue = value.replaceAll(RegExp(r'[^\d]'), '');
|
|
return int.tryParse(cleanValue) ?? 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static String _formatPower(dynamic draw, dynamic cap) {
|
|
final drawValue = _parseIntValue(draw);
|
|
final capValue = _parseIntValue(cap);
|
|
|
|
if (drawValue == 0 && capValue == 0) return 'N/A';
|
|
if (capValue == 0) return '${drawValue}W';
|
|
return '${drawValue}W / ${capValue}W';
|
|
}
|
|
|
|
static AmdSmiMem _parseMemory(Map<String, dynamic> memData) {
|
|
final total = _parseIntValue(memData['total'] ?? memData['total_memory']);
|
|
final used = _parseIntValue(memData['used'] ?? memData['used_memory']);
|
|
final unit = memData['unit']?.toString() ?? 'MB';
|
|
|
|
final processes = <AmdSmiMemProcess>[];
|
|
final processesData = memData['processes'];
|
|
if (processesData is List) {
|
|
for (final proc in processesData) {
|
|
if (proc is Map<String, dynamic>) {
|
|
final process = _parseProcess(proc);
|
|
if (process != null) processes.add(process);
|
|
}
|
|
}
|
|
}
|
|
|
|
return AmdSmiMem(total, used, unit, processes);
|
|
}
|
|
|
|
static AmdSmiMemProcess? _parseProcess(Map<String, dynamic> procData) {
|
|
final pid = _parseIntValue(procData['pid']);
|
|
final name = procData['name']?.toString() ?? procData['process_name']?.toString() ?? 'Unknown';
|
|
final memory = _parseIntValue(procData['memory'] ?? procData['used_memory']);
|
|
|
|
if (pid == 0) return null;
|
|
return AmdSmiMemProcess(pid, name, memory);
|
|
}
|
|
}
|
|
|
|
class AmdSmiItem {
|
|
final String deviceId;
|
|
final String name;
|
|
final int temp;
|
|
final String power;
|
|
final AmdSmiMem memory;
|
|
final int utilization;
|
|
final int fanSpeed;
|
|
final int clockSpeed;
|
|
|
|
const AmdSmiItem({
|
|
required this.deviceId,
|
|
required this.name,
|
|
required this.temp,
|
|
required this.power,
|
|
required this.memory,
|
|
required this.utilization,
|
|
required this.fanSpeed,
|
|
required this.clockSpeed,
|
|
});
|
|
|
|
@override
|
|
String toString() {
|
|
return 'AmdSmiItem{name: $name, temp: $temp, power: $power, utilization: $utilization%, memory: $memory}';
|
|
}
|
|
}
|
|
|
|
class AmdSmiMem {
|
|
final int total;
|
|
final int used;
|
|
final String unit;
|
|
final List<AmdSmiMemProcess> processes;
|
|
|
|
const AmdSmiMem(this.total, this.used, this.unit, this.processes);
|
|
|
|
@override
|
|
String toString() {
|
|
return 'AmdSmiMem{total: $total, used: $used, unit: $unit, processes: ${processes.length}}';
|
|
}
|
|
}
|
|
|
|
class AmdSmiMemProcess {
|
|
final int pid;
|
|
final String name;
|
|
final int memory;
|
|
|
|
const AmdSmiMemProcess(this.pid, this.name, this.memory);
|
|
|
|
@override
|
|
String toString() {
|
|
return 'AmdSmiMemProcess{pid: $pid, name: $name, memory: $memory}';
|
|
}
|
|
}
|