From f518e513d4f9eda88d6808e44da2eaa9d31c2daf Mon Sep 17 00:00:00 2001 From: scooletz Date: Thu, 22 Jun 2023 14:07:37 +0200 Subject: [PATCH 1/4] reporting --- src/Paprika.Runner/Program.cs | 8 ++++--- src/Paprika/Chain/Blockchain.cs | 3 +++ src/Paprika/Data/HashingMap.cs | 9 ++++++++ src/Paprika/IReadOnlyBatch.cs | 6 ++--- src/Paprika/Paprika.csproj | 1 + src/Paprika/Store/DataPage.cs | 30 +++++++++++++++++++++++- src/Paprika/Store/IReporter.cs | 41 +++++++++++++++++++++++++++++++++ src/Paprika/Store/PagedDb.cs | 22 ++++++++++++++++++ 8 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 src/Paprika/Store/IReporter.cs diff --git a/src/Paprika.Runner/Program.cs b/src/Paprika.Runner/Program.cs index 4f66e30a..5b658cdc 100644 --- a/src/Paprika.Runner/Program.cs +++ b/src/Paprika.Runner/Program.cs @@ -14,8 +14,7 @@ namespace Paprika.Runner; public static class Program { - private const int BlockCount = PersistentDb ? 25_000 : 3_000; - private const int RandomSampleSize = 260_000_000; + private const int BlockCount = PersistentDb ? 5_000 : 3_000; private const int AccountsPerBlock = 1000; private const int MaxReorgDepth = 64; private const int FinalizeEvery = 32; @@ -131,7 +130,7 @@ public static async Task Main(String[] args) } // waiting for finalization - var read = db.BeginReadOnlyBatch(); + using var read = db.BeginReadOnlyBatch(); var readingStopWatch = Stopwatch.StartNew(); random = BuildRandom(); @@ -184,6 +183,9 @@ public static async Task Main(String[] args) // the final report ReportReading(counter); + var statistics = new StatisticsReporter(); + read.Report(statistics); + spectre.Cancel(); await reportingTask; diff --git a/src/Paprika/Chain/Blockchain.cs b/src/Paprika/Chain/Blockchain.cs index 867f8073..4da89560 100644 --- a/src/Paprika/Chain/Blockchain.cs +++ b/src/Paprika/Chain/Blockchain.cs @@ -278,6 +278,9 @@ public bool TryGet(in Key key, out ReadOnlySpan result) Dispose(); } } + + public void Report(IReporter reporter) => + throw new NotImplementedException("One should not report over a block"); } /// diff --git a/src/Paprika/Data/HashingMap.cs b/src/Paprika/Data/HashingMap.cs index c1531c3d..522e94b5 100644 --- a/src/Paprika/Data/HashingMap.cs +++ b/src/Paprika/Data/HashingMap.cs @@ -84,6 +84,15 @@ public bool TryGet(uint hash, in Key key, out ReadOnlySpan value) return false; } + public int Count + { + get + { + var indexOf = _hashes.IndexOf(NoHash); + return indexOf == IndexOfNotFound ? _hashes.Length : indexOf; + } + } + private Span GetEntry(int position) => _entries.Slice(position * EntrySize, EntrySize); public bool TrySet(uint hash, in Key key, ReadOnlySpan value) diff --git a/src/Paprika/IReadOnlyBatch.cs b/src/Paprika/IReadOnlyBatch.cs index 0b89ec5c..17ae2612 100644 --- a/src/Paprika/IReadOnlyBatch.cs +++ b/src/Paprika/IReadOnlyBatch.cs @@ -1,6 +1,4 @@ -using Nethermind.Int256; -using Paprika.Crypto; -using Paprika.Data; +using Paprika.Data; using Paprika.Store; namespace Paprika; @@ -16,4 +14,6 @@ public interface IReadOnlyBatch : IDisposable /// /// bool TryGet(in Key key, out ReadOnlySpan result); + + void Report(IReporter reporter); } diff --git a/src/Paprika/Paprika.csproj b/src/Paprika/Paprika.csproj index 5a881c79..052cfa43 100644 --- a/src/Paprika/Paprika.csproj +++ b/src/Paprika/Paprika.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Paprika/Store/DataPage.cs b/src/Paprika/Store/DataPage.cs index cb267eae..0a621e02 100644 --- a/src/Paprika/Store/DataPage.cs +++ b/src/Paprika/Store/DataPage.cs @@ -38,7 +38,7 @@ public struct Payload { private const int Size = Page.PageSize - PageHeader.Size; - private const int BucketCount = 16; + public const int BucketCount = 16; /// /// The size of the raw byte data held in this page. Must be long aligned. @@ -248,6 +248,34 @@ public bool TryGet(uint hash, Key key, IReadOnlyBatchContext batch, out ReadOnly return false; } + public void Report(IReporter reporter, IPageResolver resolver, int level) + { + var emptyBuckets = 0; + + foreach (var bucket in Data.Buckets) + { + if (bucket.IsNull) + { + emptyBuckets++; + } + else + { + new DataPage(resolver.GetAt(bucket)).Report(reporter, resolver, level + 1); + } + } + + if (emptyBuckets == 0) + { + // all filled + reporter.Report(level, 0, Payload.BucketCount, new HashingMap(Data.DataSpan).Count); + } + else + { + reporter.Report(level, emptyBuckets, Payload.BucketCount - emptyBuckets, + new NibbleBasedMap(Data.DataSpan).Count); + } + } + private static bool TryFindExistingStorageTreeForCellOf(in NibbleBasedMap map, in Key key, out DbAddress storageTreeAddress) { diff --git a/src/Paprika/Store/IReporter.cs b/src/Paprika/Store/IReporter.cs new file mode 100644 index 00000000..2747e223 --- /dev/null +++ b/src/Paprika/Store/IReporter.cs @@ -0,0 +1,41 @@ +using HdrHistogram; + +namespace Paprika.Store; + +/// +/// Provides capability to report stats for . +/// +public interface IReporter +{ + void Report(int level, int emptyBuckets, int filledBuckets, int entriesPerPage); +} + +public class StatisticsReporter : IReporter +{ + private readonly Dictionary _levels = new(); + + public void Report(int level, int emptyBuckets, int filledBuckets, int entriesPerPage) + { + if (_levels.TryGetValue(level, out var lvl) == false) + { + lvl = _levels[level] = new Level(); + } + + try + { + lvl.ChildCount.RecordValue(filledBuckets); + lvl.Entries.RecordValue(entriesPerPage); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + private class Level + { + public readonly IntHistogram ChildCount = new(1000, 5); + public readonly IntHistogram Entries = new(1000, 5); + } +} \ No newline at end of file diff --git a/src/Paprika/Store/PagedDb.cs b/src/Paprika/Store/PagedDb.cs index 97000760..902926d0 100644 --- a/src/Paprika/Store/PagedDb.cs +++ b/src/Paprika/Store/PagedDb.cs @@ -270,6 +270,17 @@ public bool TryGet(in Key key, out ReadOnlySpan result) return new DataPage(GetAt(addr)).TryGet(hash, sliced, this, out result); } + public void Report(IReporter reporter) + { + foreach (var addr in _rootDataPages) + { + if (addr.IsNull == false) + { + new DataPage(GetAt(addr)).Report(reporter, this, 1); + } + } + } + public uint BatchId { get; } public Page GetAt(DbAddress address) => _db._manager.GetAt(address); @@ -388,6 +399,17 @@ private void CheckDisposed() } } + public void Report(IReporter reporter) + { + foreach (var addr in _root.Data.AccountPages) + { + if (addr.IsNull == false) + { + new DataPage(GetAt(addr)).Report(reporter, this, 1); + } + } + } + public async ValueTask Commit(CommitOptions options) { var watch = Stopwatch.StartNew(); From 8c845301b17a4e8e92bcf56c89533c3e1f57006f Mon Sep 17 00:00:00 2001 From: scooletz Date: Thu, 22 Jun 2023 18:55:52 +0200 Subject: [PATCH 2/4] percentiles written properly --- src/Paprika.Runner/Program.cs | 21 ++++++++++++++++++--- src/Paprika/Store/IReporter.cs | 8 ++++---- src/Paprika/Store/Page.cs | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/Paprika.Runner/Program.cs b/src/Paprika.Runner/Program.cs index 5b658cdc..fb81fdd1 100644 --- a/src/Paprika.Runner/Program.cs +++ b/src/Paprika.Runner/Program.cs @@ -14,7 +14,7 @@ namespace Paprika.Runner; public static class Program { - private const int BlockCount = PersistentDb ? 5_000 : 3_000; + private const int BlockCount = PersistentDb ? 25_000 : 3_000; private const int AccountsPerBlock = 1000; private const int MaxReorgDepth = 64; private const int FinalizeEvery = 32; @@ -183,8 +183,23 @@ public static async Task Main(String[] args) // the final report ReportReading(counter); - var statistics = new StatisticsReporter(); - read.Report(statistics); + var stats = new StatisticsReporter(); + read.Report(stats); + var table = new Table(); + + table.AddColumn(new TableColumn("Level of Paprika tree")); + table.AddColumn(new TableColumn("Child page count")); + table.AddColumn(new TableColumn("Entries in page")); + + foreach (var (key, level) in stats.Levels) + { + table.AddRow( + new Text(key.ToString()), + new Text(level.ChildCount.GetValueAtPercentile(90).ToString()), + new Text(level.Entries.GetValueAtPercentile(90).ToString())); + } + + layout[info].Update(new Panel(table.Expand()).Header("Paprika tree statistics").Expand()); spectre.Cancel(); await reportingTask; diff --git a/src/Paprika/Store/IReporter.cs b/src/Paprika/Store/IReporter.cs index 2747e223..c4da707d 100644 --- a/src/Paprika/Store/IReporter.cs +++ b/src/Paprika/Store/IReporter.cs @@ -12,13 +12,13 @@ public interface IReporter public class StatisticsReporter : IReporter { - private readonly Dictionary _levels = new(); + public readonly SortedDictionary Levels = new(); public void Report(int level, int emptyBuckets, int filledBuckets, int entriesPerPage) { - if (_levels.TryGetValue(level, out var lvl) == false) + if (Levels.TryGetValue(level, out var lvl) == false) { - lvl = _levels[level] = new Level(); + lvl = Levels[level] = new Level(); } try @@ -33,7 +33,7 @@ public void Report(int level, int emptyBuckets, int filledBuckets, int entriesPe } } - private class Level + public class Level { public readonly IntHistogram ChildCount = new(1000, 5); public readonly IntHistogram Entries = new(1000, 5); diff --git a/src/Paprika/Store/Page.cs b/src/Paprika/Store/Page.cs index ee671eb0..6008a79a 100644 --- a/src/Paprika/Store/Page.cs +++ b/src/Paprika/Store/Page.cs @@ -87,7 +87,7 @@ public enum PageType : byte /// public readonly unsafe struct Page : IPage, IEquatable { - public const int PageCount = 0x0100_0000; // 64GB addressable + public const int PageCount = 0x1000_0000; // 64GB addressable public const int PageAddressMask = PageCount - 1; public const int PageSize = 4 * 1024; From e8a83a2e40030822338be120a9827d53c7c8a7c7 Mon Sep 17 00:00:00 2001 From: scooletz Date: Thu, 22 Jun 2023 19:59:28 +0200 Subject: [PATCH 3/4] a nicer reporting --- src/Paprika.Runner/Program.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Paprika.Runner/Program.cs b/src/Paprika.Runner/Program.cs index fb81fdd1..d6f663ea 100644 --- a/src/Paprika.Runner/Program.cs +++ b/src/Paprika.Runner/Program.cs @@ -1,12 +1,14 @@ using System.Buffers.Binary; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using HdrHistogram; using Nethermind.Int256; using Paprika.Chain; using Paprika.Crypto; using Paprika.Store; using Paprika.Tests; using Spectre.Console; +using Spectre.Console.Rendering; [assembly: ExcludeFromCodeCoverage] @@ -26,7 +28,7 @@ public static class Program private const long DbFileSize = PersistentDb ? 256 * Gb : 16 * Gb; private const long Gb = 1024 * 1024 * 1024L; - private static readonly TimeSpan FlushEvery = TimeSpan.FromSeconds(5); + private static readonly TimeSpan FlushEvery = TimeSpan.FromSeconds(30); private const int LogEvery = BlockCount / NumberOfLogs; @@ -195,8 +197,8 @@ public static async Task Main(String[] args) { table.AddRow( new Text(key.ToString()), - new Text(level.ChildCount.GetValueAtPercentile(90).ToString()), - new Text(level.Entries.GetValueAtPercentile(90).ToString())); + Report(level.ChildCount), + Report(level.Entries)); } layout[info].Update(new Panel(table.Expand()).Header("Paprika tree statistics").Expand()); @@ -237,6 +239,17 @@ void ReportReading(int i) private static Random BuildRandom() => new(RandomSeed); + private static IRenderable Report(HistogramBase histogram) + { + var table = new Table(); + table.AddColumns("P50", "P90", "P95"); + table.AddRow( + histogram.GetValueAtPercentile(50).ToString(), + histogram.GetValueAtPercentile(90).ToString(), + histogram.GetValueAtPercentile(95).ToString()); + return table.Expand(); + } + private static int Writer(Blockchain blockchain, Keccak bigStorageAccount, Random random, Layout reporting) { From 8f3d4007a59a8010b4784fb742ecb61eabd5d9a2 Mon Sep 17 00:00:00 2001 From: scooletz Date: Fri, 23 Jun 2023 10:16:57 +0200 Subject: [PATCH 4/4] reporting --- src/Paprika.Runner/Program.cs | 29 +++++++++++++++++------------ src/Paprika/Store/DataPage.cs | 4 ++-- src/Paprika/Store/IReporter.cs | 19 +++++++------------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Paprika.Runner/Program.cs b/src/Paprika.Runner/Program.cs index d6f663ea..47563a43 100644 --- a/src/Paprika.Runner/Program.cs +++ b/src/Paprika.Runner/Program.cs @@ -16,7 +16,7 @@ namespace Paprika.Runner; public static class Program { - private const int BlockCount = PersistentDb ? 25_000 : 3_000; + private const int BlockCount = PersistentDb ? 20_000 : 3_000; private const int AccountsPerBlock = 1000; private const int MaxReorgDepth = 64; private const int FinalizeEvery = 32; @@ -197,11 +197,16 @@ public static async Task Main(String[] args) { table.AddRow( new Text(key.ToString()), - Report(level.ChildCount), - Report(level.Entries)); + WriteHistogram(level.ChildCount), + WriteHistogram(level.Entries)); } - layout[info].Update(new Panel(table.Expand()).Header("Paprika tree statistics").Expand()); + var mb = (long)stats.PageCount * Page.PageSize / 1024 / 1024; + var report = new Layout().SplitRows( + new Layout(new Paragraph($"General stats:\n1. Size of this Paprika tree: {mb}MB")).Size(3), + new Layout(table.Expand())); + + layout[info].Update(new Panel(report).Header("Paprika tree statistics").Expand()); spectre.Cancel(); await reportingTask; @@ -239,15 +244,15 @@ void ReportReading(int i) private static Random BuildRandom() => new(RandomSeed); - private static IRenderable Report(HistogramBase histogram) + private static IRenderable WriteHistogram(HistogramBase histogram) { - var table = new Table(); - table.AddColumns("P50", "P90", "P95"); - table.AddRow( - histogram.GetValueAtPercentile(50).ToString(), - histogram.GetValueAtPercentile(90).ToString(), - histogram.GetValueAtPercentile(95).ToString()); - return table.Expand(); + string Percentile(int percentile, string color) + { + var value = histogram.GetValueAtPercentile(percentile); + return $"[{color}]P{percentile}: {value,2}[/] "; + } + + return new Markup(Percentile(50, "green") + Percentile(90, "yellow") + Percentile(95, "red")); } private static int Writer(Blockchain blockchain, Keccak bigStorageAccount, Random random, diff --git a/src/Paprika/Store/DataPage.cs b/src/Paprika/Store/DataPage.cs index 0a621e02..adb2a29f 100644 --- a/src/Paprika/Store/DataPage.cs +++ b/src/Paprika/Store/DataPage.cs @@ -267,11 +267,11 @@ public void Report(IReporter reporter, IPageResolver resolver, int level) if (emptyBuckets == 0) { // all filled - reporter.Report(level, 0, Payload.BucketCount, new HashingMap(Data.DataSpan).Count); + reporter.Report(level, Payload.BucketCount, new HashingMap(Data.DataSpan).Count); } else { - reporter.Report(level, emptyBuckets, Payload.BucketCount - emptyBuckets, + reporter.Report(level, Payload.BucketCount - emptyBuckets, new NibbleBasedMap(Data.DataSpan).Count); } } diff --git a/src/Paprika/Store/IReporter.cs b/src/Paprika/Store/IReporter.cs index c4da707d..24673f60 100644 --- a/src/Paprika/Store/IReporter.cs +++ b/src/Paprika/Store/IReporter.cs @@ -7,30 +7,25 @@ namespace Paprika.Store; /// public interface IReporter { - void Report(int level, int emptyBuckets, int filledBuckets, int entriesPerPage); + void Report(int level, int filledBuckets, int entriesPerPage); } public class StatisticsReporter : IReporter { public readonly SortedDictionary Levels = new(); + public int PageCount = 0; - public void Report(int level, int emptyBuckets, int filledBuckets, int entriesPerPage) + public void Report(int level, int filledBuckets, int entriesPerPage) { if (Levels.TryGetValue(level, out var lvl) == false) { lvl = Levels[level] = new Level(); } - try - { - lvl.ChildCount.RecordValue(filledBuckets); - lvl.Entries.RecordValue(entriesPerPage); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + PageCount++; + + lvl.ChildCount.RecordValue(filledBuckets); + lvl.Entries.RecordValue(entriesPerPage); } public class Level