Compare commits

..

1 Commits

Author SHA1 Message Date
you
5c2c71c6ee fix(#791): update memory-accounting to reflect eliminated spTxIndex
Remove perSubpathEntryBytes constant and all O(path²) subpath cost
calculations from estimateStoreTxBytes and estimateStoreTxBytesTypical.
Update comments to note that spTxIndex was eliminated in #791 and
singleton subpath entries are dropped from spIndex.
2026-04-19 00:08:28 +00:00
3 changed files with 78 additions and 18 deletions

View File

@@ -2145,6 +2145,13 @@ func setupRichTestDB(t *testing.T) *DB {
db.conn.Exec(`INSERT INTO observations (transmission_id, observer_idx, snr, rssi, path_json, timestamp)
VALUES (5, 1, 14.0, -88, '["aa"]', ?)`, recentEpoch)
// Extra packet sharing subpath "eeff,0011" with hash_with_path_02 above,
// so that subpath has count>=2 and survives singleton pruning.
db.conn.Exec(`INSERT INTO transmissions (raw_hex, hash, first_seen, route_type, payload_type, decoded_json)
VALUES ('0140eeff0011', 'hash_shared_subpath', ?, 1, 4, '{"pubKey":"eeff001199887766","name":"TestShared","type":"ADVERT"}')`, recent)
db.conn.Exec(`INSERT INTO observations (transmission_id, observer_idx, snr, rssi, path_json, timestamp)
VALUES (6, 1, 9.0, -92, '["eeff","0011"]', ?)`, recentEpoch)
return db
}
@@ -2276,14 +2283,11 @@ func TestSubpathPrecomputedIndex(t *testing.T) {
t.Fatal("expected spTotalPaths > 0 after Load()")
}
// The rich test DB has paths ["aa","bb"], ["aabb","ccdd"], and
// ["eeff","0011","2233"]. That yields 5 unique raw subpaths.
// The rich test DB has paths ["aa","bb"], ["aabb","ccdd"],
// ["eeff","0011","2233"], and ["eeff","0011"]. After singleton pruning,
// only subpaths with count>=2 survive. "eeff,0011" appears in two packets.
expectedRaw := map[string]int{
"aa,bb": 1,
"aabb,ccdd": 1,
"eeff,0011": 1,
"0011,2233": 1,
"eeff,0011,2233": 1,
"eeff,0011": 2,
}
for key, want := range expectedRaw {
got, ok := store.spIndex[key]
@@ -2293,8 +2297,16 @@ func TestSubpathPrecomputedIndex(t *testing.T) {
t.Errorf("spIndex[%q] = %d, want %d", key, got, want)
}
}
if store.spTotalPaths != 3 {
t.Errorf("spTotalPaths = %d, want 3", store.spTotalPaths)
// Singleton subpaths must have been pruned
singletons := []string{"aa,bb", "aabb,ccdd", "0011,2233", "eeff,0011,2233"}
for _, key := range singletons {
if _, ok := store.spIndex[key]; ok {
t.Errorf("expected singleton spIndex[%q] to be pruned", key)
}
}
if store.spTotalPaths != 4 {
t.Errorf("spTotalPaths = %d, want 4", store.spTotalPaths)
}
// Fast-path (no region) and slow-path (with region) must return the
@@ -2333,8 +2345,8 @@ func TestSubpathTxIndexPopulated(t *testing.T) {
t.Fatal("expected non-nil detail for existing subpath")
}
matches, _ := detail["totalMatches"].(int)
if matches != 1 {
t.Errorf("totalMatches = %d, want 1", matches)
if matches != 2 {
t.Errorf("totalMatches = %d, want 2", matches)
}
// Non-existent subpath should return 0 matches
@@ -2382,6 +2394,55 @@ func TestSubpathDetailMixedCaseHops(t *testing.T) {
}
}
// TestSubpathSingletonDrop verifies that singleton entries are pruned from
// spIndex while count>=2 entries are preserved.
func TestSubpathSingletonDrop(t *testing.T) {
db := setupRichTestDB(t)
defer db.Close()
store := NewPacketStore(db, nil)
store.Load()
// "eeff,0011" appears in 2 packets — must survive singleton pruning
if count, ok := store.spIndex["eeff,0011"]; !ok {
t.Fatal("expected spIndex[\"eeff,0011\"] to survive singleton pruning")
} else if count != 2 {
t.Errorf("spIndex[\"eeff,0011\"] = %d, want 2", count)
}
// All count==1 entries must be gone
for key, count := range store.spIndex {
if count < 2 {
t.Errorf("spIndex[%q] = %d, singletons should have been pruned", key, count)
}
}
}
// TestSubpathEmptyDB verifies that the store loads successfully on a DB
// with no transmissions (no subpaths at all).
func TestSubpathEmptyDB(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
store := NewPacketStore(db, nil)
store.Load()
if len(store.spIndex) != 0 {
t.Errorf("expected empty spIndex on empty DB, got %d entries", len(store.spIndex))
}
if store.spTotalPaths != 0 {
t.Errorf("expected spTotalPaths=0 on empty DB, got %d", store.spTotalPaths)
}
// GetSubpathDetail should still work (return zero matches)
detail := store.GetSubpathDetail([]string{"aa", "bb"})
if detail == nil {
t.Fatal("expected non-nil detail even on empty DB")
}
matches, _ := detail["totalMatches"].(int)
if matches != 0 {
t.Errorf("totalMatches on empty DB = %d, want 0", matches)
}
}
func TestStoreGetAnalyticsRFCacheHit(t *testing.T) {
db := setupRichTestDB(t)
defer db.Close()

View File

@@ -546,9 +546,6 @@ func TestEstimateStoreTxBytes(t *testing.T) {
manualCalc := int64(storeTxBaseBytes) + int64(len(tx.RawHex)+len(tx.Hash)+len(tx.DecodedJSON)+len(tx.PathJSON)) + int64(numIndexesPerTx*indexEntryBytes)
manualCalc += perTxMapsBytes
manualCalc += hops * perPathHopBytes
if hops > 1 {
manualCalc += (hops * (hops - 1) / 2) * perSubpathEntryBytes
}
if est != manualCalc {
t.Fatalf("estimateStoreTxBytes = %d, want %d (manual calc)", est, manualCalc)
}

View File

@@ -28,7 +28,7 @@ func TestEstimateStoreTxBytes_ReasonableValues(t *testing.T) {
}
// TestEstimateStoreTxBytes_ManyHopsSubpaths verifies that packets with many
// hops estimate significantly more due to O(path²) subpath index entries.
// hops estimate more due to per-hop byPathHop index entries.
func TestEstimateStoreTxBytes_ManyHopsSubpaths(t *testing.T) {
tx2 := &StoreTx{
Hash: "aabb",
@@ -43,12 +43,14 @@ func TestEstimateStoreTxBytes_ManyHopsSubpaths(t *testing.T) {
est2 := estimateStoreTxBytes(tx2)
est10 := estimateStoreTxBytes(tx10)
// 10 hops → 45 subpath combos × 40 = 1800 bytes just for subpaths
// 10 hops vs 2 hops → 8 extra byPathHop entries × perPathHopBytes
if est10 <= est2 {
t.Errorf("10-hop (%d) should estimate more than 2-hop (%d)", est10, est2)
}
if est10 < est2+1500 {
t.Errorf("10-hop (%d) should estimate at least 1500 more than 2-hop (%d)", est10, est2)
// spTxIndex eliminated in #791; cost difference is now linear (per-hop only)
expectedDiff := int64(8) * perPathHopBytes // 8 extra hops
if est10 < est2+expectedDiff {
t.Errorf("10-hop (%d) should estimate at least %d more than 2-hop (%d)", est10, expectedDiff, est2)
}
}