github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/metrics/scope.go (about) 1 // Copyright 2019 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 package metrics 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/gob" 11 "fmt" 12 "sync/atomic" 13 "unsafe" 14 ) 15 16 // Scope is a collection of metric instances. 17 type Scope struct { 18 // storage is an unsafe pointer to a []unsafe.Pointer. It is used to 19 // implement scopes as a thread-safe, persistent data structure. 20 storage unsafe.Pointer 21 } 22 23 // GobEncode implements a custom gob encoder for scopes. 24 func (s *Scope) GobEncode() ([]byte, error) { 25 var b bytes.Buffer 26 list := make([]interface{}, len(metrics)) 27 for i, m := range metrics { 28 list[i] = s.load(m) 29 } 30 err := gob.NewEncoder(&b).Encode(list) 31 return b.Bytes(), err 32 } 33 34 // GobDecode implements a custom gob decoder for scopes. 35 func (s *Scope) GobDecode(p []byte) error { 36 var list []interface{} 37 dec := gob.NewDecoder(bytes.NewReader(p)) 38 if err := dec.Decode(&list); err != nil { 39 return err 40 } 41 if len(list) != len(metrics) { 42 return fmt.Errorf("incompatible metric set: remote: %d, local: %d", len(list), len(metrics)) 43 } 44 for i, v := range list { 45 s.store(metrics[i], v) 46 } 47 return nil 48 } 49 50 // Merge merges instances from Scope u into Scope s. 51 func (s *Scope) Merge(u *Scope) { 52 for _, m := range metrics { 53 inst := u.load(m) 54 if inst == nil { 55 continue 56 } 57 m.merge(s.instance(m), inst) 58 } 59 } 60 61 // Reset resets the scope s to u. It is reset to its initial (zero) state 62 // if u is nil. 63 func (s *Scope) Reset(u *Scope) { 64 if u == nil { 65 atomic.StorePointer(&s.storage, unsafe.Pointer(nil)) 66 } else { 67 for _, m := range metrics { 68 s.store(m, u.load(m)) 69 } 70 } 71 } 72 73 // instance returns the instance associated with metrics m in the scope s. A new 74 // instance is created if none exists yet. 75 func (s *Scope) instance(m Metric) interface{} { 76 if inst := s.load(m); inst != nil { 77 return inst 78 } 79 var ( 80 list = s.list() 81 inst = m.newInstance() 82 ) 83 if inst == nil { 84 panic("metric: metric returned nil instance") 85 } 86 for { 87 ptr := atomic.LoadPointer(&list[m.metricID()]) 88 if ptr != nil { 89 return *(*interface{})(ptr) 90 } 91 if atomic.CompareAndSwapPointer(&list[m.metricID()], ptr, unsafe.Pointer(&inst)) { 92 return inst 93 } 94 } 95 } 96 97 // load loads the metric m from the Scope s, returning nil if it was not found. 98 func (s *Scope) load(m Metric) interface{} { 99 list := s.list() 100 ptr := atomic.LoadPointer(&list[m.metricID()]) 101 if ptr == nil { 102 return nil 103 } 104 return *(*interface{})(ptr) 105 } 106 107 // store stores the instance value v for metric m into this scope. If v is nil, 108 // the metrics is reset. 109 func (s *Scope) store(m Metric, v interface{}) { 110 var ( 111 list = s.list() 112 ptr unsafe.Pointer 113 ) 114 if v != nil { 115 ptr = unsafe.Pointer(&v) 116 } 117 atomic.StorePointer(&list[m.metricID()], ptr) 118 } 119 120 // list returns the slice of instances in this scope. It is created 121 // if empty. 122 func (s *Scope) list() []unsafe.Pointer { 123 for { 124 ptr := atomic.LoadPointer(&s.storage) 125 if ptr != nil { 126 return *(*[]unsafe.Pointer)(ptr) 127 } 128 list := make([]unsafe.Pointer, len(metrics)) 129 if atomic.CompareAndSwapPointer(&s.storage, ptr, unsafe.Pointer(&list)) { 130 return list 131 } 132 } 133 } 134 135 // contextKeyType is used to create unique context key for scopes, 136 // available only to code in this package. 137 type contextKeyType struct{} 138 139 // contextKey is the key used to attach scopes to contexts. 140 var contextKey contextKeyType 141 142 // ScopedContext returns a context with the provided scope attached. 143 // The scope may be retrieved by ContextScope. 144 func ScopedContext(ctx context.Context, scope *Scope) context.Context { 145 return context.WithValue(ctx, contextKey, scope) 146 } 147 148 // ContextScope returns the scope attached to the provided context. ContextScope 149 // panics if the context does not have an attached scope. 150 func ContextScope(ctx context.Context) *Scope { 151 s := ctx.Value(contextKey) 152 if s == nil { 153 panic("metrics: context does not provide metrics") 154 } 155 return s.(*Scope) 156 }