feat: Add avg difficulty per share statistic to dashboard
- Add AvgDifficulty and DiffCurrent fields to PerformanceMetrics - Calculate avg difficulty as HashesTotal/SharesGood in XMRig stats - Add difficulty data to TT-Miner stats using pool difficulty - Display "Avg Diff" stat in stats panel with k/M/G/T formatting - Add WriteStdin to MockMiner for test interface compliance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f10e7a16e2
commit
b9f9143336
5 changed files with 71 additions and 17 deletions
|
|
@ -123,13 +123,15 @@ type Config struct {
|
|||
|
||||
// PerformanceMetrics represents the performance metrics for a miner.
|
||||
type PerformanceMetrics struct {
|
||||
Hashrate int `json:"hashrate"`
|
||||
Shares int `json:"shares"`
|
||||
Rejected int `json:"rejected"`
|
||||
Uptime int `json:"uptime"`
|
||||
LastShare int64 `json:"lastShare"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
ExtraData map[string]interface{} `json:"extraData,omitempty"`
|
||||
Hashrate int `json:"hashrate"`
|
||||
Shares int `json:"shares"`
|
||||
Rejected int `json:"rejected"`
|
||||
Uptime int `json:"uptime"`
|
||||
LastShare int64 `json:"lastShare"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
AvgDifficulty int `json:"avgDifficulty"` // Average difficulty per accepted share (HashesTotal/SharesGood)
|
||||
DiffCurrent int `json:"diffCurrent"` // Current job difficulty from pool
|
||||
ExtraData map[string]interface{} `json:"extraData,omitempty"`
|
||||
}
|
||||
|
||||
// HashratePoint represents a single hashrate measurement at a specific time.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ type MockMiner struct {
|
|||
AddHashratePointFunc func(point HashratePoint)
|
||||
ReduceHashrateHistoryFunc func(now time.Time)
|
||||
GetLogsFunc func() []string
|
||||
WriteStdinFunc func(input string) error
|
||||
}
|
||||
|
||||
func (m *MockMiner) Install() error { return m.InstallFunc() }
|
||||
|
|
@ -43,6 +44,7 @@ func (m *MockMiner) GetHashrateHistory() []HashratePoint { return m.GetHashrate
|
|||
func (m *MockMiner) AddHashratePoint(point HashratePoint) { m.AddHashratePointFunc(point) }
|
||||
func (m *MockMiner) ReduceHashrateHistory(now time.Time) { m.ReduceHashrateHistoryFunc(now) }
|
||||
func (m *MockMiner) GetLogs() []string { return m.GetLogsFunc() }
|
||||
func (m *MockMiner) WriteStdin(input string) error { return m.WriteStdinFunc(input) }
|
||||
|
||||
// MockManager is a mock implementation of the Manager for testing.
|
||||
type MockManager struct {
|
||||
|
|
|
|||
|
|
@ -49,11 +49,17 @@ func (m *TTMiner) GetStats() (*PerformanceMetrics, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// For TT-Miner, we use the connection difficulty as both current and avg
|
||||
// since TT-Miner doesn't expose per-share difficulty data
|
||||
diffCurrent := summary.Connection.Diff
|
||||
|
||||
return &PerformanceMetrics{
|
||||
Hashrate: int(totalHashrate),
|
||||
Shares: summary.Results.SharesGood,
|
||||
Rejected: summary.Results.SharesTotal - summary.Results.SharesGood,
|
||||
Uptime: summary.Uptime,
|
||||
Algorithm: summary.Algo,
|
||||
Hashrate: int(totalHashrate),
|
||||
Shares: summary.Results.SharesGood,
|
||||
Rejected: summary.Results.SharesTotal - summary.Results.SharesGood,
|
||||
Uptime: summary.Uptime,
|
||||
Algorithm: summary.Algo,
|
||||
AvgDifficulty: diffCurrent, // Use pool diff as approximation
|
||||
DiffCurrent: diffCurrent,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,11 +42,19 @@ func (m *XMRigMiner) GetStats() (*PerformanceMetrics, error) {
|
|||
hashrate = int(summary.Hashrate.Total[0])
|
||||
}
|
||||
|
||||
// Calculate average difficulty per accepted share
|
||||
var avgDifficulty int
|
||||
if summary.Results.SharesGood > 0 {
|
||||
avgDifficulty = summary.Results.HashesTotal / summary.Results.SharesGood
|
||||
}
|
||||
|
||||
return &PerformanceMetrics{
|
||||
Hashrate: hashrate,
|
||||
Shares: summary.Results.SharesGood,
|
||||
Rejected: summary.Results.SharesTotal - summary.Results.SharesGood,
|
||||
Uptime: summary.Uptime,
|
||||
Algorithm: summary.Algo,
|
||||
Hashrate: hashrate,
|
||||
Shares: summary.Results.SharesGood,
|
||||
Rejected: summary.Results.SharesTotal - summary.Results.SharesGood,
|
||||
Uptime: summary.Uptime,
|
||||
Algorithm: summary.Algo,
|
||||
AvgDifficulty: avgDifficulty,
|
||||
DiffCurrent: summary.Results.DiffCurrent,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,18 @@ import { MinerService } from '../../miner.service';
|
|||
|
||||
<div class="stat-divider"></div>
|
||||
|
||||
<div class="stat-item">
|
||||
<svg class="stat-icon text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
||||
</svg>
|
||||
<div class="stat-content">
|
||||
<span class="stat-value tabular-nums">{{ formatDifficulty(avgDifficulty()) }}</span>
|
||||
</div>
|
||||
<span class="stat-label">Avg Diff</span>
|
||||
</div>
|
||||
|
||||
<div class="stat-divider"></div>
|
||||
|
||||
<div class="stat-item workers">
|
||||
<svg class="stat-icon" [class.text-success-500]="minerCount() > 0" [class.text-slate-500]="minerCount() === 0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>
|
||||
|
|
@ -149,6 +161,10 @@ import { MinerService } from '../../miner.service';
|
|||
background: rgb(37 37 66 / 0.3);
|
||||
}
|
||||
|
||||
.text-purple-500 {
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stats-panel {
|
||||
gap: 0.75rem;
|
||||
|
|
@ -202,6 +218,17 @@ export class StatsPanelComponent {
|
|||
|
||||
minerCount = computed(() => this.miners().length);
|
||||
|
||||
// Calculate average difficulty per accepted share (HashesTotal / SharesGood)
|
||||
avgDifficulty = computed(() => {
|
||||
let totalHashes = 0;
|
||||
let totalShares = 0;
|
||||
this.miners().forEach(m => {
|
||||
totalHashes += m.full_stats?.results?.hashes_total || 0;
|
||||
totalShares += m.full_stats?.results?.shares_good || 0;
|
||||
});
|
||||
return totalShares > 0 ? Math.floor(totalHashes / totalShares) : 0;
|
||||
});
|
||||
|
||||
formatHashrate(hashrate: number): string {
|
||||
if (hashrate >= 1000000000) return (hashrate / 1000000000).toFixed(2);
|
||||
if (hashrate >= 1000000) return (hashrate / 1000000).toFixed(2);
|
||||
|
|
@ -227,4 +254,13 @@ export class StatsPanelComponent {
|
|||
const mins = Math.floor((seconds % 3600) / 60);
|
||||
return `${hours}h ${mins}m`;
|
||||
}
|
||||
|
||||
formatDifficulty(diff: number): string {
|
||||
if (diff === 0) return '-';
|
||||
if (diff >= 1000000000000) return (diff / 1000000000000).toFixed(2) + 'T';
|
||||
if (diff >= 1000000000) return (diff / 1000000000).toFixed(2) + 'G';
|
||||
if (diff >= 1000000) return (diff / 1000000).toFixed(2) + 'M';
|
||||
if (diff >= 1000) return (diff / 1000).toFixed(2) + 'k';
|
||||
return diff.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue