alexandre-normand/slackscot

View on GitHub
coremetrics.go

Summary

Maintainability
B
5 hrs
Test Coverage
B
82%
package slackscot

import (
    "go.opentelemetry.io/otel/label"
    "go.opentelemetry.io/otel/metric"
    "time"
)

const (
    newMsgType    = "new"
    updateMsgType = "edit"
    deleteMsgType = "delete"
)

// instrumenter holds data for core instrumentation
type instrumenter struct {
    appName       string
    coreMetrics   coreMetrics
    pluginMetrics map[string]pluginMetrics
    meter         metric.Meter
}

// coreMetrics holds core slackscot metrics
type coreMetrics struct {
    msgsSeen                   metric.BoundInt64Counter
    msgsProcessed              map[string]metric.BoundInt64Counter
    msgProcessingLatencyMillis map[string]metric.BoundInt64ValueRecorder
    msgDispatchLatencyMillis   metric.BoundInt64ValueRecorder
    slackLatencyMillis         metric.Int64ValueObserver
}

// pluginMetrics holds metrics specific to a plugin
type pluginMetrics struct {
    processingTimeMillis metric.BoundInt64ValueRecorder
    reactionCount        metric.BoundInt64Counter
}

// newInstrumenter creates a new core instrumenter
func newInstrumenter(appName string, meter metric.Meter, latencyCallback metric.Int64ObserverFunc) (ins *instrumenter, err error) {
    ins = new(instrumenter)

    defaultLabels := []label.KeyValue{label.String("name", appName)}

    msgSeen, err := meter.NewInt64Counter("msgSeen")
    if err != nil {
        return nil, err
    }

    slackLatency, err := meter.NewInt64ValueObserver("slackLatencyMillis", latencyCallback)
    if err != nil {
        return nil, err
    }

    dispatchLatency, err := meter.NewInt64ValueRecorder("msgDispatchLatencyMillis")
    if err != nil {
        return nil, err
    }

    msgProcessed, err := newBoundCounterByMsgType("msgProcessed", appName, meter)
    if err != nil {
        return nil, err
    }

    msgProcessingLatencyMillis, err := newBoundValueRecorderByMsgType("msgProcessingLatencyMillis", appName, meter)
    if err != nil {
        return nil, err
    }

    ins.coreMetrics = coreMetrics{msgsSeen: msgSeen.Bind(defaultLabels...),
        msgsProcessed:              msgProcessed,
        msgProcessingLatencyMillis: msgProcessingLatencyMillis,
        msgDispatchLatencyMillis:   dispatchLatency.Bind(defaultLabels...),
        slackLatencyMillis:         slackLatency}

    ins.appName = appName
    ins.pluginMetrics = make(map[string]pluginMetrics)

    ins.meter = meter
    return ins, nil
}

// newBoundValueRecorderByMsgType creates a set of BoundInt64Counter by message type
func newBoundCounterByMsgType(counterName string, appName string, meter metric.Meter) (boundCounter map[string]metric.BoundInt64Counter, err error) {
    boundCounter = make(map[string]metric.BoundInt64Counter)

    c, err := meter.NewInt64Counter(counterName)
    if err != nil {
        return nil, err
    }

    boundCounter[newMsgType] = c.Bind(label.String("name", appName), label.String("msgType", newMsgType))
    boundCounter[updateMsgType] = c.Bind(label.String("name", appName), label.String("msgType", updateMsgType))
    boundCounter[deleteMsgType] = c.Bind(label.String("name", appName), label.String("msgType", deleteMsgType))

    return boundCounter, nil
}

// newBoundValueRecorderByMsgType creates a set of BoundInt64ValueRecorder by message type
func newBoundValueRecorderByMsgType(ValueRecorderName string, appName string, meter metric.Meter) (boundValueRecorder map[string]metric.BoundInt64ValueRecorder, err error) {
    boundValueRecorder = make(map[string]metric.BoundInt64ValueRecorder)

    m, err := meter.NewInt64ValueRecorder(ValueRecorderName)
    if err != nil {
        return nil, err
    }

    boundValueRecorder[newMsgType] = m.Bind(label.String("name", appName), label.String("msgType", newMsgType))
    boundValueRecorder[updateMsgType] = m.Bind(label.String("name", appName), label.String("msgType", updateMsgType))
    boundValueRecorder[deleteMsgType] = m.Bind(label.String("name", appName), label.String("msgType", deleteMsgType))

    return boundValueRecorder, nil
}

// getOrCreatePluginMetrics returns an existing pluginMetrics for a plugin or creates a new one, if necessary
func (ins *instrumenter) getOrCreatePluginMetrics(pluginName string) (pm pluginMetrics, err error) {
    if _, ok := ins.pluginMetrics[pluginName]; !ok {
        pm, err = newPluginMetrics(ins.appName, pluginName, ins.meter)
        if err != nil {
            return pm, err
        }
        ins.pluginMetrics[pluginName] = pm
    }

    return ins.pluginMetrics[pluginName], nil
}

// newPluginMetrics returns a new pluginMetrics instance for a plugin
func newPluginMetrics(appName string, pluginName string, meter metric.Meter) (pm pluginMetrics, err error) {
    c, err := meter.NewInt64Counter("reactionCount")
    if err != nil {
        return pm, err
    }
    m, err := meter.NewInt64ValueRecorder("processingTimeMillis")
    if err != nil {
        return pm, err
    }

    pm.reactionCount = c.Bind(label.String("name", appName), label.String("plugin", pluginName))
    pm.processingTimeMillis = m.Bind(label.String("name", appName), label.String("plugin", pluginName))

    return pm, nil
}

type timed func()

// measure returns the execution duration of a timed function
func measure(operation timed) (d time.Duration) {
    before := time.Now()

    operation()

    return time.Now().Sub(before)
}