From 880b253d1261252cdeb784c125ea637c9ca1cd0e Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Mon, 15 Dec 2025 12:36:35 -0300 Subject: [PATCH] eventstore/mmm: stats. --- eventstore/mmm/fuzz_test.go | 10 +++ eventstore/mmm/stats.go | 135 ++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 eventstore/mmm/stats.go diff --git a/eventstore/mmm/fuzz_test.go b/eventstore/mmm/fuzz_test.go index 5d23c83..a9c0203 100644 --- a/eventstore/mmm/fuzz_test.go +++ b/eventstore/mmm/fuzz_test.go @@ -99,6 +99,16 @@ func FuzzTest(f *testing.F) { count++ } require.Equal(t, count, len(storedByLayer[layer.name]), "layer %d ('%s')", i, layer.name) + + // call ComputeStats + stats, err := layer.ComputeStats() + require.NoError(t, err, "ComputeStats failed for layer %d ('%s')", i, layer.name) + require.NotNil(t, stats, "ComputeStats returned nil for layer %d ('%s')", i, layer.name) + require.Equal(t, stats.Total, uint(count)) + if count > 0 { + require.GreaterOrEqual(t, len(stats.PerWeek), 1) + require.Len(t, stats.PerPubKeyPrefix, 1) + } } // randomly select n events to delete from random layers diff --git a/eventstore/mmm/stats.go b/eventstore/mmm/stats.go new file mode 100644 index 0000000..1845372 --- /dev/null +++ b/eventstore/mmm/stats.go @@ -0,0 +1,135 @@ +package mmm + +import ( + "encoding/binary" + "time" + + "fiatjaf.com/nostr" + "github.com/PowerDNS/lmdb-go/lmdb" +) + +type EventStats struct { + Total uint + PerWeek []uint + PerPubKeyPrefix map[string]PubKeyStats + PerKind map[nostr.Kind]KindStats +} + +type KindStats struct { + Total uint + PerWeek []uint +} + +type PubKeyStats struct { + Total uint + PerWeek []uint + PerKind map[nostr.Kind]uint + PerKindPerWeek map[nostr.Kind][]uint +} + +func (il *IndexingLayer) ComputeStats() (*EventStats, error) { + stats := &EventStats{ + Total: 0, + PerWeek: make([]uint, 0, 24), + PerPubKeyPrefix: make(map[string]PubKeyStats, 30), + PerKind: make(map[nostr.Kind]KindStats, 20), + } + + err := il.lmdbEnv.View(func(txn *lmdb.Txn) error { + cursor, err := txn.OpenCursor(il.indexPubkeyKind) + if err != nil { + return err + } + defer cursor.Close() + + for { + key, _, err := cursor.Get(nil, nil, lmdb.Next) + if lmdb.IsNotFound(err) { + break + } + if err != nil { + return err + } + + if len(key) < 14 { + continue + } + + // parse key: [8 bytes pubkey][2 bytes kind][4 bytes timestamp] + pubkeyPrefix := nostr.HexEncodeToString(key[0:8]) + kind := nostr.Kind(binary.BigEndian.Uint16(key[8:10])) + createdTime := time.Unix(int64(binary.BigEndian.Uint32(key[10:14])), 0) + + // figure out how many weeks in the past this is + weekIndex := weeksInPast(createdTime) + + // update totals + stats.Total++ + if weekIndex >= 0 { + for len(stats.PerWeek) <= weekIndex { + stats.PerWeek = append(stats.PerWeek, 0) + } + stats.PerWeek[weekIndex]++ + } + if this, exists := stats.PerPubKeyPrefix[pubkeyPrefix]; exists { + this.Total++ + this.PerKind[kind]++ + if weekIndex >= 0 { + for len(this.PerWeek) <= weekIndex { + this.PerWeek = append(this.PerWeek, 0) + } + this.PerWeek[weekIndex]++ + } + stats.PerPubKeyPrefix[pubkeyPrefix] = this + } else { + stats.PerPubKeyPrefix[pubkeyPrefix] = PubKeyStats{ + Total: 1, + PerKind: map[nostr.Kind]uint{ + kind: 1, + }, + } + } + if this, exists := stats.PerKind[kind]; exists { + this.Total++ + if weekIndex >= 0 { + for len(this.PerWeek) <= weekIndex { + this.PerWeek = append(this.PerWeek, 0) + } + this.PerWeek[weekIndex]++ + } + stats.PerKind[kind] = this + } else { + stats.PerKind[kind] = KindStats{ + Total: 1, + } + } + } + + return nil + }) + + return stats, err +} + +func weeksInPast(date time.Time) int { + now := time.Now() + + if date.After(now) { + // when in the future always return -1 + return -1 + } + + lastSaturday := now.AddDate(0, 0, -int(now.Weekday()+1)) + lastSaturday = time.Date(lastSaturday.Year(), lastSaturday.Month(), lastSaturday.Day(), 23, 59, 59, 0, lastSaturday.Location()) + + // if the date is after the last completed Saturday, it's in the current incomplete week + if date.After(lastSaturday) { + return 0 + } + + // calculate the number of complete weeks between the date and last saturday + daysDiff := int(lastSaturday.Sub(date).Hours() / 24) + completeWeeks := (daysDiff / 7) + 1 // +1 because we've already passed at least one Saturday + + return completeWeeks +}