github.com/m3db/m3@v1.5.0/src/x/instrument/string_list_emitter.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package instrument
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/uber-go/tally"
    30  )
    31  
    32  const (
    33  	// stringListEmitterWaitInterval defines the time to wait between emitting
    34  	// the value of the Gauge again.
    35  	stringListEmitterWaitInterval = 10 * time.Second
    36  )
    37  
    38  var (
    39  	errStringListEmitterAlreadyRunning = errors.New("string list emitter: already running")
    40  	errStringListEmitterNotStarted     = errors.New("string list emitter: not running")
    41  )
    42  
    43  // StringListEmitter emits a gauges indicating the order of a list of strings.
    44  type StringListEmitter struct {
    45  	sync.Mutex
    46  	running   bool
    47  	doneCh    chan bool
    48  	scope     tally.Scope
    49  	gauges    []tally.Gauge
    50  	name      string
    51  	tagPrefix string
    52  }
    53  
    54  // NewStringListEmitter returns a StringListEmitter.
    55  func NewStringListEmitter(scope tally.Scope, name string) *StringListEmitter {
    56  	gauge := []tally.Gauge{tally.NoopScope.Gauge("blackhole")}
    57  	return &StringListEmitter{
    58  		running: false,
    59  		doneCh:  make(chan bool, 1),
    60  		scope:   scope,
    61  		gauges:  gauge,
    62  		name:    name,
    63  	}
    64  }
    65  
    66  // newGauges creates a gauge per string in a passed list of strings.
    67  func (sle *StringListEmitter) newGauges(scope tally.Scope, sl []string) []tally.Gauge {
    68  	gauges := make([]tally.Gauge, len(sl))
    69  	for i, v := range sl {
    70  		name := fmt.Sprintf("%s_%d", sle.name, i)
    71  		g := scope.Tagged(map[string]string{"type": v}).Gauge(name)
    72  		g.Update(1)
    73  		gauges[i] = g
    74  	}
    75  
    76  	return gauges
    77  }
    78  
    79  // update updates the Gauges on the StringListEmitter. Client should acquire a
    80  // Lock before updating.
    81  func (sle *StringListEmitter) update(val float64) {
    82  	for _, gauge := range sle.gauges {
    83  		gauge.Update(val)
    84  	}
    85  }
    86  
    87  // Start starts a goroutine that continuously emits the value of the gauges
    88  func (sle *StringListEmitter) Start(sl []string) error {
    89  	sle.Lock()
    90  	defer sle.Unlock()
    91  
    92  	if sle.running {
    93  		return errStringListEmitterAlreadyRunning
    94  	}
    95  
    96  	sle.gauges = sle.newGauges(sle.scope, sl)
    97  
    98  	sle.running = true
    99  	go func() {
   100  		for {
   101  			select {
   102  			case <-sle.doneCh:
   103  				return
   104  			default:
   105  				sle.Lock()
   106  				sle.update(1)
   107  				sle.Unlock()
   108  				time.Sleep(stringListEmitterWaitInterval)
   109  			}
   110  		}
   111  	}()
   112  	return nil
   113  }
   114  
   115  // UpdateStringList updates the gauges according to the passed
   116  // list of strings. It will first set the old gauges to 0, then emit
   117  // new metrics with different values for the "type" label.
   118  func (sle *StringListEmitter) UpdateStringList(sl []string) error {
   119  	sle.Lock()
   120  	defer sle.Unlock()
   121  
   122  	if !sle.running {
   123  		return errStringListEmitterNotStarted
   124  	}
   125  
   126  	sle.update(0)
   127  
   128  	sle.gauges = sle.newGauges(sle.scope, sl)
   129  
   130  	return nil
   131  }
   132  
   133  // Close stops emitting the gauge.
   134  func (sle *StringListEmitter) Close() error {
   135  	sle.Lock()
   136  	defer sle.Unlock()
   137  
   138  	if !sle.running {
   139  		return errStringListEmitterNotStarted
   140  	}
   141  
   142  	sle.running = false
   143  	close(sle.doneCh)
   144  
   145  	return nil
   146  }