diff --git a/include/multipass/platform.h b/include/multipass/platform.h index a5c153d9de..68e98e5111 100644 --- a/include/multipass/platform.h +++ b/include/multipass/platform.h @@ -77,6 +77,8 @@ class Platform : public Singleton virtual QString default_privileged_mounts() const; virtual bool is_image_url_supported() const; [[nodiscard]] virtual std::string bridge_nomenclature() const; + virtual int get_cpus() const; + virtual long long get_total_ram() const; }; QString interpret_setting(const QString& key, const QString& val); diff --git a/src/client/gui/lib/providers.dart b/src/client/gui/lib/providers.dart index 0cfc0fb721..a10afd1714 100644 --- a/src/client/gui/lib/providers.dart +++ b/src/client/gui/lib/providers.dart @@ -72,6 +72,10 @@ final daemonAvailableProvider = Provider((ref) { return false; }); +final daemonInfoProvider = FutureProvider((ref) { + return ref.watch(grpcClientProvider).daemonInfo(); +}); + class AllVmInfosNotifier extends Notifier> { @override List build() { diff --git a/src/client/gui/lib/vm_details/cpus_slider.dart b/src/client/gui/lib/vm_details/cpus_slider.dart index f7bfe5b2b8..a0f955a0f0 100644 --- a/src/client/gui/lib/vm_details/cpus_slider.dart +++ b/src/client/gui/lib/vm_details/cpus_slider.dart @@ -2,9 +2,11 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:system_info2/system_info2.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class CpusSlider extends StatefulWidget { +import '../providers.dart'; + +class CpusSlider extends ConsumerStatefulWidget { final int? initialValue; final FormFieldSetter onSaved; @@ -15,14 +17,12 @@ class CpusSlider extends StatefulWidget { }); @override - State createState() => _CpusSliderState(); + ConsumerState createState() => _CpusSliderState(); } -class _CpusSliderState extends State { - static final cores = SysInfo.cores.length; - +class _CpusSliderState extends ConsumerState { final min = 1; - late final max = math.max(widget.initialValue ?? 0, cores); + late final controller = TextEditingController( text: widget.initialValue?.toString(), ); @@ -57,6 +57,11 @@ class _CpusSliderState extends State { @override Widget build(BuildContext context) { + final daemonInfo = ref.watch(daemonInfoProvider); + final cores = daemonInfo.valueOrNull?.cpus ?? min; + final max = math.max(widget.initialValue ?? min, cores); + final divisions = math.max(1, max - min); // Ensure at least 1 division + final textField = TextField( controller: controller, focusNode: focusNode, @@ -78,7 +83,7 @@ class _CpusSliderState extends State { Slider( min: min.toDouble(), max: max.toDouble(), - divisions: max - min, + divisions: divisions, value: (field.value ?? min).toDouble(), onChanged: (value) { final intValue = value.toInt(); diff --git a/src/client/gui/lib/vm_details/disk_slider.dart b/src/client/gui/lib/vm_details/disk_slider.dart index 74e597ce73..fa25361137 100644 --- a/src/client/gui/lib/vm_details/disk_slider.dart +++ b/src/client/gui/lib/vm_details/disk_slider.dart @@ -8,19 +8,12 @@ import '../tooltip.dart'; import 'mapping_slider.dart'; import 'memory_slider.dart'; -final diskSizeProvider = FutureProvider((ref) { - return ref - .watch(grpcClientProvider) - .daemonInfo() - .then((r) => r.availableSpace.toInt()); -}); - class DiskSlider extends ConsumerWidget { final int? initialValue; final int min; final FormFieldSetter onSaved; - DiskSlider({ + const DiskSlider({ super.key, int? min, this.initialValue, @@ -29,7 +22,8 @@ class DiskSlider extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final disk = ref.watch(diskSizeProvider).valueOrNull ?? min; + final daemonInfo = ref.watch(daemonInfoProvider); + final disk = daemonInfo.valueOrNull?.availableSpace.toInt() ?? min; final max = math.max(initialValue ?? 0, disk); final enabled = min != max; diff --git a/src/client/gui/lib/vm_details/ram_slider.dart b/src/client/gui/lib/vm_details/ram_slider.dart index 0580f3da49..6246945545 100644 --- a/src/client/gui/lib/vm_details/ram_slider.dart +++ b/src/client/gui/lib/vm_details/ram_slider.dart @@ -1,30 +1,35 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; -import 'package:system_info2/system_info2.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../providers.dart'; import 'mapping_slider.dart'; import 'memory_slider.dart'; -class RamSlider extends StatelessWidget { - static final ram = SysInfo.getTotalPhysicalMemory(); - +class RamSlider extends ConsumerWidget { final int? initialValue; + final int min; final FormFieldSetter onSaved; const RamSlider({ super.key, + int? min, this.initialValue, required this.onSaved, - }); + }) : min = min ?? 512.mebi; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final daemonInfo = ref.watch(daemonInfoProvider); + final ram = daemonInfo.valueOrNull?.memory.toInt() ?? min; + final max = math.max(initialValue ?? min, ram); + return MemorySlider( label: 'Memory', initialValue: initialValue, - min: 512.mebi, - max: math.max(initialValue ?? 0, ram), + min: min, + max: max, sysMax: ram, onSaved: onSaved, ); diff --git a/src/client/gui/pubspec.lock b/src/client/gui/pubspec.lock index f067df5da1..1b26c5da0e 100644 --- a/src/client/gui/pubspec.lock +++ b/src/client/gui/pubspec.lock @@ -268,14 +268,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - globbing: - dependency: transitive - description: - name: globbing - sha256: "4f89cfaf6fa74c9c1740a96259da06bd45411ede56744e28017cc534a12b6e2d" - url: "https://pub.dev" - source: hosted - version: "1.0.0" google_identity_services_web: dependency: transitive description: @@ -698,15 +690,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.0+3" - system_info2: - dependency: "direct main" - description: - path: "." - ref: "4.0.0+mp" - resolved-ref: ea5fac0e3a03db72276d946f6ca9abc9939e737a - url: "https://github.com/andrei-toterman/system_info.git" - source: git - version: "4.0.0" term_glyph: dependency: transitive description: diff --git a/src/client/gui/pubspec.yaml b/src/client/gui/pubspec.yaml index 29ca48340b..69307aa3c1 100644 --- a/src/client/gui/pubspec.yaml +++ b/src/client/gui/pubspec.yaml @@ -33,10 +33,6 @@ dependencies: rxdart: ^0.28.0 shared_preferences: ^2.3.2 synchronized: ^3.3.0+3 - system_info2: - git: - url: https://github.com/andrei-toterman/system_info.git - ref: 4.0.0+mp tray_menu: git: url: https://github.com/andrei-toterman/tray_menu.git diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 5852f9f45a..9675858258 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2789,6 +2789,9 @@ try // clang-format on QStorageInfo storage_info{config->data_directory}; response.set_available_space(storage_info.bytesTotal()); + response.set_cpus(MP_PLATFORM.get_cpus()); + response.set_memory(MP_PLATFORM.get_total_ram()); + server->Write(response); status_promise->set_value(grpc::Status{}); } diff --git a/src/platform/platform_unix.cpp b/src/platform/platform_unix.cpp index 9e00b6aaf3..900e95a0ec 100644 --- a/src/platform/platform_unix.cpp +++ b/src/platform/platform_unix.cpp @@ -183,3 +183,13 @@ std::function mp::platform::make_quit_watchdog() return sig; }; } + +int mp::platform::Platform::get_cpus() const +{ + return sysconf(_SC_NPROCESSORS_ONLN); +} + +long long mp::platform::Platform::get_total_ram() const +{ + return static_cast(sysconf(_SC_PHYS_PAGES)) * sysconf(_SC_PAGESIZE); +} diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 8141d3f623..8ee6b9c2b1 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -547,4 +547,6 @@ message DaemonInfoRequest { message DaemonInfoReply { string log_line = 1; uint64 available_space = 2; + uint32 cpus = 3; + uint64 memory = 4; } diff --git a/tests/unix/test_platform_unix.cpp b/tests/unix/test_platform_unix.cpp index 02c129e33d..a21ca5739d 100644 --- a/tests/unix/test_platform_unix.cpp +++ b/tests/unix/test_platform_unix.cpp @@ -142,3 +142,15 @@ TEST_F(TestPlatformUnix, multipassStorageLocationNotSetReturnsEmpty) EXPECT_TRUE(storage_path.isEmpty()); } + +TEST_F(TestPlatformUnix, get_cpus_returns_greater_than_zero) +{ + // On any real system, there should be at least 1 CPU + EXPECT_GT(MP_PLATFORM.get_cpus(), 0); +} + +TEST_F(TestPlatformUnix, get_total_ram_returns_greater_than_zero) +{ + // On any real system, there should be some RAM + EXPECT_GT(MP_PLATFORM.get_total_ram(), 0LL); +}