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  }