Skip to content

Commit 0190d07

Browse files
authored
Miner GPU update (#347)
* using new GPU external miner added controls for number of CPU and GPU * gpu fix * CPU mining can be 0, off * minor design update * prevent hash rate flickering between 0 and not 0 * persist CPU/GPU settings
1 parent 6466290 commit 0190d07

10 files changed

Lines changed: 409 additions & 33 deletions

miner-app/lib/features/miner/miner_balance_card.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'dart:async';
22
import 'dart:io';
3+
34
import 'package:flutter/material.dart';
45
import 'package:quantus_miner/src/services/binary_manager.dart';
56
import 'package:quantus_miner/src/shared/extensions/snackbar_extensions.dart';
7+
import 'package:quantus_miner/src/shared/miner_app_constants.dart';
68
import 'package:quantus_sdk/quantus_sdk.dart';
79

810
class MinerBalanceCard extends StatefulWidget {
@@ -87,6 +89,7 @@ class _MinerBalanceCardState extends State<MinerBalanceCard> {
8789
Widget build(BuildContext context) {
8890
return Container(
8991
margin: const EdgeInsets.only(bottom: 20),
92+
height: MinerAppConstants.cardHeight,
9093
decoration: BoxDecoration(
9194
gradient: LinearGradient(
9295
begin: Alignment.topLeft,

miner-app/lib/features/miner/miner_controls.dart

Lines changed: 120 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
55
import 'package:quantus_miner/src/services/mining_stats_service.dart';
66
import 'package:quantus_miner/src/shared/extensions/snackbar_extensions.dart';
77

8+
import '../../main.dart';
89
import '../../src/services/binary_manager.dart';
10+
import '../../src/services/gpu_detection_service.dart';
911
import '../../src/services/miner_process.dart';
10-
import '../../main.dart';
12+
import '../../src/services/miner_settings_service.dart';
1113

1214
class MinerControls extends StatefulWidget {
1315
final MinerProcess? minerProcess;
@@ -29,6 +31,38 @@ class MinerControls extends StatefulWidget {
2931

3032
class _MinerControlsState extends State<MinerControls> {
3133
bool _isAttemptingToggle = false;
34+
int _cpuWorkers = 8;
35+
int _gpuDevices = 0;
36+
int _detectedGpuCount = 0;
37+
final _settingsService = MinerSettingsService();
38+
39+
@override
40+
void initState() {
41+
super.initState();
42+
_loadSettings();
43+
_detectHardware();
44+
}
45+
46+
Future<void> _loadSettings() async {
47+
final savedCpuWorkers = await _settingsService.getCpuWorkers();
48+
final savedGpuDevices = await _settingsService.getGpuDevices();
49+
50+
if (mounted) {
51+
setState(() {
52+
_cpuWorkers = savedCpuWorkers ?? (Platform.numberOfProcessors > 0 ? Platform.numberOfProcessors : 8);
53+
_gpuDevices = savedGpuDevices ?? 0;
54+
});
55+
}
56+
}
57+
58+
Future<void> _detectHardware() async {
59+
final gpuCount = await GpuDetectionService.detectGpuCount();
60+
if (mounted) {
61+
setState(() {
62+
_detectedGpuCount = gpuCount;
63+
});
64+
}
65+
}
3266

3367
Future<void> _toggle() async {
3468
if (_isAttemptingToggle) return;
@@ -65,7 +99,15 @@ class _MinerControlsState extends State<MinerControls> {
6599
return;
66100
}
67101

68-
final newProc = MinerProcess(bin, id, rew, onStatsUpdate: widget.onMetricsUpdate);
102+
final newProc = MinerProcess(
103+
bin,
104+
id,
105+
rew,
106+
onStatsUpdate: widget.onMetricsUpdate,
107+
cpuWorkers: _cpuWorkers,
108+
gpuDevices: _gpuDevices,
109+
detectedGpuCount: _detectedGpuCount,
110+
);
69111
// Notify parent about the new miner process
70112
widget.onMinerProcessChanged.call(newProc);
71113

@@ -129,15 +171,82 @@ class _MinerControlsState extends State<MinerControls> {
129171

130172
@override
131173
Widget build(BuildContext context) {
132-
return ElevatedButton(
133-
style: ElevatedButton.styleFrom(
134-
backgroundColor: widget.minerProcess == null ? Colors.green : Colors.blue,
135-
padding: const EdgeInsets.symmetric(vertical: 15),
136-
textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
137-
minimumSize: const Size(200, 50),
138-
),
139-
onPressed: _isAttemptingToggle ? null : _toggle,
140-
child: Text(widget.minerProcess == null ? 'Start Mining' : 'Stop Mining'),
174+
return Column(
175+
mainAxisSize: MainAxisSize.min,
176+
children: [
177+
// CPU Workers Control
178+
Padding(
179+
padding: const EdgeInsets.symmetric(horizontal: 24.0),
180+
child: Column(
181+
crossAxisAlignment: CrossAxisAlignment.start,
182+
children: [
183+
Row(
184+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
185+
children: [
186+
const Text('CPU Workers', style: TextStyle(fontWeight: FontWeight.bold)),
187+
Text('$_cpuWorkers'),
188+
],
189+
),
190+
Slider(
191+
value: _cpuWorkers.toDouble(),
192+
min: 0,
193+
max: (Platform.numberOfProcessors > 0 ? Platform.numberOfProcessors : 16).toDouble(),
194+
divisions: (Platform.numberOfProcessors > 0 ? Platform.numberOfProcessors : 16),
195+
label: _cpuWorkers.toString(),
196+
onChanged: widget.minerProcess == null
197+
? (value) {
198+
final rounded = value.round();
199+
setState(() => _cpuWorkers = rounded);
200+
_settingsService.saveCpuWorkers(rounded);
201+
}
202+
: null,
203+
),
204+
],
205+
),
206+
),
207+
const SizedBox(height: 16),
208+
// GPU Devices Control
209+
Padding(
210+
padding: const EdgeInsets.symmetric(horizontal: 24.0),
211+
child: Column(
212+
crossAxisAlignment: CrossAxisAlignment.start,
213+
children: [
214+
Row(
215+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
216+
children: [
217+
const Text('GPU Devices', style: TextStyle(fontWeight: FontWeight.bold)),
218+
Text('$_gpuDevices / $_detectedGpuCount'),
219+
],
220+
),
221+
Slider(
222+
value: _gpuDevices.toDouble(),
223+
min: 0,
224+
max: _detectedGpuCount > 0 ? _detectedGpuCount.toDouble() : 1,
225+
divisions: _detectedGpuCount > 0 ? _detectedGpuCount : 1,
226+
label: _gpuDevices.toString(),
227+
onChanged: widget.minerProcess == null
228+
? (value) {
229+
final rounded = value.round();
230+
setState(() => _gpuDevices = rounded);
231+
_settingsService.saveGpuDevices(rounded);
232+
}
233+
: null,
234+
),
235+
],
236+
),
237+
),
238+
const SizedBox(height: 24),
239+
ElevatedButton(
240+
style: ElevatedButton.styleFrom(
241+
backgroundColor: widget.minerProcess == null ? Colors.green : Colors.blue,
242+
padding: const EdgeInsets.symmetric(vertical: 15),
243+
textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
244+
minimumSize: const Size(200, 50),
245+
),
246+
onPressed: _isAttemptingToggle ? null : _toggle,
247+
child: Text(widget.minerProcess == null ? 'Start Mining' : 'Stop Mining'),
248+
),
249+
],
141250
);
142251
}
143252
}

miner-app/lib/features/miner/miner_stats_card.dart

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'package:flutter/material.dart';
22
import 'package:quantus_miner/src/services/mining_stats_service.dart';
3+
import 'package:quantus_miner/src/shared/miner_app_constants.dart';
4+
import 'package:quantus_miner/src/utils/hashrate_formatter.dart';
35
import 'package:quantus_sdk/quantus_sdk.dart';
46

57
class MinerStatsCard extends StatefulWidget {
@@ -49,6 +51,7 @@ class _MinerStatsCardState extends State<MinerStatsCard> {
4951
Container _buildStatsDisplay() {
5052
return Container(
5153
margin: const EdgeInsets.only(bottom: 20),
54+
height: MinerAppConstants.cardHeight,
5255
decoration: BoxDecoration(
5356
gradient: LinearGradient(
5457
begin: Alignment.topLeft,
@@ -99,10 +102,13 @@ class _MinerStatsCardState extends State<MinerStatsCard> {
99102
children: [
100103
_buildCompactStat(icon: Icons.people, label: 'Peers', value: '${_miningStats!.peerCount}'),
101104
const SizedBox(height: 16),
102-
_buildCompactStat(
103-
icon: Icons.settings,
104-
label: 'Workers',
105-
value: '${_miningStats!.workers} / ${_miningStats!.cpuCapacity}',
105+
_buildDualStat(
106+
icon: Icons.memory,
107+
label1: 'CPU',
108+
value1: '${_miningStats!.workers} / ${_miningStats!.cpuCapacity}',
109+
label2: 'GPU',
110+
value2:
111+
'${_miningStats!.gpuDevices} / ${_miningStats!.gpuCapacity > 0 ? _miningStats!.gpuCapacity : (_miningStats!.gpuDevices > 0 ? _miningStats!.gpuDevices : "-")}',
106112
),
107113
],
108114
),
@@ -115,7 +121,7 @@ class _MinerStatsCardState extends State<MinerStatsCard> {
115121
_buildCompactStat(
116122
icon: Icons.speed,
117123
label: 'Hashrate',
118-
value: '${_miningStats!.hashrate.toStringAsFixed(2)} H/s',
124+
value: HashrateFormatter.format(_miningStats!.hashrate),
119125
),
120126
const SizedBox(height: 16),
121127
_buildCompactStat(
@@ -134,6 +140,98 @@ class _MinerStatsCardState extends State<MinerStatsCard> {
134140
);
135141
}
136142

143+
Widget _buildDualStat({
144+
required IconData icon,
145+
required String label1,
146+
required String value1,
147+
required String label2,
148+
required String value2,
149+
}) {
150+
return Row(
151+
crossAxisAlignment: CrossAxisAlignment.start,
152+
children: [
153+
Container(
154+
padding: const EdgeInsets.all(8),
155+
decoration: BoxDecoration(
156+
gradient: const LinearGradient(
157+
colors: [
158+
Color(0xFF6366F1), // Deep purple
159+
Color(0xFF1E3A8A), // Deep blue
160+
],
161+
),
162+
borderRadius: BorderRadius.circular(8),
163+
),
164+
child: Icon(icon, color: Colors.white, size: 16),
165+
),
166+
const SizedBox(width: 12),
167+
Expanded(
168+
child: Transform.translate(
169+
offset: const Offset(0, -4),
170+
child: Row(
171+
mainAxisSize: MainAxisSize.min,
172+
crossAxisAlignment: CrossAxisAlignment.start,
173+
children: [
174+
Column(
175+
crossAxisAlignment: CrossAxisAlignment.start,
176+
mainAxisSize: MainAxisSize.min,
177+
children: [
178+
Text(
179+
value1,
180+
style: const TextStyle(
181+
fontSize: 16,
182+
fontWeight: FontWeight.w700,
183+
color: Colors.white,
184+
letterSpacing: -0.3,
185+
),
186+
),
187+
// const SizedBox(height: 0),
188+
Text(
189+
label1,
190+
style: TextStyle(
191+
fontSize: 11,
192+
color: Colors.white.useOpacity(0.6),
193+
fontWeight: FontWeight.w500,
194+
letterSpacing: 0.5,
195+
),
196+
),
197+
],
198+
),
199+
const SizedBox(width: 8),
200+
Container(width: 1, height: 28, color: Colors.white.useOpacity(0.3)),
201+
const SizedBox(width: 8),
202+
Column(
203+
crossAxisAlignment: CrossAxisAlignment.start,
204+
mainAxisSize: MainAxisSize.min,
205+
children: [
206+
Text(
207+
value2,
208+
style: const TextStyle(
209+
fontSize: 16,
210+
fontWeight: FontWeight.w700,
211+
color: Colors.white,
212+
letterSpacing: -0.3,
213+
),
214+
),
215+
// const SizedBox(height: 2),
216+
Text(
217+
label2,
218+
style: TextStyle(
219+
fontSize: 11,
220+
color: Colors.white.useOpacity(0.6),
221+
fontWeight: FontWeight.w500,
222+
letterSpacing: 0.5,
223+
),
224+
),
225+
],
226+
),
227+
],
228+
),
229+
),
230+
),
231+
],
232+
);
233+
}
234+
137235
Widget _buildCompactStat({required IconData icon, required String label, required String value}) {
138236
return Row(
139237
children: [

miner-app/lib/src/services/external_miner_api_client.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
23
import 'package:http/http.dart' as http;
34

45
class ExternalMinerMetrics {
@@ -7,6 +8,7 @@ class ExternalMinerMetrics {
78
final int totalHashes;
89
final int workers;
910
final int cpuCapacity;
11+
final int gpuDevices;
1012
final bool isHealthy;
1113

1214
ExternalMinerMetrics({
@@ -15,12 +17,13 @@ class ExternalMinerMetrics {
1517
required this.totalHashes,
1618
required this.workers,
1719
required this.cpuCapacity,
20+
this.gpuDevices = 0,
1821
required this.isHealthy,
1922
});
2023

2124
@override
2225
String toString() {
23-
return 'ExternalMinerMetrics(hashRate: ${hashRate.toStringAsFixed(2)} H/s, activeJobs: $activeJobs, totalHashes: $totalHashes, workers: $workers, cpuCapacity: $cpuCapacity, isHealthy: $isHealthy)';
26+
return 'ExternalMinerMetrics(hashRate: ${hashRate.toStringAsFixed(2)} H/s, activeJobs: $activeJobs, totalHashes: $totalHashes, workers: $workers, cpuCapacity: $cpuCapacity, gpuDevices: $gpuDevices, isHealthy: $isHealthy)';
2427
}
2528
}
2629

@@ -82,6 +85,7 @@ class ExternalMinerApiClient {
8285
int totalHashes = 0;
8386
int workers = 0;
8487
int cpuCapacity = 0;
88+
int gpuDevices = 0;
8589

8690
for (final line in lines) {
8791
if (line.startsWith('#')) continue; // Skip comments
@@ -113,6 +117,11 @@ class ExternalMinerApiClient {
113117
if (parts.length >= 2) {
114118
cpuCapacity = int.tryParse(parts.last) ?? 0;
115119
}
120+
} else if (line.startsWith('miner_gpu_devices ')) {
121+
final parts = line.split(' ');
122+
if (parts.length >= 2) {
123+
gpuDevices = int.tryParse(parts.last) ?? 0;
124+
}
116125
}
117126
} catch (e) {
118127
// Skip invalid lines
@@ -126,6 +135,7 @@ class ExternalMinerApiClient {
126135
totalHashes: totalHashes,
127136
workers: workers,
128137
cpuCapacity: cpuCapacity,
138+
gpuDevices: gpuDevices,
129139
isHealthy: hashRate > 0 || activeJobs > 0,
130140
);
131141

@@ -147,6 +157,7 @@ class ExternalMinerApiClient {
147157
totalHashes: 0,
148158
workers: 0,
149159
cpuCapacity: 0,
160+
gpuDevices: 0,
150161
isHealthy: false,
151162
),
152163
);

0 commit comments

Comments
 (0)