Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nethermind UI (initial) #8109

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,5 @@ FodyWeavers.xsd
## Nethermind
keystore/
/.githooks
bundle.js
src/Nethermind/Nethermind.Runner/wwwroot/js.map
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Blockchain/BlockTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,7 @@ void SetTotalDifficultyDeep(BlockHeader current)
public event EventHandler<BlockEventArgs>? NewSuggestedBlock;

public event EventHandler<BlockEventArgs>? NewHeadBlock;
public event EventHandler<IBlockTree.ForkChoice>? OnForkChoiceUpdated;

/// <summary>
/// Can delete a slice of the chain (usually invoked when the chain is corrupted in the DB).
Expand Down Expand Up @@ -1557,6 +1558,11 @@ public void ForkChoiceUpdated(Hash256? finalizedBlockHash, Hash256? safeBlockHas
_metadataDb.Set(MetadataDbKeys.FinalizedBlockHash, Rlp.Encode(FinalizedHash!).Bytes);
_metadataDb.Set(MetadataDbKeys.SafeBlockHash, Rlp.Encode(SafeHash!).Bytes);
}

var finalizedNumber = FindHeader(FinalizedHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing)?.Number;
var safeNumber = FindHeader(safeBlockHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing)?.Number;
Comment on lines +1562 to +1563
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potentially two lookups when we aren't sure if anyone is listening, seems wasteful?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ForkChoiceHandler has already looked them up; however need to work out how to plumb it through


OnForkChoiceUpdated?.Invoke(this, new(Head?.Number ?? 0, safeNumber ?? 0, finalizedNumber ?? 0));
}
}
}
15 changes: 15 additions & 0 deletions src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ public event EventHandler<OnUpdateMainChainArgs>? OnUpdateMainChain
}
}

public event EventHandler<IBlockTree.ForkChoice> OnForkChoiceUpdated
{
add
{
_baseTree.OnForkChoiceUpdated += value;
_overlayTree.OnForkChoiceUpdated += value;
}

remove
{
_baseTree.OnForkChoiceUpdated -= value;
_overlayTree.OnForkChoiceUpdated -= value;
}
}

public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) =>
_overlayTree.DeleteChainSlice(startNumber, endNumber, force);

Expand Down
8 changes: 8 additions & 0 deletions src/Nethermind/Nethermind.Blockchain/IBlockTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBlockOption
/// the whole branch.
/// </summary>
event EventHandler<OnUpdateMainChainArgs> OnUpdateMainChain;
event EventHandler<ForkChoice> OnForkChoiceUpdated;

int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false);

Expand All @@ -178,5 +179,12 @@ AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBlockOption
void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainChainStartPoint);

void RecalculateTreeLevels();

public readonly struct ForkChoice(long head, long safe, long finalized)
{
public readonly long Head => head;
public readonly long Safe => safe;
public readonly long Finalized => finalized;
}
}
}
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ public event EventHandler<OnUpdateMainChainArgs>? OnUpdateMainChain
remove { }
}

event EventHandler<IBlockTree.ForkChoice> IBlockTree.OnForkChoiceUpdated
{
add { }
remove { }
}

public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false)
{
var bestKnownNumber = BestKnownNumber;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing
private readonly Stopwatch _stopwatch = new();

public event EventHandler<IBlockchainProcessor.InvalidBlockEventArgs>? InvalidBlock;
public event EventHandler<BlockStatistics>? NewProcessingStatistics;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just pass through add/remove for simplicity?


/// <summary>
///
Expand Down Expand Up @@ -92,8 +93,12 @@ public BlockchainProcessor(
_blockTree.NewHeadBlock += OnNewHeadBlock;

_stats = new ProcessingStats(stateReader, _logger);
_stats.NewProcessingStatistics += OnNewProcessingStatistics;
}

private void OnNewProcessingStatistics(object? sender, BlockStatistics stats)
=> NewProcessingStatistics?.Invoke(sender, stats);

private void OnNewHeadBlock(object? sender, BlockEventArgs e)
{
_lastProcessedBlock = DateTime.UtcNow;
Expand Down Expand Up @@ -730,6 +735,7 @@ private bool RunSimpleChecksAheadOfProcessing(Block suggestedBlock, ProcessingOp
public void Dispose()
{
_recoveryComplete = true;
_stats.NewProcessingStatistics -= OnNewProcessingStatistics;
_recoveryQueue.Writer.TryComplete();
_blockQueue.Writer.TryComplete();
_loopCancellationSource?.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public interface IBlockchainProcessor : IDisposable
bool IsProcessingBlocks(ulong? maxProcessingInterval);

event EventHandler<InvalidBlockEventArgs> InvalidBlock;
event EventHandler<BlockStatistics> NewProcessingStatistics;

public class InvalidBlockEventArgs : EventArgs
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public bool IsProcessingBlocks(ulong? maxProcessingInterval)
public event EventHandler<BlockProcessedEventArgs> BlockProcessed;
public event EventHandler<BlockProcessedEventArgs> BlockInvalid;
public event EventHandler<IBlockchainProcessor.InvalidBlockEventArgs>? InvalidBlock;
public event EventHandler<BlockStatistics> NewProcessingStatistics;
#pragma warning restore 67

public void Dispose()
Expand Down
61 changes: 46 additions & 15 deletions src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,24 @@

namespace Nethermind.Consensus.Processing
{
public class BlockStatistics
{
public long BlockCount { get; internal set; }
public long BlockFrom { get; internal set; }
public long BlockTo { get; internal set; }
public double ProcessingMs { get; internal set; }
public double SlotMs { get; internal set; }
public double MgasPerSecond { get; internal set; }
public float MinGas { get; internal set; }
public float MedianGas { get; internal set; }
public float AveGas { get; internal set; }
public float MaxGas { get; internal set; }
public long GasLimit { get; internal set; }
}
//TODO Consult on disabling of such metrics from configuration
internal class ProcessingStats : IThreadPoolWorkItem
{
public event EventHandler<BlockStatistics>? NewProcessingStatistics;
private readonly IStateReader _stateReader;
private readonly ILogger _logger;
private readonly Stopwatch _runStopwatch = new();
Expand Down Expand Up @@ -195,23 +210,39 @@ void Execute()
}
}

if (_logger.IsInfo)
long chunkTx = Metrics.Transactions - _lastTotalTx;
long chunkCalls = _currentCallOps - _lastCallOps;
long chunkEmptyCalls = _currentEmptyCalls - _lastEmptyCalls;
long chunkCreates = _currentCreatesOps - _lastCreateOps;
long chunkSload = _currentSLoadOps - _lastSLoadOps;
long chunkSstore = _currentSStoreOps - _lastSStoreOps;
long contractsAnalysed = _currentContractsAnalyzed - _lastContractsAnalyzed;
long cachedContractsUsed = _currentCachedContractsUsed - _lastCachedContractsUsed;
double txps = chunkMicroseconds == 0 ? -1 : chunkTx / chunkMicroseconds * 1_000_000.0;
double bps = chunkMicroseconds == 0 ? -1 : chunkBlocks / chunkMicroseconds * 1_000_000.0;
double chunkMs = (chunkMicroseconds == 0 ? -1 : chunkMicroseconds / 1000.0);
double runMs = (_runMicroseconds == 0 ? -1 : _runMicroseconds / 1000.0);
string blockGas = Evm.Metrics.BlockMinGasPrice != float.MaxValue ? $"⛽ Gas gwei: {Evm.Metrics.BlockMinGasPrice:N2} .. {whiteText}{Math.Max(Evm.Metrics.BlockMinGasPrice, Evm.Metrics.BlockEstMedianGasPrice):N2}{resetColor} ({Evm.Metrics.BlockAveGasPrice:N2}) .. {Evm.Metrics.BlockMaxGasPrice:N2}" : "";
string mgasColor = whiteText;

NewProcessingStatistics?.Invoke(this, new BlockStatistics()
{
long chunkTx = Metrics.Transactions - _lastTotalTx;
long chunkCalls = _currentCallOps - _lastCallOps;
long chunkEmptyCalls = _currentEmptyCalls - _lastEmptyCalls;
long chunkCreates = _currentCreatesOps - _lastCreateOps;
long chunkSload = _currentSLoadOps - _lastSLoadOps;
long chunkSstore = _currentSStoreOps - _lastSStoreOps;
long contractsAnalysed = _currentContractsAnalyzed - _lastContractsAnalyzed;
long cachedContractsUsed = _currentCachedContractsUsed - _lastCachedContractsUsed;
double txps = chunkMicroseconds == 0 ? -1 : chunkTx / chunkMicroseconds * 1_000_000.0;
double bps = chunkMicroseconds == 0 ? -1 : chunkBlocks / chunkMicroseconds * 1_000_000.0;
double chunkMs = (chunkMicroseconds == 0 ? -1 : chunkMicroseconds / 1000.0);
double runMs = (_runMicroseconds == 0 ? -1 : _runMicroseconds / 1000.0);
string blockGas = Evm.Metrics.BlockMinGasPrice != float.MaxValue ? $"⛽ Gas gwei: {Evm.Metrics.BlockMinGasPrice:N2} .. {whiteText}{Math.Max(Evm.Metrics.BlockMinGasPrice, Evm.Metrics.BlockEstMedianGasPrice):N2}{resetColor} ({Evm.Metrics.BlockAveGasPrice:N2}) .. {Evm.Metrics.BlockMaxGasPrice:N2}" : "";
string mgasColor = whiteText;
BlockCount = chunkBlocks,
BlockFrom = block.Number - chunkBlocks + 1,
BlockTo = block.Number,

ProcessingMs = chunkMs,
SlotMs = runMs,
MgasPerSecond = mgasPerSecond,
MinGas = Evm.Metrics.BlockMinGasPrice,
MedianGas = Math.Max(Evm.Metrics.BlockMinGasPrice, Evm.Metrics.BlockEstMedianGasPrice),
AveGas = Evm.Metrics.BlockAveGasPrice,
MaxGas = Evm.Metrics.BlockMaxGasPrice,
GasLimit = block.GasLimit
});

if (_logger.IsInfo)
{
if (chunkBlocks > 1)
{
_logger.Info($"Processed {block.Number - chunkBlocks + 1,10}...{block.Number,9} | {chunkMs,10:N1} ms | slot {runMs,11:N0} ms |{blockGas}");
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Runner.Test/StandardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void All_json_rpc_methods_are_documented()
[Test]
public void All_metrics_are_described()
{
Monitoring.Test.MetricsTests.ValidateMetricsDescriptions();
Nethermind.Monitoring.Test.MetricsTests.ValidateMetricsDescriptions();
}

[Test]
Expand Down
131 changes: 131 additions & 0 deletions src/Nethermind/Nethermind.Runner/ConsoleHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,39 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Nethermind.Runner;

public static class ConsoleHelpers
{
private static LineInterceptingTextWriter _interceptingWriter;
public static event EventHandler<string>? LineWritten;
public static string[] GetRecentMessages() => _interceptingWriter.GetRecentMessages();

public static void EnableConsoleColorOutput()
{
const int STD_OUTPUT_HANDLE = -11;
const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;

Console.OutputEncoding = System.Text.Encoding.UTF8;

// Capture original out
TextWriter originalOut = Console.Out;

// Create our intercepting writer
_interceptingWriter = new LineInterceptingTextWriter(originalOut);
_interceptingWriter.LineWritten += (sender, line) =>
{
LineWritten?.Invoke(sender, line);
};

// Redirect Console.Out
Console.SetOut(_interceptingWriter);

if (!OperatingSystem.IsWindowsVersionAtLeast(10))
return;

Expand All @@ -41,3 +61,114 @@ public static void EnableConsoleColorOutput()
[DllImport("kernel32.dll")]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
}


public sealed class LineInterceptingTextWriter : TextWriter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need that? Doesn't NLog have something that would make it simpler for us?

{
// Event raised every time a full line ending with Environment.NewLine is written
public event EventHandler<string>? LineWritten;

// The "real" underlying writer (i.e., the original Console.Out)
private readonly TextWriter _underlyingWriter;

// Buffer used to accumulate written data until we detect a new line
private readonly StringBuilder _buffer;

public LineInterceptingTextWriter(TextWriter underlyingWriter)
{
_underlyingWriter = underlyingWriter ?? throw new ArgumentNullException(nameof(underlyingWriter));
_buffer = new StringBuilder();
}

// You must override Encoding, even if just forwarding
public override Encoding Encoding => _underlyingWriter.Encoding;

// Overriding WriteLine(string) is handy for direct calls to Console.WriteLine(...).
// However, you also want to handle the general case in Write(string).
public override void WriteLine(string? value)
{
Write(value);
Write(Environment.NewLine);
}

public override void Write(string? value)
{
if (value == null)
{
return;
}

// Append to the buffer
_buffer.Append(value);

// Pass the data along to the underlying writer
_underlyingWriter.Write(value);

// Check if we can extract lines from the buffer
CheckForLines();
}

public override void Write(char value)
{
_buffer.Append(value);
_underlyingWriter.Write(value);
CheckForLines();
}

public override void Flush()
{
base.Flush();
_underlyingWriter.Flush();
}

private void CheckForLines()
{
// Environment.NewLine might be "\r\n" or "\n" depending on platform
// so let's find each occurrence and split it off
string newLine = Environment.NewLine;

while (true)
{
// Find the next index of the new line
int newLinePos = _buffer.ToString().IndexOf(newLine, StringComparison.Ordinal);

// If there's no complete new line, break
if (newLinePos < 0)
{
break;
}

// Extract the line up to the new line
string line = _buffer.ToString(0, newLinePos);

// Remove that portion (including the new line) from the buffer
_buffer.Remove(0, newLinePos + newLine.Length);

// Raise the event
OnLineWritten(line);
}
}

public string[] GetRecentMessages()
{
lock (_recentMessages)
{
return _recentMessages.ToArray();
}
}

Queue<string> _recentMessages = new(capacity: 100);
private void OnLineWritten(string line)
{
lock (_recentMessages)
{
if (_recentMessages.Count > 100)
{
_recentMessages.Dequeue();
}
_recentMessages.Enqueue(line);
}
// Raise the event, if subscribed
LineWritten?.Invoke(this, line);
}
}
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public async Task Start(CancellationToken cancellationToken)
s.AddSingleton(_jsonRpcUrlCollection);
s.AddSingleton(_webSocketsManager);
s.AddSingleton(_rpcAuthentication);
s.AddSingleton(_api);
foreach (var plugin in _api.Plugins.OfType<INethermindServicesPlugin>())
{
plugin.AddServices(s);
Expand Down
Loading
Loading