github.com/evdatsion/aphelion-dpos-bft@v0.32.1/tools/tm-monitor/eventmeter/eventmeter.go (about)

     1  // eventmeter - generic system to subscribe to events and record their frequency.
     2  package eventmeter
     3  
     4  import (
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	metrics "github.com/rcrowley/go-metrics"
    12  
    13  	"github.com/evdatsion/aphelion-dpos-bft/libs/events"
    14  	"github.com/evdatsion/aphelion-dpos-bft/libs/log"
    15  	client "github.com/evdatsion/aphelion-dpos-bft/rpc/lib/client"
    16  )
    17  
    18  const (
    19  	// Get ping/pong latency and call LatencyCallbackFunc with this period.
    20  	latencyPeriod = 1 * time.Second
    21  
    22  	// Check if the WS client is connected every
    23  	connectionCheckPeriod = 100 * time.Millisecond
    24  )
    25  
    26  // EventMetric exposes metrics for an event.
    27  type EventMetric struct {
    28  	ID          string    `json:"id"`
    29  	Started     time.Time `json:"start_time"`
    30  	LastHeard   time.Time `json:"last_heard"`
    31  	MinDuration int64     `json:"min_duration"`
    32  	MaxDuration int64     `json:"max_duration"`
    33  
    34  	// tracks event count and rate
    35  	meter metrics.Meter
    36  
    37  	// filled in from the Meter
    38  	Count    int64   `json:"count"`
    39  	Rate1    float64 `json:"rate_1" amino:"unsafe"`
    40  	Rate5    float64 `json:"rate_5" amino:"unsafe"`
    41  	Rate15   float64 `json:"rate_15" amino:"unsafe"`
    42  	RateMean float64 `json:"rate_mean" amino:"unsafe"`
    43  
    44  	// so the event can have effects in the eventmeter's consumer. runs in a go
    45  	// routine.
    46  	callback EventCallbackFunc
    47  }
    48  
    49  func (metric *EventMetric) Copy() *EventMetric {
    50  	metricCopy := *metric
    51  	metricCopy.meter = metric.meter.Snapshot()
    52  	return &metricCopy
    53  }
    54  
    55  // called on GetMetric
    56  func (metric *EventMetric) fillMetric() *EventMetric {
    57  	metric.Count = metric.meter.Count()
    58  	metric.Rate1 = metric.meter.Rate1()
    59  	metric.Rate5 = metric.meter.Rate5()
    60  	metric.Rate15 = metric.meter.Rate15()
    61  	metric.RateMean = metric.meter.RateMean()
    62  	return metric
    63  }
    64  
    65  // EventCallbackFunc is a closure to enable side effects from receiving an
    66  // event.
    67  type EventCallbackFunc func(em *EventMetric, data interface{})
    68  
    69  // EventUnmarshalFunc is a closure to get the query and data out of the raw
    70  // JSON received over the RPC WebSocket.
    71  type EventUnmarshalFunc func(b json.RawMessage) (string, events.EventData, error)
    72  
    73  // LatencyCallbackFunc is a closure to enable side effects from receiving a latency.
    74  type LatencyCallbackFunc func(meanLatencyNanoSeconds float64)
    75  
    76  // DisconnectCallbackFunc is a closure to notify a consumer that the connection
    77  // has died.
    78  type DisconnectCallbackFunc func()
    79  
    80  // EventMeter tracks events, reports latency and disconnects.
    81  type EventMeter struct {
    82  	wsc *client.WSClient
    83  
    84  	mtx              sync.Mutex
    85  	queryToMetricMap map[string]*EventMetric
    86  
    87  	unmarshalEvent     EventUnmarshalFunc
    88  	latencyCallback    LatencyCallbackFunc
    89  	disconnectCallback DisconnectCallbackFunc
    90  	subscribed         bool
    91  
    92  	quit chan struct{}
    93  
    94  	logger log.Logger
    95  }
    96  
    97  func NewEventMeter(addr string, unmarshalEvent EventUnmarshalFunc) *EventMeter {
    98  	return &EventMeter{
    99  		wsc:              client.NewWSClient(addr, "/websocket", client.PingPeriod(1*time.Second)),
   100  		queryToMetricMap: make(map[string]*EventMetric),
   101  		unmarshalEvent:   unmarshalEvent,
   102  		logger:           log.NewNopLogger(),
   103  	}
   104  }
   105  
   106  // SetLogger lets you set your own logger.
   107  func (em *EventMeter) SetLogger(l log.Logger) {
   108  	em.logger = l
   109  	em.wsc.SetLogger(l.With("module", "rpcclient"))
   110  }
   111  
   112  // String returns a string representation of event meter.
   113  func (em *EventMeter) String() string {
   114  	return em.wsc.Address
   115  }
   116  
   117  // Start boots up event meter.
   118  func (em *EventMeter) Start() error {
   119  	if err := em.wsc.Start(); err != nil {
   120  		return err
   121  	}
   122  
   123  	em.quit = make(chan struct{})
   124  	go em.receiveRoutine()
   125  	go em.disconnectRoutine()
   126  
   127  	err := em.subscribe()
   128  	if err != nil {
   129  		return err
   130  	}
   131  	em.subscribed = true
   132  	return nil
   133  }
   134  
   135  // Stop stops event meter.
   136  func (em *EventMeter) Stop() {
   137  	close(em.quit)
   138  
   139  	if em.wsc.IsRunning() {
   140  		em.wsc.Stop()
   141  	}
   142  }
   143  
   144  // Subscribe for the given query. Callback function will be called upon
   145  // receiving an event.
   146  func (em *EventMeter) Subscribe(query string, cb EventCallbackFunc) error {
   147  	em.mtx.Lock()
   148  	defer em.mtx.Unlock()
   149  
   150  	if err := em.wsc.Subscribe(context.TODO(), query); err != nil {
   151  		return err
   152  	}
   153  
   154  	metric := &EventMetric{
   155  		meter:    metrics.NewMeter(),
   156  		callback: cb,
   157  	}
   158  	em.queryToMetricMap[query] = metric
   159  	return nil
   160  }
   161  
   162  // Unsubscribe from the given query.
   163  func (em *EventMeter) Unsubscribe(query string) error {
   164  	em.mtx.Lock()
   165  	defer em.mtx.Unlock()
   166  
   167  	return em.wsc.Unsubscribe(context.TODO(), query)
   168  }
   169  
   170  // GetMetric fills in the latest data for an query and return a copy.
   171  func (em *EventMeter) GetMetric(query string) (*EventMetric, error) {
   172  	em.mtx.Lock()
   173  	defer em.mtx.Unlock()
   174  	metric, ok := em.queryToMetricMap[query]
   175  	if !ok {
   176  		return nil, fmt.Errorf("unknown query: %s", query)
   177  	}
   178  	return metric.fillMetric().Copy(), nil
   179  }
   180  
   181  // RegisterLatencyCallback allows you to set latency callback.
   182  func (em *EventMeter) RegisterLatencyCallback(f LatencyCallbackFunc) {
   183  	em.mtx.Lock()
   184  	defer em.mtx.Unlock()
   185  	em.latencyCallback = f
   186  }
   187  
   188  // RegisterDisconnectCallback allows you to set disconnect callback.
   189  func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) {
   190  	em.mtx.Lock()
   191  	defer em.mtx.Unlock()
   192  	em.disconnectCallback = f
   193  }
   194  
   195  ///////////////////////////////////////////////////////////////////////////////
   196  // Private
   197  
   198  func (em *EventMeter) subscribe() error {
   199  	for query := range em.queryToMetricMap {
   200  		if err := em.wsc.Subscribe(context.TODO(), query); err != nil {
   201  			return err
   202  		}
   203  	}
   204  	return nil
   205  }
   206  
   207  func (em *EventMeter) receiveRoutine() {
   208  	latencyTicker := time.NewTicker(latencyPeriod)
   209  	for {
   210  		select {
   211  		case resp := <-em.wsc.ResponsesCh:
   212  			if resp.Error != nil {
   213  				em.logger.Error("expected some event, got error", "err", resp.Error.Error())
   214  				continue
   215  			}
   216  			query, data, err := em.unmarshalEvent(resp.Result)
   217  			if err != nil {
   218  				em.logger.Error("failed to unmarshal event", "err", err)
   219  				continue
   220  			}
   221  			if query != "" { // FIXME how can it be an empty string?
   222  				em.updateMetric(query, data)
   223  			}
   224  		case <-latencyTicker.C:
   225  			if em.wsc.IsActive() {
   226  				em.callLatencyCallback(em.wsc.PingPongLatencyTimer.Mean())
   227  			}
   228  		case <-em.wsc.Quit():
   229  			return
   230  		case <-em.quit:
   231  			return
   232  		}
   233  	}
   234  }
   235  
   236  func (em *EventMeter) disconnectRoutine() {
   237  	ticker := time.NewTicker(connectionCheckPeriod)
   238  	for {
   239  		select {
   240  		case <-ticker.C:
   241  			if em.wsc.IsReconnecting() && em.subscribed { // notify user about disconnect only once
   242  				em.callDisconnectCallback()
   243  				em.subscribed = false
   244  			} else if !em.wsc.IsReconnecting() && !em.subscribed { // resubscribe
   245  				em.subscribe()
   246  				em.subscribed = true
   247  			}
   248  		case <-em.wsc.Quit():
   249  			return
   250  		case <-em.quit:
   251  			return
   252  		}
   253  	}
   254  }
   255  
   256  func (em *EventMeter) updateMetric(query string, data events.EventData) {
   257  	em.mtx.Lock()
   258  	defer em.mtx.Unlock()
   259  
   260  	metric, ok := em.queryToMetricMap[query]
   261  	if !ok {
   262  		// we already unsubscribed, or got an unexpected query
   263  		return
   264  	}
   265  
   266  	last := metric.LastHeard
   267  	metric.LastHeard = time.Now()
   268  	metric.meter.Mark(1)
   269  	dur := int64(metric.LastHeard.Sub(last))
   270  	if dur < metric.MinDuration {
   271  		metric.MinDuration = dur
   272  	}
   273  	if !last.IsZero() && dur > metric.MaxDuration {
   274  		metric.MaxDuration = dur
   275  	}
   276  
   277  	if metric.callback != nil {
   278  		go metric.callback(metric.Copy(), data)
   279  	}
   280  }
   281  
   282  func (em *EventMeter) callDisconnectCallback() {
   283  	em.mtx.Lock()
   284  	if em.disconnectCallback != nil {
   285  		go em.disconnectCallback()
   286  	}
   287  	em.mtx.Unlock()
   288  }
   289  
   290  func (em *EventMeter) callLatencyCallback(meanLatencyNanoSeconds float64) {
   291  	em.mtx.Lock()
   292  	if em.latencyCallback != nil {
   293  		go em.latencyCallback(meanLatencyNanoSeconds)
   294  	}
   295  	em.mtx.Unlock()
   296  }