blob: 7ab0a0aeecdf63c78b02184db84a5e5674e281da [file] [log] [blame]
package vtrace
import (
"sync"
"veyron.io/veyron/veyron2/uniqueid"
"veyron.io/veyron/veyron2/vtrace"
)
// Store implements a store for traces. The idea is to keep all the
// information we have about some subset of traces that pass through
// the server. For now we just implement an LRU cache, so the least
// recently started/finished/annotated traces expire after some
// maximum trace count is reached.
// TODO(mattr): LRU is the wrong policy in the long term, we should
// try to keep some diverse set of traces and allow users to
// specifically tell us to capture a specific trace. LRU will work OK
// for many testing scenarios and low volume applications.
type Store struct {
size int
// traces and head together implement a linked-hash-map.
// head points to the head and tail of the doubly-linked-list
// of recently used items (the tail is the LRU traceSet).
mu sync.Mutex
traces map[uniqueid.ID]*traceSet // GUARDED_BY(mu)
head *traceSet // GUARDED_BY(mu)
}
// NewStore creates a new store that will keep a maximum of size
// traces in memory.
// TODO(mattr): Traces can be of widely varying size, we should have
// some better measurement then just number of traces.
func NewStore(size int) *Store {
head := &traceSet{}
head.next, head.prev = head, head
return &Store{
size: size,
traces: make(map[uniqueid.ID]*traceSet),
head: head,
}
}
// Consider should be called whenever an interesting change happens to
// a trace the store will decide whether to keep it or not.
func (s *Store) Consider(trace vtrace.Trace) {
s.mu.Lock()
defer s.mu.Unlock()
set := s.traces[trace.ID()]
if set == nil {
set = newTraceSet()
s.traces[trace.ID()] = set
}
set.add(trace)
set.moveAfter(s.head)
s.trimLocked()
}
// TraceRecords returns TraceRecords for all traces saved in the store.
func (s *Store) TraceRecords() []vtrace.TraceRecord {
s.mu.Lock()
defer s.mu.Unlock()
out := make([]vtrace.TraceRecord, len(s.traces))
i := 0
for _, ts := range s.traces {
ts.traceRecord(&out[i])
i++
}
return out
}
// TraceRecord returns a TraceRecord for a given ID. Returns
// nil if the given id is not present.
func (s *Store) TraceRecord(id uniqueid.ID) *vtrace.TraceRecord {
s.mu.Lock()
defer s.mu.Unlock()
ts := s.traces[id]
if ts == nil {
return nil
}
out := vtrace.TraceRecord{}
ts.traceRecord(&out)
return &out
}
// trimLocked removes elements from the store LRU first until we are
// below Store.size.
func (s *Store) trimLocked() {
for len(s.traces) > s.size {
el := s.head.prev
el.removeFromList()
delete(s.traces, el.id())
}
}
// We need to capture traceSets because a single trace can reach this
// server along multiple paths. Consider a client that calls this
// server twice in the same operation.
type traceSet struct {
elts map[vtrace.Trace]bool
prev, next *traceSet
}
func newTraceSet() *traceSet {
return &traceSet{elts: make(map[vtrace.Trace]bool)}
}
func (ts *traceSet) add(trace vtrace.Trace) {
ts.elts[trace] = true
}
func (ts *traceSet) removeFromList() {
if ts.prev != nil {
ts.prev.next = ts.next
}
if ts.next != nil {
ts.next.prev = ts.prev
}
ts.next = nil
ts.prev = nil
}
func (ts *traceSet) moveAfter(prev *traceSet) {
ts.removeFromList()
ts.prev = prev
ts.next = prev.next
prev.next.prev = ts
prev.next = ts
}
func (ts *traceSet) id() uniqueid.ID {
for e := range ts.elts {
return e.ID()
}
panic("unreachable")
}
func (ts *traceSet) traceRecord(out *vtrace.TraceRecord) {
// It is possible to have duplicate copies of spans. Consider the
// case where a server calls itself (even indirectly) we'll have one
// Trace in the set for the parent call and one Trace in the set for
// the decendant. The two records will be exactly the same we
// therefore de-dup here.
spans := make(map[uniqueid.ID]bool)
for e, _ := range ts.elts {
record := e.Record()
out.ID = record.ID
for _, span := range record.Spans {
if spans[span.ID] {
continue
}
spans[span.ID] = true
out.Spans = append(out.Spans, span)
}
}
}