Class Sealed
public sealed class LazyDebugStats

Namespace: Moka.Blazor.Json.Models

Debug statistics for lazy JSON document source. Used by Moka.Blazor.Json.Diagnostics to render the debug overlay.

Properties

NameDescription
AvgParseTime Average subtree parse time.
CacheHitRate Cache hit rate as a percentage.
CacheHits Number of times a cached subtree was reused.
CacheMisses Number of times a subtree had to be parsed fresh (cache miss).
CoveragePercent Percentage of unique document bytes that have been parsed.
CumulativeBytesParsed Cumulative bytes parsed across all parse calls (may double-count overlapping regions).
IndexEntries Number of entries in the structural index.
LazyIndexOps Number of lazy child-indexing operations.
MaxParseTime Slowest subtree parse.
MinParseTime Fastest subtree parse.
RecentParses Recent parse operations with path, size, and duration.
SubtreeParseCount Total number of subtree parse operations (including overlapping regions).
TotalBytes Total document size in bytes.
TotalParseTime Total time spent parsing subtrees.
UniqueBytesParsed Unique bytes covered by at least one parse (no double-counting).

AvgParseTime

TimeSpan LazyDebugStats.AvgParseTime { get; }

Average subtree parse time.

CacheHitRate

double LazyDebugStats.CacheHitRate { get; }

Cache hit rate as a percentage.

CacheHits

int LazyDebugStats.CacheHits { get; }

Number of times a cached subtree was reused.

CacheMisses

int LazyDebugStats.CacheMisses { get; }

Number of times a subtree had to be parsed fresh (cache miss).

CoveragePercent

double LazyDebugStats.CoveragePercent { get; }

Percentage of unique document bytes that have been parsed.

CumulativeBytesParsed

long LazyDebugStats.CumulativeBytesParsed { get; set; }

Cumulative bytes parsed across all parse calls (may double-count overlapping regions).

IndexEntries

int LazyDebugStats.IndexEntries { get; set; }

Number of entries in the structural index.

LazyIndexOps

int LazyDebugStats.LazyIndexOps { get; }

Number of lazy child-indexing operations.

MaxParseTime

TimeSpan LazyDebugStats.MaxParseTime { get; set; }

Slowest subtree parse.

MinParseTime

TimeSpan LazyDebugStats.MinParseTime { get; set; }

Fastest subtree parse.

RecentParses

List<LazyParseEntry> LazyDebugStats.RecentParses { get; }

Recent parse operations with path, size, and duration.

SubtreeParseCount

int LazyDebugStats.SubtreeParseCount { get; set; }

Total number of subtree parse operations (including overlapping regions).

TotalBytes

long LazyDebugStats.TotalBytes { get; set; }

Total document size in bytes.

TotalParseTime

TimeSpan LazyDebugStats.TotalParseTime { get; set; }

Total time spent parsing subtrees.

UniqueBytesParsed

long LazyDebugStats.UniqueBytesParsed { get; set; }

Unique bytes covered by at least one parse (no double-counting).

Methods

NameDescription
RecordCacheHit() Records a cache hit.
RecordCacheMiss() Records a cache miss.
RecordLazyIndex() Records a lazy index operation.
RecordParse(…) Records a subtree parse operation.

RecordCacheHit()

void LazyDebugStats.RecordCacheHit()

Records a cache hit.

RecordCacheMiss()

void LazyDebugStats.RecordCacheMiss()

Records a cache miss.

RecordLazyIndex()

void LazyDebugStats.RecordLazyIndex()

Records a lazy index operation.

RecordParse(string path, long startOffset, long length, TimeSpan duration)

void LazyDebugStats.RecordParse(string path, long startOffset, long length, TimeSpan duration)

Records a subtree parse operation.

View Source
/// <summary>
///     Debug statistics for lazy JSON document source.
///     Used by Moka.Blazor.Json.Diagnostics to render the debug overlay.
/// </summary>
public sealed class LazyDebugStats
{
    private readonly Lock _lock = new();
    private readonly List<ParsedRegion> _parsedRegions = [];
    private int _cacheHits;
    private int _cacheMisses;
    private int _lazyIndexOps;
    /// <summary>Total document size in bytes.</summary>
    public long TotalBytes { get; set; }
    /// <summary>Number of entries in the structural index.</summary>
    public int IndexEntries { get; set; }
    /// <summary>Total number of subtree parse operations (including overlapping regions).</summary>
    public int SubtreeParseCount { get; private set; }
    /// <summary>Cumulative bytes parsed across all parse calls (may double-count overlapping regions).</summary>
    public long CumulativeBytesParsed { get; private set; }
    /// <summary>Unique bytes covered by at least one parse (no double-counting).</summary>
    public long UniqueBytesParsed { get; private set; }
    /// <summary>Number of times a cached subtree was reused.</summary>
    public int CacheHits => _cacheHits;
    /// <summary>Number of times a subtree had to be parsed fresh (cache miss).</summary>
    public int CacheMisses => _cacheMisses;
    /// <summary>Number of lazy child-indexing operations.</summary>
    public int LazyIndexOps => _lazyIndexOps;
    /// <summary>Total time spent parsing subtrees.</summary>
    public TimeSpan TotalParseTime { get; private set; }
    /// <summary>Fastest subtree parse.</summary>
    public TimeSpan MinParseTime { get; private set; } = TimeSpan.MaxValue;
    /// <summary>Slowest subtree parse.</summary>
    public TimeSpan MaxParseTime { get; private set; }
    /// <summary>Average subtree parse time.</summary>
    public TimeSpan AvgParseTime => SubtreeParseCount > 0 ? TotalParseTime / SubtreeParseCount : TimeSpan.Zero;
    /// <summary>Percentage of unique document bytes that have been parsed.</summary>
    public double CoveragePercent => TotalBytes > 0 ? (double)UniqueBytesParsed / TotalBytes * 100 : 0;

    /// <summary>Cache hit rate as a percentage.</summary>
    public double CacheHitRate
    {
        get
        {
            int total = _cacheHits + _cacheMisses;
            return total > 0 ? (double)_cacheHits / total * 100 : 0;
        }
    }

    /// <summary>Recent parse operations with path, size, and duration.</summary>
    public List<LazyParseEntry> RecentParses { get; } = new(20);

    /// <summary>Records a subtree parse operation.</summary>
    public void RecordParse(string path, long startOffset, long length, TimeSpan duration)
    {
        lock (_lock)
        {
            SubtreeParseCount++;
            CumulativeBytesParsed += length;
            TotalParseTime += duration;
            if (duration < MinParseTime)
            {
                MinParseTime = duration;
            }

            if (duration > MaxParseTime)
            {
                MaxParseTime = duration;
            }

            InsertSorted(new ParsedRegion(startOffset, startOffset + length));
            RecalculateUniqueCoverage();
            if (RecentParses.Count >= 20)
            {
                RecentParses.RemoveAt(0);
            }

            RecentParses.Add(new LazyParseEntry(path, length, duration));
        }
    }

    /// <summary>Records a cache hit.</summary>
    public void RecordCacheHit() => Interlocked.Increment(ref _cacheHits);
    /// <summary>Records a cache miss.</summary>
    public void RecordCacheMiss() => Interlocked.Increment(ref _cacheMisses);
    /// <summary>Records a lazy index operation.</summary>
    public void RecordLazyIndex() => Interlocked.Increment(ref _lazyIndexOps);
    /// <summary>
    ///     Inserts a region in sorted order by Start offset using binary search.
    ///     Avoids re-sorting the entire list on every parse operation.
    /// </summary>
    private void InsertSorted(ParsedRegion region)
    {
        int lo = 0, hi = _parsedRegions.Count;
        while (lo < hi)
        {
            int mid = (lo + hi) / 2;
            if (_parsedRegions[mid].Start <= region.Start)
            {
                lo = mid + 1;
            }
            else
            {
                hi = mid;
            }
        }

        _parsedRegions.Insert(lo, region);
    }

    private void RecalculateUniqueCoverage()
    {
        if (_parsedRegions.Count == 0)
        {
            UniqueBytesParsed = 0;
            return;
        }

        // _parsedRegions is maintained in sorted order by InsertSorted
        long uniqueBytes = 0;
        long mergedStart = _parsedRegions[0].Start;
        long mergedEnd = _parsedRegions[0].End;
        for (int i = 1; i < _parsedRegions.Count; i++)
        {
            if (_parsedRegions[i].Start <= mergedEnd)
            {
                mergedEnd = Math.Max(mergedEnd, _parsedRegions[i].End);
            }
            else
            {
                uniqueBytes += mergedEnd - mergedStart;
                mergedStart = _parsedRegions[i].Start;
                mergedEnd = _parsedRegions[i].End;
            }
        }

        uniqueBytes += mergedEnd - mergedStart;
        UniqueBytesParsed = uniqueBytes;
    }

    private readonly record struct ParsedRegion(long Start, long End);
}
Was this page helpful?