Release Vexta CPU Miner v0.1.2
This commit is contained in:
parent
17949bc516
commit
d3b9876b45
3 changed files with 399 additions and 10 deletions
351
Program.cs
351
Program.cs
|
|
@ -1,12 +1,14 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
|
|
@ -85,10 +87,11 @@ internal static class Program
|
|||
password = GetArg(args, "-p") ?? "x";
|
||||
|
||||
var threadText = GetArg(args, "-t");
|
||||
var threadsAuto = threadText == null;
|
||||
|
||||
threadCount = threadText != null
|
||||
? int.Parse(threadText, CultureInfo.InvariantCulture)
|
||||
: Environment.ProcessorCount;
|
||||
threadCount = threadsAuto
|
||||
? GetPhysicalCoreCount()
|
||||
: int.Parse(threadText!, CultureInfo.InvariantCulture);
|
||||
|
||||
if(threadCount < 1)
|
||||
throw new ArgumentException("Thread count must be at least 1");
|
||||
|
|
@ -125,9 +128,12 @@ internal static class Program
|
|||
};
|
||||
|
||||
PrintBanner();
|
||||
Console.WriteLine($"Pool : {host}:{port}");
|
||||
Console.WriteLine($"Worker : {worker}");
|
||||
Console.WriteLine($"Threads : {threadCount}");
|
||||
PrintStartupDiagnostics();
|
||||
Console.WriteLine($"Pool : {host}:{port}");
|
||||
Console.WriteLine($"Worker : {worker}");
|
||||
Console.WriteLine(
|
||||
$"Mining Threads : {threadCount}" +
|
||||
(threadsAuto ? " (auto)" : " (manual)"));
|
||||
Console.WriteLine();
|
||||
|
||||
var statsTask = StatsLoopAsync(shutdown.Token);
|
||||
|
|
@ -814,6 +820,235 @@ internal static class Program
|
|||
}
|
||||
}
|
||||
|
||||
private static void PrintStartupDiagnostics()
|
||||
{
|
||||
var logicalCpus = Environment.ProcessorCount;
|
||||
var physicalCores = GetPhysicalCoreCount();
|
||||
var cpuModel = GetCpuModel();
|
||||
var aesSupported = GetAesSupport();
|
||||
var hugePages = GetHugePagesStatus();
|
||||
|
||||
string randomXStatus;
|
||||
string randomXPath;
|
||||
|
||||
if(OperatingSystem.IsLinux())
|
||||
{
|
||||
// Accessing these properties initializes VextaRandomX and
|
||||
// explicitly loads librandomx.so from the miner directory.
|
||||
randomXStatus = VextaRandomX.NativeLibraryLoaded
|
||||
? "Loaded OK"
|
||||
: "Not loaded";
|
||||
|
||||
randomXPath = VextaRandomX.NativeLibraryPath ?? "Unknown";
|
||||
}
|
||||
else
|
||||
{
|
||||
randomXStatus = "System loader";
|
||||
randomXPath = "Platform default";
|
||||
}
|
||||
|
||||
Console.WriteLine($"CPU : {cpuModel}");
|
||||
Console.WriteLine($"Physical Cores : {physicalCores}");
|
||||
Console.WriteLine($"Logical CPUs : {logicalCpus}");
|
||||
Console.WriteLine($"AES : {aesSupported}");
|
||||
Console.WriteLine($"Huge Pages : {hugePages}");
|
||||
Console.WriteLine($"RandomX Library: {randomXStatus}");
|
||||
Console.WriteLine($"Library Path : {randomXPath}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static string GetCpuModel()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(OperatingSystem.IsLinux() && File.Exists("/proc/cpuinfo"))
|
||||
{
|
||||
foreach(var line in File.ReadLines("/proc/cpuinfo"))
|
||||
{
|
||||
if(line.StartsWith("model name", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var colon = line.IndexOf(':');
|
||||
|
||||
if(colon >= 0)
|
||||
return line[(colon + 1)..].Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var identifier = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(identifier))
|
||||
return identifier.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private static int GetPhysicalCoreCount()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(OperatingSystem.IsLinux() && File.Exists("/proc/cpuinfo"))
|
||||
{
|
||||
var cores = new HashSet<string>();
|
||||
string? physicalId = null;
|
||||
string? coreId = null;
|
||||
|
||||
foreach(var rawLine in File.ReadLines("/proc/cpuinfo"))
|
||||
{
|
||||
var line = rawLine.Trim();
|
||||
|
||||
if(line.Length == 0)
|
||||
{
|
||||
if(physicalId != null && coreId != null)
|
||||
cores.Add($"{physicalId}:{coreId}");
|
||||
|
||||
physicalId = null;
|
||||
coreId = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(line.StartsWith("physical id", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var colon = line.IndexOf(':');
|
||||
|
||||
if(colon >= 0)
|
||||
physicalId = line[(colon + 1)..].Trim();
|
||||
}
|
||||
else if(line.StartsWith("core id", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var colon = line.IndexOf(':');
|
||||
|
||||
if(colon >= 0)
|
||||
coreId = line[(colon + 1)..].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
if(physicalId != null && coreId != null)
|
||||
cores.Add($"{physicalId}:{coreId}");
|
||||
|
||||
if(cores.Count > 0)
|
||||
return cores.Count;
|
||||
}
|
||||
|
||||
if(OperatingSystem.IsWindows())
|
||||
{
|
||||
var windowsCores = GetWindowsPhysicalCoreCount();
|
||||
|
||||
if(windowsCores > 0)
|
||||
return windowsCores;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
private static int GetWindowsPhysicalCoreCount()
|
||||
{
|
||||
uint length = 0;
|
||||
|
||||
GetLogicalProcessorInformation(IntPtr.Zero, ref length);
|
||||
|
||||
if(length == 0)
|
||||
return 0;
|
||||
|
||||
var buffer = Marshal.AllocHGlobal((int)length);
|
||||
|
||||
try
|
||||
{
|
||||
if(!GetLogicalProcessorInformation(buffer, ref length))
|
||||
return 0;
|
||||
|
||||
// SYSTEM_LOGICAL_PROCESSOR_INFORMATION is 32 bytes on x64
|
||||
// and 24 bytes on x86.
|
||||
var entrySize = IntPtr.Size == 8 ? 32 : 24;
|
||||
var relationshipOffset = IntPtr.Size;
|
||||
var count = 0;
|
||||
|
||||
for(var offset = 0;
|
||||
offset + entrySize <= length;
|
||||
offset += entrySize)
|
||||
{
|
||||
var relationship = Marshal.ReadInt32(
|
||||
buffer,
|
||||
offset + relationshipOffset);
|
||||
|
||||
// RelationProcessorCore = 0
|
||||
if(relationship == 0)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetLogicalProcessorInformation(
|
||||
IntPtr buffer,
|
||||
ref uint returnedLength);
|
||||
|
||||
private static string GetAesSupport()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(System.Runtime.Intrinsics.X86.Aes.IsSupported)
|
||||
return "Supported";
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return "Not detected";
|
||||
}
|
||||
|
||||
private static string GetHugePagesStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
if(OperatingSystem.IsLinux() && File.Exists("/proc/meminfo"))
|
||||
{
|
||||
foreach(var line in File.ReadLines("/proc/meminfo"))
|
||||
{
|
||||
if(!line.StartsWith("HugePages_Total:", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
var value = line["HugePages_Total:".Length..].Trim();
|
||||
var parts = value.Split(
|
||||
' ',
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if(parts.Length > 0 &&
|
||||
long.TryParse(
|
||||
parts[0],
|
||||
NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var total))
|
||||
{
|
||||
return total > 0
|
||||
? $"Configured ({total})"
|
||||
: "Not configured";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private static async Task StatsLoopAsync(CancellationToken ct)
|
||||
{
|
||||
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
|
||||
|
|
@ -823,6 +1058,10 @@ internal static class Program
|
|||
var lastTime = sw.Elapsed;
|
||||
var measuring = false;
|
||||
|
||||
var averageHistory = new List<(TimeSpan Time, long Hashes)>();
|
||||
TimeSpan? averageStartTime = null;
|
||||
var nextAverageReport = TimeSpan.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
while(await timer.WaitForNextTickAsync(ct))
|
||||
|
|
@ -830,6 +1069,34 @@ internal static class Program
|
|||
var now = sw.Elapsed;
|
||||
var hashes = Interlocked.Read(ref totalHashes);
|
||||
|
||||
if(hashes > 0)
|
||||
{
|
||||
if(averageStartTime == null)
|
||||
{
|
||||
averageStartTime = now;
|
||||
nextAverageReport = now + TimeSpan.FromMinutes(1);
|
||||
averageHistory.Add((now, hashes));
|
||||
}
|
||||
else if(now >= nextAverageReport)
|
||||
{
|
||||
averageHistory.Add((now, hashes));
|
||||
|
||||
WriteAverageHashrate(
|
||||
now,
|
||||
hashes,
|
||||
averageHistory,
|
||||
averageStartTime.Value);
|
||||
|
||||
do
|
||||
{
|
||||
nextAverageReport += TimeSpan.FromMinutes(1);
|
||||
}
|
||||
while(nextAverageReport <= now);
|
||||
|
||||
TrimAverageHistory(now, averageHistory);
|
||||
}
|
||||
}
|
||||
|
||||
if(hashes == 0)
|
||||
{
|
||||
lastHashes = hashes;
|
||||
|
|
@ -877,6 +1144,74 @@ internal static class Program
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteAverageHashrate(
|
||||
TimeSpan now,
|
||||
long hashes,
|
||||
List<(TimeSpan Time, long Hashes)> history,
|
||||
TimeSpan startTime)
|
||||
{
|
||||
var avg5 = FormatAverageWindow(
|
||||
now, hashes, history, startTime, TimeSpan.FromMinutes(5));
|
||||
|
||||
var avg15 = FormatAverageWindow(
|
||||
now, hashes, history, startTime, TimeSpan.FromMinutes(15));
|
||||
|
||||
var avg60 = FormatAverageWindow(
|
||||
now, hashes, history, startTime, TimeSpan.FromMinutes(60));
|
||||
|
||||
WriteColor(
|
||||
$"AVG HASHRATE 5m:{avg5} 15m:{avg15} 60m:{avg60}",
|
||||
ConsoleColor.DarkCyan);
|
||||
}
|
||||
|
||||
private static string FormatAverageWindow(
|
||||
TimeSpan now,
|
||||
long hashes,
|
||||
List<(TimeSpan Time, long Hashes)> history,
|
||||
TimeSpan startTime,
|
||||
TimeSpan window)
|
||||
{
|
||||
if(now - startTime < window)
|
||||
return "warming up";
|
||||
|
||||
var cutoff = now - window;
|
||||
(TimeSpan Time, long Hashes)? sample = null;
|
||||
|
||||
foreach(var item in history)
|
||||
{
|
||||
if(item.Time <= cutoff)
|
||||
sample = item;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(sample == null)
|
||||
return "warming up";
|
||||
|
||||
var elapsed = (now - sample.Value.Time).TotalSeconds;
|
||||
|
||||
if(elapsed <= 0)
|
||||
return "warming up";
|
||||
|
||||
var deltaHashes = hashes - sample.Value.Hashes;
|
||||
var rate = deltaHashes / elapsed;
|
||||
|
||||
return FormatHashrate(rate);
|
||||
}
|
||||
|
||||
private static void TrimAverageHistory(
|
||||
TimeSpan now,
|
||||
List<(TimeSpan Time, long Hashes)> history)
|
||||
{
|
||||
var keepAfter = now - TimeSpan.FromMinutes(62);
|
||||
|
||||
while(history.Count > 2 &&
|
||||
history[1].Time < keepAfter)
|
||||
{
|
||||
history.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatHashrate(double value)
|
||||
{
|
||||
if(value >= 1_000_000)
|
||||
|
|
@ -956,7 +1291,7 @@ internal static class Program
|
|||
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Vexta CPU Miner 0.1.1");
|
||||
Console.WriteLine("Vexta CPU Miner 0.1.2");
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.DarkGray;
|
||||
Console.WriteLine("Copyright (c) 2026 Vexta Project");
|
||||
|
|
@ -996,7 +1331,7 @@ internal static class Program
|
|||
Console.WriteLine(" -u Vexta address / worker name");
|
||||
Console.WriteLine(" -p Password (default: x)");
|
||||
Console.WriteLine(
|
||||
" -t CPU threads (default: logical CPU count)");
|
||||
" -t CPU threads (default: auto-detect physical cores)");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Ctrl+C stops the miner cleanly.");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue