collector.go raw

   1  package profile
   2  
   3  import (
   4  	"sync"
   5  
   6  	"git.mleku.dev/mleku/dendrite/pkg/grow"
   7  )
   8  
   9  // Collector accumulates a Profile from growth events.
  10  // Thread-safe: multiple goroutines can call Record concurrently.
  11  type Collector struct {
  12  	mu      sync.Mutex
  13  	profile *Profile
  14  	lastTag string // tag of most recently bonded element, for transition tracking
  15  }
  16  
  17  // NewCollector creates a collector with an empty profile.
  18  func NewCollector() *Collector {
  19  	return &Collector{profile: New()}
  20  }
  21  
  22  // RecordGrowEvent processes a single growth event.
  23  func (c *Collector) RecordGrowEvent(ev grow.Event) {
  24  	c.mu.Lock()
  25  	defer c.mu.Unlock()
  26  
  27  	c.profile.TokensIngested++
  28  
  29  	switch ev.Type {
  30  	case grow.EventBonded:
  31  		c.profile.BondEvents++
  32  		c.profile.PathFreq[ev.NodeID]++
  33  		c.profile.WalkDistHist[ev.Steps]++
  34  		if ev.Element != nil {
  35  			tag := ev.Element.Type()
  36  			c.profile.BondDist[tag]++
  37  			if c.lastTag != "" {
  38  				c.profile.TransitionFreq[[2]string{c.lastTag, tag}]++
  39  			}
  40  			c.lastTag = tag
  41  		}
  42  	case grow.EventRejected:
  43  		c.profile.RejectEvents++
  44  	case grow.EventExpired:
  45  		c.profile.ExpireEvents++
  46  	}
  47  }
  48  
  49  // RecordProbeEvent processes a single probe event from inference on a
  50  // trained lattice. Probe matches record into the same profile structure
  51  // as growth bonds — the statistical fingerprint is what matters, not
  52  // whether bonding actually occurred.
  53  func (c *Collector) RecordProbeEvent(ev grow.ProbeEvent) {
  54  	c.mu.Lock()
  55  	defer c.mu.Unlock()
  56  
  57  	c.profile.TokensIngested++
  58  
  59  	switch ev.Type {
  60  	case grow.EventBonded:
  61  		c.profile.BondEvents++
  62  		c.profile.PathFreq[ev.NodeID]++
  63  		c.profile.WalkDistHist[ev.Steps]++
  64  		if ev.Element != nil {
  65  			tag := ev.Element.Type()
  66  			c.profile.BondDist[tag]++
  67  			if c.lastTag != "" {
  68  				c.profile.TransitionFreq[[2]string{c.lastTag, tag}]++
  69  			}
  70  			c.lastTag = tag
  71  		}
  72  	case grow.EventExpired:
  73  		c.profile.ExpireEvents++
  74  	}
  75  }
  76  
  77  // RecordNewVertex increments the new vertex counter.
  78  // Called when the hexagram engine creates a new node (OpNucleate/OpExplore).
  79  func (c *Collector) RecordNewVertex() {
  80  	c.mu.Lock()
  81  	c.profile.NewVertices++
  82  	c.mu.Unlock()
  83  }
  84  
  85  // Snapshot returns a deep copy of the current profile.
  86  func (c *Collector) Snapshot() *Profile {
  87  	c.mu.Lock()
  88  	defer c.mu.Unlock()
  89  	return c.profile.Clone()
  90  }
  91