feat: Windows compatibility (#836)

* 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>
This commit is contained in:
lollipopkit🏳️‍⚧️
2025-08-08 16:56:36 +08:00
committed by GitHub
parent 46a12bc844
commit 3a615449e3
103 changed files with 9591 additions and 1906 deletions

View File

@@ -313,7 +313,7 @@ void main() {
}
]
''';
final gpu = AmdSmi.fromJson(jsonWithInvalidProcess)[0];
expect(gpu.memory.processes.length, 1);
expect(gpu.memory.processes[0].pid, 1234);
@@ -409,4 +409,4 @@ void main() {
expect(gpus[0].clockSpeed, 0);
});
});
}
}

View File

@@ -9,25 +9,25 @@ void main() {
final disks = Disk.parse(_btrfsRaidJsonOutput);
expect(disks, isNotEmpty);
expect(disks.length, 4); // Should have 2 parent disks + 2 BTRFS partitions
// We should get two distinct disks with the same UUID but different paths
final nvme1Disk = disks.firstWhere((disk) => disk.path == '/dev/nvme1n1p1');
final nvme2Disk = disks.firstWhere((disk) => disk.path == '/dev/nvme2n1p1');
// Both should exist
expect(nvme1Disk, isNotNull);
expect(nvme2Disk, isNotNull);
// They should have the same UUID (since they're part of the same BTRFS volume)
expect(nvme1Disk.uuid, nvme2Disk.uuid);
// But they should be treated as distinct disks
expect(identical(nvme1Disk, nvme2Disk), isFalse);
// Verify DiskUsage counts physical disks correctly
final usage = DiskUsage.parse(disks);
// With our unique path+kname identifier, both disks should be counted
expect(usage.size, nvme1Disk.size + nvme2Disk.size);
expect(usage.size, nvme1Disk.size + nvme2Disk.size);
expect(usage.used, nvme1Disk.used + nvme2Disk.used);
});
});

View File

@@ -15,7 +15,7 @@ fa1215b4be74 Up 12 hours firefly
const images = [
'rustdesk/rustdesk-server:latest',
'rustdesk/rustdesk-server:latest',
'uusec/firefly:latest'
'uusec/firefly:latest',
];
const states = ['Up 2 hours', 'Up 41 minutes', 'Up 12 hours'];
for (var idx = 1; idx < lines.length; idx++) {

View File

@@ -11,12 +11,12 @@ void main() {
expect(disks, isNotEmpty);
}
});
test('parse lsblk JSON output', () {
final disks = Disk.parse(_jsonLsblkOutput);
expect(disks, isNotEmpty);
expect(disks.length, 6); // Should find ext4 root, vfat efi, and ext2 boot
expect(disks.length, 6); // Should find ext4 root, vfat efi, and ext2 boot
// Verify root filesystem
final rootFs = disks.firstWhere((disk) => disk.mount == '/');
expect(rootFs.fsTyp, 'ext4');
@@ -24,44 +24,44 @@ void main() {
expect(rootFs.used, BigInt.parse('552718364672') ~/ BigInt.from(1024));
expect(rootFs.avail, BigInt.parse('379457622016') ~/ BigInt.from(1024));
expect(rootFs.usedPercent, 56);
// Verify boot/efi filesystem
final efiFs = disks.firstWhere((disk) => disk.mount == '/boot/efi');
expect(efiFs.fsTyp, 'vfat');
expect(efiFs.size, BigInt.parse('535805952') ~/ BigInt.from(1024));
expect(efiFs.usedPercent, 1);
// Verify boot filesystem
final bootFs = disks.firstWhere((disk) => disk.mount == '/boot');
expect(bootFs.fsTyp, 'ext2');
expect(bootFs.usedPercent, 34);
});
test('parse nested lsblk JSON output with parent/child relationships', () {
final disks = Disk.parse(_nestedJsonLsblkOutput);
expect(disks, isNotEmpty);
// Check parent device with children
final parentDisk = disks.firstWhere((disk) => disk.path == '/dev/nvme0n1');
expect(parentDisk.children, isNotEmpty);
expect(parentDisk.children.length, 3);
// Check one of the children
final rootPartition = parentDisk.children.firstWhere((disk) => disk.mount == '/');
expect(rootPartition.fsTyp, 'ext4');
expect(rootPartition.path, '/dev/nvme0n1p2');
expect(rootPartition.usedPercent, 45);
// Verify we have a child partition with UUID
final bootPartition = parentDisk.children.firstWhere((disk) => disk.mount == '/boot');
expect(bootPartition.uuid, '12345678-abcd-1234-abcd-1234567890ab');
});
test('DiskUsage handles zero size correctly', () {
final usage = DiskUsage(used: BigInt.from(1000), size: BigInt.zero);
expect(usage.usedPercent, 0); // Should return 0 instead of throwing
});
test('DiskUsage handles null kname', () {
final disks = [
Disk(
@@ -74,7 +74,7 @@ void main() {
kname: null, // Explicitly null kname
),
];
final usage = DiskUsage.parse(disks);
expect(usage.used, BigInt.from(5000));
expect(usage.size, BigInt.from(10000));
@@ -198,16 +198,16 @@ const _nestedJsonLsblkOutput = '''
''';
const _raws = [
// '''
// Filesystem 1K-blocks Used Available Use% Mounted on
// udev 864088 0 864088 0% /dev
// tmpfs 176724 688 176036 1% /run
// /dev/vda3 40910528 18067948 20951380 47% /
// tmpfs 883612 0 883612 0% /dev/shm
// tmpfs 5120 0 5120 0% /run/lock
// /dev/vda2 192559 11807 180752 7% /boot/efi
// tmpfs 176720 104 176616 1% /run/user/1000
// ''',
// '''
// Filesystem 1K-blocks Used Available Use% Mounted on
// udev 864088 0 864088 0% /dev
// tmpfs 176724 688 176036 1% /run
// /dev/vda3 40910528 18067948 20951380 47% /
// tmpfs 883612 0 883612 0% /dev/shm
// tmpfs 5120 0 5120 0% /run/lock
// /dev/vda2 192559 11807 180752 7% /boot/efi
// tmpfs 176720 104 176616 1% /run/user/1000
// ''',
'''
Filesystem 1K-blocks Used Available Use% Mounted on
udev 16181648 0 16181648 0% /dev

View File

@@ -113,15 +113,12 @@ void main() {
SensorAdaptor.virtual,
SensorAdaptor.pci,
]);
expect(
sensors.map((e) => e.summary),
[
'+56.0°C (high = +105.0°C, crit = +105.0°C)',
'+27.8°C (crit = +119.0°C)',
'+56.0°C',
'+45.9°C (low = -273.1°C, high = +83.8°C)',
],
);
expect(sensors.map((e) => e.summary), [
'+56.0°C (high = +105.0°C, crit = +105.0°C)',
'+27.8°C (crit = +119.0°C)',
'+56.0°C',
'+45.9°C (low = -273.1°C, high = +83.8°C)',
]);
});
test('parse sensors2', () {
@@ -138,14 +135,11 @@ void main() {
SensorAdaptor.pci,
SensorAdaptor.pci,
]);
expect(
sensors.map((e) => e.summary),
[
'1.26 V',
'1.19 V (min = +0.00 V, max = +1.74 V)',
'+45.9°C (low = -273.1°C, high = +69.8°C)',
'+44.9°C',
],
);
expect(sensors.map((e) => e.summary), [
'1.26 V',
'1.19 V (min = +0.00 V, max = +1.74 V)',
'+45.9°C (low = -273.1°C, high = +69.8°C)',
'+44.9°C',
]);
});
}

87
test/uptime_test.dart Normal file
View File

@@ -0,0 +1,87 @@
import 'package:flutter_test/flutter_test.dart';
void main() {
group('Linux uptime parsing tests', () {
test('should parse uptime with days and hours:minutes', () {
const raw = '19:39:15 up 61 days, 18:16, 1 user, load average: 0.00, 0.00, 0.00';
final result = _testParseUpTime(raw);
expect(result, '61 days, 18:16');
});
test('should parse uptime with single day and hours:minutes', () {
const raw = '19:39:15 up 1 day, 2:34, 1 user, load average: 0.00, 0.00, 0.00';
final result = _testParseUpTime(raw);
expect(result, '1 day, 2:34');
});
test('should parse uptime with only hours:minutes', () {
const raw = '19:39:15 up 2:34, 1 user, load average: 0.00, 0.00, 0.00';
final result = _testParseUpTime(raw);
expect(result, '2:34');
});
test('should parse uptime with only minutes', () {
const raw = '19:39:15 up 34 min, 1 user, load average: 0.00, 0.00, 0.00';
final result = _testParseUpTime(raw);
expect(result, '34 min');
});
test('should parse uptime with days only (no time part)', () {
const raw = '19:39:15 up 5 days, 1 user, load average: 0.00, 0.00, 0.00';
final result = _testParseUpTime(raw);
expect(result, '5 days');
});
test('should return null for invalid format', () {
const raw = 'invalid uptime format';
final result = _testParseUpTime(raw);
expect(result, null);
});
test('should handle edge case with empty string', () {
const raw = '';
final result = _testParseUpTime(raw);
expect(result, null);
});
});
}
// Helper function to test the private _parseUpTime function
String? _testParseUpTime(String raw) {
final splitedUp = raw.split('up ');
if (splitedUp.length == 2) {
final uptimePart = splitedUp[1];
final splitedComma = uptimePart.split(', ');
if (splitedComma.isEmpty) return null;
// Handle different uptime formats
final firstPart = splitedComma[0].trim();
// Case 1: "61 days" or "1 day" - need to get the time part from next segment
if (firstPart.contains('day')) {
if (splitedComma.length >= 2) {
final timePart = splitedComma[1].trim();
// Check if it's in HH:MM format
if (timePart.contains(':') && !timePart.contains('user') && !timePart.contains('load')) {
return '$firstPart, $timePart';
}
}
return firstPart;
}
// Case 2: "2:34" (hours:minutes) - already in good format
if (firstPart.contains(':') && !firstPart.contains('user') && !firstPart.contains('load')) {
return firstPart;
}
// Case 3: "34 min" - already in good format
if (firstPart.contains('min')) {
return firstPart;
}
// Fallback: return first part
return firstPart;
}
return null;
}

473
test/windows_test.dart Normal file
View File

@@ -0,0 +1,473 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/server_status_update_req.dart';
import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/res/status.dart';
void main() {
group('Windows System Tests', () {
test('should verify Windows segments length matches command types', () {
final systemType = SystemType.windows;
final expectedLength = WindowsStatusCmdType.values.length;
expect(systemType.segmentsLen, equals(expectedLength));
expect(systemType.isSegmentsLenMatch(expectedLength), isTrue);
});
test('should generate Windows PowerShell script correctly', () {
final script = ShellFunc.allScript({'custom_cmd': 'echo "test"'}, systemType: SystemType.windows);
expect(script, contains('PowerShell script for ServerBox'));
expect(script, contains('function SbStatus'));
expect(script, contains('function SbProcess'));
expect(script, contains('function SbShutdown'));
expect(script, contains('function SbReboot'));
expect(script, contains('function SbSuspend'));
expect(script, contains('switch (\$args[0])'));
expect(script, contains('"-s" { SbStatus }'));
expect(script, contains('echo "test"'));
});
test('should handle Windows system parsing with real data', () async {
final segments = _windowsStatusSegments;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
// Verify system information was parsed
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
expect(result.more[StatusCmdType.host], equals('LKH6'));
// Verify CPU information
expect(result.cpu.now, isNotEmpty);
expect(result.cpu.brand.keys.first, contains('12th Gen Intel(R) Core(TM) i5-12490F'));
// Verify memory information
expect(result.mem, isNotNull);
expect(result.mem.total, equals(66943944));
expect(result.mem.free, equals(58912812));
// Verify disk information
expect(result.disk, isNotEmpty);
final cDrive = result.disk.firstWhere((disk) => disk.path == 'C:');
expect(cDrive.fsTyp, equals('NTFS'));
expect(cDrive.size, equals(BigInt.parse('999271952384') ~/ BigInt.from(1024)));
expect(cDrive.avail, equals(BigInt.parse('386084032512') ~/ BigInt.from(1024)));
// Verify TCP connections
expect(result.tcp, isNotNull);
expect(result.tcp.active, equals(2));
});
test('should parse Windows CPU data correctly', () async {
const cpuJson = '''
{
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
"LoadPercentage": 42
}
''';
final segments = ['__windows', '1754151483', '', '', cpuJson];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.cpu.now, hasLength(1));
expect(result.cpu.now.first.user, equals(42));
expect(result.cpu.now.first.idle, equals(58));
});
test('should parse Windows memory data correctly', () async {
const memoryJson = '''
{
"TotalVisibleMemorySize": 66943944,
"FreePhysicalMemory": 58912812
}
''';
final segments = ['__windows', '1754151483', '', '', '', '', '', '', memoryJson];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.mem, isNotNull);
expect(result.mem.total, equals(66943944));
expect(result.mem.free, equals(58912812));
expect(result.mem.avail, equals(58912812));
});
test('should parse Windows disk data correctly', () async {
const diskJson = '''
{
"DeviceID": "C:",
"Size": 999271952384,
"FreeSpace": 386084032512,
"FileSystem": "NTFS"
}
''';
final segments = ['__windows', '1754151483', '', '', '', '', '', diskJson];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.disk, hasLength(1));
final disk = result.disk.first;
expect(disk.path, equals('C:'));
expect(disk.mount, equals('C:'));
expect(disk.fsTyp, equals('NTFS'));
expect(disk.size, equals(BigInt.parse('999271952384') ~/ BigInt.from(1024)));
expect(disk.avail, equals(BigInt.parse('386084032512') ~/ BigInt.from(1024)));
expect(disk.usedPercent, equals(61));
});
test('should parse Windows battery data correctly', () async {
const batteryJson = '''
{
"EstimatedChargeRemaining": 85,
"BatteryStatus": 6
}
''';
// Create segments with enough elements to reach battery position
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.battery.index] = batteryJson;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.batteries, hasLength(1));
final battery = result.batteries.first;
expect(battery.name, equals('Battery'));
expect(battery.percent, equals(85));
expect(battery.status.name, equals('charging'));
});
test('should handle Windows uptime parsing correctly', () async {
// Test new format with date line + uptime days
const uptimeNewFormat = 'Friday, July 25, 2025 2:26:42 PM\n2';
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.uptime.index] = uptimeNewFormat;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.more[StatusCmdType.uptime], isNotNull);
});
test('should handle Windows uptime parsing with old format', () async {
const uptimeDateTime = 'Friday, July 25, 2025 2:26:42 PM';
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.uptime.index] = uptimeDateTime;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
expect(result.more[StatusCmdType.uptime], isNotNull);
});
test('should handle Windows script path generation', () {
const serverId = 'test-server';
final scriptPath = ShellFunc.getScriptPath(serverId, systemType: SystemType.windows);
expect(scriptPath, contains('.ps1'));
expect(scriptPath, contains('\\'));
final installCmd = ShellFunc.getInstallShellCmd(serverId, systemType: SystemType.windows);
expect(installCmd, contains('New-Item'));
expect(installCmd, contains('Set-Content'));
// No longer contains 'powershell' prefix as commands now run in PowerShell session
});
test('should execute Windows commands correctly', () {
const serverId = 'test-server';
final statusCmd = ShellFunc.status.exec(serverId, systemType: SystemType.windows);
expect(statusCmd, contains('powershell'));
expect(statusCmd, contains('-ExecutionPolicy Bypass'));
expect(statusCmd, contains('-s'));
final processCmd = ShellFunc.process.exec(serverId, systemType: SystemType.windows);
expect(processCmd, contains('powershell'));
expect(processCmd, contains('-p'));
});
test('should handle GPU detection on Windows', () async {
const nvidiaNotFound = 'NVIDIA driver not found';
const amdNotFound = 'AMD driver not found';
final segments = List.filled(WindowsStatusCmdType.values.length, '');
segments[0] = '__windows';
segments[WindowsStatusCmdType.nvidia.index] = nvidiaNotFound;
segments[WindowsStatusCmdType.amd.index] = amdNotFound;
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
final result = await getStatus(req);
// Should not throw errors even when GPU drivers are not found
expect(result.nvidia, anyOf(isNull, isEmpty));
expect(result.amd, anyOf(isNull, isEmpty));
});
test('should handle Windows error conditions gracefully', () async {
// Test with malformed JSON and error messages
final segments = [
'__windows',
'1754151483',
'Network adapter error',
'Microsoft Windows 11 Pro for Workstations',
'invalid json {',
'uptime error',
'connection error',
'disk error',
'memory error',
'temp error',
'LKH6',
'diskio error',
'battery error',
'NVIDIA driver not found',
'AMD driver not found',
'sensor error',
'smart error',
'12th Gen Intel(R) Core(TM) i5-12490F',
];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
// Should not throw exceptions
expect(() async => await getStatus(req), returnsNormally);
final result = await getStatus(req);
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
expect(result.more[StatusCmdType.host], equals('LKH6'));
});
test('should handle Windows temperature error output gracefully', () async {
// Test with actual error output from win_raw.txt
final segments = [
'__windows',
'1754151483',
'', // network
'Microsoft Windows 11 Pro for Workstations', // system
'''
{
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
"LoadPercentage": 42
}
''', // cpu
'Friday, July 25, 2025 2:26:42 PM', // uptime
'2', // connections
'''
{
"DeviceID": "C:",
"Size": 999271952384,
"FreeSpace": 386084032512,
"FileSystem": "NTFS"
}
''', // disk
'''
{
"TotalVisibleMemorySize": 66943944,
"FreePhysicalMemory": 58912812
}
''', // memory
'''
The string is missing the terminator: ".
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
''', // temp (error output)
'LKH6', // host
'', // diskio
'', // battery
'NVIDIA driver not found', // nvidia
'AMD driver not found', // amd
'', // sensors
'''
{
"DeviceId": "0",
"Temperature": 41,
"TemperatureMax": 70,
"Wear": 0,
"PowerOnHours": null
}
''', // smart
'12th Gen Intel(R) Core(TM) i5-12490F', // cpu brand
];
final serverStatus = InitStatus.status;
final req = ServerStatusUpdateReq(
system: SystemType.windows,
ss: serverStatus,
segments: segments,
customCmds: {},
);
// Should not throw exceptions even with error output in temperature values
expect(() async => await getStatus(req), returnsNormally);
final result = await getStatus(req);
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
expect(result.more[StatusCmdType.host], equals('LKH6'));
// Temperature should be empty since we got error output
expect(result.temps.isEmpty, isTrue);
});
});
}
// Sample Windows status segments based on real PowerShell output
final _windowsStatusSegments = [
'__windows', // System type marker
'1754151483', // Unix timestamp
'', // Network data (empty for now)
'Microsoft Windows 11 Pro for Workstations', // System name
'''
{
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
"LoadPercentage": 42
}
''', // CPU data
'Friday, July 25, 2025 2:26:42 PM', // Uptime (boot time)
'2', // Connection count
'''
{
"DeviceID": "C:",
"Size": 999271952384,
"FreeSpace": 386084032512,
"FileSystem": "NTFS"
}
''', // Disk data
'''
{
"TotalVisibleMemorySize": 66943944,
"FreePhysicalMemory": 58912812
}
''', // Memory data
'', // Temperature (combined command - empty due to OpenHardwareMonitor error)
'LKH6', // Hostname
'', // Disk I/O (empty for now)
'', // Battery data (empty)
'NVIDIA driver not found', // NVIDIA GPU
'AMD driver not found', // AMD GPU
'', // Sensors (empty due to OpenHardwareMonitor error)
'''
{
"CimClass": {
"CimSuperClassName": "MSFT_StorageObject",
"CimSuperClass": {
"CimSuperClassName": null,
"CimSuperClass": null,
"CimClassProperties": "ObjectId PassThroughClass PassThroughIds PassThroughNamespace PassThroughServer UniqueId",
"CimClassQualifiers": "Abstract = True locale = 1033",
"CimClassMethods": "",
"CimSystemProperties": "Microsoft.Management.Infrastructure.CimSystemProperties"
},
"CimClassProperties": [
"ObjectId",
"PassThroughClass",
"PassThroughIds",
"PassThroughNamespace",
"PassThroughServer",
"UniqueId",
"DeviceId",
"FlushLatencyMax",
"LoadUnloadCycleCount",
"LoadUnloadCycleCountMax",
"ManufactureDate",
"PowerOnHours",
"ReadErrorsCorrected",
"ReadErrorsTotal",
"ReadErrorsUncorrected",
"ReadLatencyMax",
"StartStopCycleCount",
"StartStopCycleCountMax",
"Temperature",
"TemperatureMax",
"Wear",
"WriteErrorsCorrected",
"WriteErrorsTotal",
"WriteErrorsUncorrected",
"WriteLatencyMax"
]
},
"Temperature": 46,
"TemperatureMax": 70,
"Wear": 0,
"ReadLatencyMax": 1930,
"WriteLatencyMax": 1903,
"FlushLatencyMax": 262
}
''', // Disk SMART data
'12th Gen Intel(R) Core(TM) i5-12490F', // CPU brand
];