Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9b9be47
[`ut`] 100% Coverage Trie.Get (#3952)
shargon May 22, 2025
e254f77
Add OpenTelemetry plugin for comprehensive observability
Jim8y Jul 23, 2025
ba6f233
Add OpenTelemetry observability plugin for Neo N3
Jim8y Jul 24, 2025
21315dd
Add comprehensive node overview dashboard with system metrics
Jim8y Jul 24, 2025
5b80749
Apply code formatting to OpenTelemetry plugin
Jim8y Jul 24, 2025
f74743b
Fix test project configuration for CI compatibility
Jim8y Jul 24, 2025
318d18a
Merge branch 'dev' into feature/opentelemetry
cschuchardt88 Jul 27, 2025
8ee278f
Merge branch 'dev' into feature/opentelemetry
Jim8y Jul 31, 2025
c4b9b0a
Merge branch 'dev' into feature/opentelemetry
Jim8y Jul 31, 2025
e4bcdae
address shargon's comments
Jim8y Jul 31, 2025
5eb651c
delete unnecessary files
Jim8y Jul 31, 2025
ced50d7
Merge branch 'feature/opentelemetry' of github.com:neo-project/neo in…
Jim8y Jul 31, 2025
375ac02
remove unnecessary files
Jim8y Jul 31, 2025
3941da7
Add comprehensive OpenTelemetry observability plugin for Neo blockchain
Jim8y Jul 31, 2025
59afa1a
Apply suggestions from code review
shargon Aug 4, 2025
d245222
refactor(opentelemetry): apply PR feedback improvements
Jim8y Aug 7, 2025
e766067
docs: document core modifications issue and refactoring plan
Jim8y Aug 7, 2025
1d56de8
refactor(opentelemetry): implement metrics collection without core mo…
Jim8y Aug 7, 2025
d248491
feat(opentelemetry): complete production-ready implementation
Jim8y Aug 7, 2025
767b9ac
feat(opentelemetry): enhance telemetry system with professional monit…
Jim8y Aug 11, 2025
d16566f
feat(opentelemetry): add deployment verification and startup scripts
Jim8y Aug 11, 2025
07f0770
merge: resolve conflicts with master branch
Jim8y Aug 11, 2025
b3299d2
merge: resolve conflicts with dev branch
Jim8y Aug 11, 2025
54e4f39
fix: update OpenTelemetry packages to resolve security vulnerability
Jim8y Aug 11, 2025
f8add87
chore: clean up PR - remove unnecessary files and core modifications
Jim8y Aug 11, 2025
92089ff
fix: resolve merge conflicts and apply code formatting
Jim8y Aug 11, 2025
355c395
fix: add nullable reference types to test project
Jim8y Aug 11, 2025
b02ef9e
fix: use Assert.ThrowsExactly instead of Assert.ThrowsException
Jim8y Aug 11, 2025
7fde360
fix: simplify OTelPlugin tests to resolve compilation errors
Jim8y Aug 11, 2025
ae7021d
feat: add comprehensive monitoring setup with dashboards and alerts
Jim8y Aug 11, 2025
383a456
feat(monitoring): enhance Neo dashboard with comprehensive metrics
Jim8y Aug 11, 2025
de82fa3
feat(monitoring): add validation scripts and fix docker-compose
Jim8y Aug 11, 2025
48adf4d
feat(monitoring): add testing infrastructure and verification tools
Jim8y Aug 11, 2025
f3e0415
feat(monitoring): implement production-ready monitoring with professi…
Jim8y Aug 11, 2025
737248b
feat(dashboard): implement enterprise-grade professional monitoring d…
Jim8y Aug 11, 2025
a05f56b
fix(dashboard): remove all sample data - 100% real metrics only
Jim8y Aug 11, 2025
b0016a6
Merge branch 'dev' into feature/opentelemetry
NGDAdmin Aug 12, 2025
e343ca0
Merge branch 'dev' into feature/opentelemetry
shargon Aug 13, 2025
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
362 changes: 362 additions & 0 deletions neo.sln

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions src/Neo/IEventHandlers/IMemPoolMetricsHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// IMemPoolMetricsHandler.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Ledger;

namespace Neo.IEventHandlers
{
/// <summary>
/// Interface for plugins that need to collect memory pool metrics
/// </summary>
public interface IMemPoolMetricsHandler
{
/// <summary>
/// Called periodically with memory pool statistics
/// </summary>
/// <param name="memPool">The memory pool instance</param>
/// <param name="stats">Current memory pool statistics</param>
void MemPool_StatsSnapshot_Handler(MemoryPool memPool, MemPoolStats stats);
}

/// <summary>
/// Memory pool statistics snapshot
/// </summary>
public class MemPoolStats
{
public int Count { get; set; }
public int VerifiedCount { get; set; }
public int UnverifiedCount { get; set; }
public int Capacity { get; set; }
public long TotalMemoryBytes { get; set; }
public int ConflictsCount { get; set; }
public int LastBatchRemovedCount { get; set; }
}
}
60 changes: 60 additions & 0 deletions src/Neo/IEventHandlers/INetworkMetricsHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// INetworkMetricsHandler.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Akka.Actor;
using Neo.Network.P2P;
using System.Collections.Generic;

namespace Neo.IEventHandlers
{
/// <summary>
/// Interface for plugins that need to collect network metrics
/// </summary>
public interface INetworkMetricsHandler
{
/// <summary>
/// Called when a peer connects to the network
/// </summary>
/// <param name="node">The local node instance</param>
/// <param name="peer">The connected peer</param>
void Network_PeerConnected_Handler(LocalNode node, IActorRef peer);

/// <summary>
/// Called when a peer disconnects from the network
/// </summary>
/// <param name="node">The local node instance</param>
/// <param name="peer">The disconnected peer</param>
void Network_PeerDisconnected_Handler(LocalNode node, IActorRef peer);

/// <summary>
/// Called periodically with network statistics
/// </summary>
/// <param name="node">The local node instance</param>
/// <param name="stats">Current network statistics</param>
void Network_StatsSnapshot_Handler(LocalNode node, NetworkStats stats);
}

/// <summary>
/// Network statistics snapshot
/// </summary>
public class NetworkStats
{
public int ConnectedPeers { get; set; }
public int UnconnectedPeers { get; set; }
public long BytesSent { get; set; }
public long BytesReceived { get; set; }
public int PendingTasks { get; set; }
public int HighPriorityQueueSize { get; set; }
public int LowPriorityQueueSize { get; set; }
public Dictionary<string, long> MessagesSentByType { get; set; } = new Dictionary<string, long>();
public Dictionary<string, long> MessagesReceivedByType { get; set; } = new Dictionary<string, long>();
}
}
61 changes: 61 additions & 0 deletions src/Neo/IEventHandlers/IStorageMetricsHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (C) 2015-2025 The Neo Project.
//
// IStorageMetricsHandler.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Persistence;
using System;

namespace Neo.IEventHandlers
{
/// <summary>
/// Interface for plugins that need to collect storage metrics
/// </summary>
public interface IStorageMetricsHandler
{
/// <summary>
/// Called when a storage read operation occurs
/// </summary>
/// <param name="store">The store instance</param>
/// <param name="key">The key being read</param>
/// <param name="found">Whether the key was found</param>
/// <param name="duration">Time taken for the operation</param>
void Storage_Read_Handler(IStore store, byte[] key, bool found, TimeSpan duration);

/// <summary>
/// Called when a storage write operation occurs
/// </summary>
/// <param name="store">The store instance</param>
/// <param name="key">The key being written</param>
/// <param name="valueSize">Size of the value in bytes</param>
/// <param name="duration">Time taken for the operation</param>
void Storage_Write_Handler(IStore store, byte[] key, int valueSize, TimeSpan duration);

/// <summary>
/// Called periodically with storage statistics
/// </summary>
/// <param name="store">The store instance</param>
/// <param name="stats">Current storage statistics</param>
void Storage_StatsSnapshot_Handler(IStore store, StorageStats stats);
}

/// <summary>
/// Storage statistics snapshot
/// </summary>
public class StorageStats
{
public long TotalReads { get; set; }
public long TotalWrites { get; set; }
public long CacheHits { get; set; }
public long CacheMisses { get; set; }
public double CacheHitRate => TotalReads > 0 ? (double)CacheHits / TotalReads : 0;
public long StorageSizeBytes { get; set; }
public int SnapshotCount { get; set; }
}
}
127 changes: 127 additions & 0 deletions src/Neo/Ledger/MemoryPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#nullable enable

using Neo.Extensions;
using Neo.IEventHandlers;
using Neo.Network.P2P;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
Expand All @@ -32,6 +34,18 @@ public class MemoryPool : IReadOnlyCollection<Transaction>
public event EventHandler<Transaction>? TransactionAdded;
public event EventHandler<TransactionRemovedEventArgs>? TransactionRemoved;

// MemPool metrics event delegates
public delegate void MemPoolStatsSnapshotHandler(MemoryPool memPool, MemPoolStats stats);

// MemPool metrics events
public static event MemPoolStatsSnapshotHandler? MemPoolStatsSnapshot;

// Metrics tracking fields
private readonly Timer? _metricsTimer;
private int _lastBatchRemovedCount;
private int _conflictsCount;
private readonly object _metricsLock = new object();

// Allow a reverified transaction to be rebroadcast if it has been this many block times since last broadcast.
private const int BlocksTillRebroadcast = 10;
private int RebroadcastMultiplierThreshold => Capacity / 10;
Expand Down Expand Up @@ -130,6 +144,12 @@ public MemoryPool(NeoSystem system)
var timePerBlock = system.GetTimePerBlock().TotalMilliseconds;
MaxMillisecondsToReverifyTx = timePerBlock / 3;
MaxMillisecondsToReverifyTxPerIdle = timePerBlock / 15;

// Start metrics collection timer if there are handlers
if (MemPoolStatsSnapshot != null)
{
_metricsTimer = new Timer(CollectMemPoolStats, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
}

/// <summary>
Expand Down Expand Up @@ -400,6 +420,10 @@ private bool CheckConflicts(Transaction tx, out List<PoolItem> conflictsList)
// Step 3: take into account sender's conflicting transactions while balance check,
// this will be done in VerifyStateDependant.

// Track conflicts if any were found
if (conflictsList.Count > 0)
IncrementConflictsCount();

return true;
}

Expand All @@ -422,6 +446,10 @@ private List<Transaction> RemoveOverCapacity()
}
} while (Count > Capacity);

// Track the number of transactions removed
if (removedTransactions.Count > 0)
SetLastBatchRemovedCount(removedTransactions.Count);

return removedTransactions;
}

Expand Down Expand Up @@ -699,6 +727,105 @@ internal void Clear()
_txRwLock.ExitReadLock();
}
}

// Event invocation methods
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void InvokeMemPoolStatsSnapshot(MemoryPool memPool, MemPoolStats stats)
{
InvokeHandlers(MemPoolStatsSnapshot?.GetInvocationList(), h => ((MemPoolStatsSnapshotHandler)h)(memPool, stats));
}

private static void InvokeHandlers(Delegate[]? handlers, Action<Delegate> handlerAction)
{
if (handlers == null) return;

foreach (var handler in handlers)
{
try
{
// skip stopped plugin.
if (handler.Target is Neo.Plugins.Plugin { IsStopped: true })
{
continue;
}

handlerAction(handler);
}
catch (Exception ex) when (handler.Target is Neo.Plugins.Plugin plugin)
{
Utility.Log(nameof(plugin), LogLevel.Error, ex);
switch (plugin.ExceptionPolicy)
{
case Neo.Plugins.UnhandledExceptionPolicy.StopNode:
throw;
case Neo.Plugins.UnhandledExceptionPolicy.StopPlugin:
//Stop plugin on exception
plugin.IsStopped = true;
break;
case Neo.Plugins.UnhandledExceptionPolicy.Ignore:
// Log the exception and continue with the next handler
break;
}
}
}
}

private void CollectMemPoolStats(object? state)
{
try
{
long totalMemoryBytes = 0;
_txRwLock.EnterReadLock();
try
{
// Calculate total memory usage
foreach (var tx in _unsortedTransactions.Values)
{
totalMemoryBytes += tx.Tx.Size;
}
foreach (var tx in _unverifiedTransactions.Values)
{
totalMemoryBytes += tx.Tx.Size;
}
}
finally
{
_txRwLock.ExitReadLock();
}

var stats = new MemPoolStats
{
Count = Count,
VerifiedCount = VerifiedCount,
UnverifiedCount = UnVerifiedCount,
Capacity = Capacity,
TotalMemoryBytes = totalMemoryBytes,
ConflictsCount = _conflictsCount,
LastBatchRemovedCount = _lastBatchRemovedCount
};

InvokeMemPoolStatsSnapshot(this, stats);
}
catch (Exception ex)
{
Utility.Log(nameof(MemoryPool), LogLevel.Error, ex);
}
}

public void IncrementConflictsCount()
{
Interlocked.Increment(ref _conflictsCount);
}

public void SetLastBatchRemovedCount(int count)
{
Interlocked.Exchange(ref _lastBatchRemovedCount, count);
}

public void Dispose()
{
_metricsTimer?.Dispose();
}
}
}

Expand Down
Loading
Loading