xmidt-org/themis

View on GitHub
xmetrics/xmetricshttp/labeller.go

Summary

Maintainability
A
1 hr
Test Coverage
package xmetricshttp

import (
    "net/http"
    "strconv"

    "github.com/xmidt-org/themis/xmetrics"
)

const (
    DefaultCodeLabel   = "code"
    DefaultMethodLabel = "method"
    DefaultOther       = "other"
)

// StatusCoder is expected to be implemented by http.ResponseWriters that participate in metrics.
// Decorating the http.ResponseWriter is left to other packages.
type StatusCoder interface {
    StatusCode() int
}

// LabelNames is a strategy for determining the names for metrics labels
type LabelNames interface {
    // LabelNames returns the ordered set of metrics label names.  The order in which these
    // names occur in the slice will match any labeller strategies in a ClientLabeller or a ServerLabeller.
    LabelNames() []string
}

// ServerLabeller is a strategy for producing metrics label/value pairs from a serverside HTTP request
type ServerLabeller interface {
    LabelNames

    // ServerLabels applies the labels this strategy provides.  The order the labels are applies
    // must match the order of names returned by LabelNames.
    ServerLabels(http.ResponseWriter, *http.Request, *xmetrics.Labels)
}

// ServerLabellers is a set of ServerLabeller strategies to be executed in sequence.  Keys are label names.
// This type guarantees a consistent ordering for both the labels and labellers.
//
// A default ServerLabellers is ready to use and can be built up using Add.  A nil ServerLabellers is also
// valid, and acts as a no-op.
type ServerLabellers struct {
    labelNames []string
    labellers  []ServerLabeller
}

func NewServerLabellers(labellers ...ServerLabeller) *ServerLabellers {
    sl := &ServerLabellers{
        labelNames: make([]string, len(labellers)), // just an optimization step
        labellers:  append([]ServerLabeller{}, labellers...),
    }

    for _, l := range labellers {
        sl.labelNames = append(sl.labelNames, l.LabelNames()...)
    }

    return sl
}

func (sl *ServerLabellers) LabelNames() []string {
    if sl == nil {
        return nil
    }

    return sl.labelNames
}

func (sl *ServerLabellers) ServerLabels(response http.ResponseWriter, request *http.Request, l *xmetrics.Labels) {
    if sl != nil {
        for _, labeller := range sl.labellers {
            labeller.ServerLabels(response, request, l)
        }
    }
}

// ClientLabeller is a strategy for producing metrics label/value pairs from a clientside HTTP request
type ClientLabeller interface {
    LabelNames

    // ClientLabels applies the labels this strategy provides.  The order the labels are applies
    // must match the order of names returned by LabelNames.
    ClientLabels(*http.Response, *http.Request, *xmetrics.Labels)
}

// ClientLabellers is a set of ClientLabeller strategies to be executed in sequence.  Keys are label names.
// This type guarantees a consistent ordering for both the labels and labellers.
//
// A default ClientLabellers is ready to use and can be built up using Add.  A nil ClientLabellers is valid
// and acts as a no-op.
type ClientLabellers struct {
    labelNames []string
    labellers  []ClientLabeller
}

func NewClientLabellers(labellers ...ClientLabeller) *ClientLabellers {
    cl := &ClientLabellers{
        labelNames: make([]string, len(labellers)), // just an optimization step
        labellers:  append([]ClientLabeller{}, labellers...),
    }

    for _, l := range labellers {
        cl.labelNames = append(cl.labelNames, l.LabelNames()...)
    }

    return cl
}

// LabelNames returns the label names in the order they were added.  This is the same order that
// ClientLabels applies labels to a metric.
func (cl *ClientLabellers) LabelNames() []string {
    if cl == nil {
        return nil
    }

    return cl.labelNames
}

func (cl *ClientLabellers) ClientLabels(response *http.Response, request *http.Request, l *xmetrics.Labels) {
    if cl != nil {
        for _, labeller := range cl.labellers {
            labeller.ClientLabels(response, request, l)
        }
    }
}

// EmptyLabeller is both a server and client labeller which produces no labels
type EmptyLabeller struct{}

func (el EmptyLabeller) LabelNames() []string {
    return nil
}

func (el EmptyLabeller) ServerLabels(http.ResponseWriter, *http.Request, *xmetrics.Labels) {
}

func (el EmptyLabeller) ClientLabels(*http.Response, *http.Request, *xmetrics.Labels) {
}

// CodeLabeller provides both ServerLabeller and ClientLabeller functionality for HTTP response codes.
// For servers, the http.ResponseWriter must implement the StatusCode interface, or this labeller will panic.
type CodeLabeller struct {
    // Name is the name of the label to apply.  If unset, DefaultCodeLabel is used.
    Name string
}

func (cl CodeLabeller) name() string {
    if len(cl.Name) > 0 {
        return cl.Name
    }

    return DefaultCodeLabel
}

func (cl CodeLabeller) LabelNames() []string {
    return []string{cl.name()}
}

func (cl CodeLabeller) ServerLabels(response http.ResponseWriter, _ *http.Request, l *xmetrics.Labels) {
    l.Add(cl.name(), strconv.Itoa(response.(StatusCoder).StatusCode()))
}

func (cl CodeLabeller) ClientLabels(response *http.Response, _ *http.Request, l *xmetrics.Labels) {
    l.Add(cl.name(), strconv.Itoa(response.StatusCode))
}

var defaultTrackedMethods = map[string]bool{
    "GET":     true,
    "HEAD":    true,
    "POST":    true,
    "PUT":     true,
    "DELETE":  true,
    "TRACE":   true,
    "CONNECT": true,
}

// MethodLabeller provides both server and client labelling for the HTTP request method.
type MethodLabeller struct {
    // Name is the name of the label to apply.  If unset, DefaultMethodLabel is used.
    Name string

    // TrackedMethods is a set of HTTP methods that are tracked by this labeller.  Methods that
    // don't have keys in this map use the Other value instead.  If unset, a default map
    // is used that includes all standard HTTP methods.
    TrackedMethods map[string]bool

    // Other is the value used for methods that do not have a key in the TrackedMethods map.
    // If unset, DefaultOther is used.
    Other string
}

func (ml MethodLabeller) name() string {
    name := ml.Name
    if len(name) == 0 {
        name = DefaultMethodLabel
    }

    return name
}

func (ml MethodLabeller) labels(request *http.Request, l *xmetrics.Labels) {
    value := request.Method
    if len(ml.TrackedMethods) > 0 {
        if !ml.TrackedMethods[value] {
            value = ml.Other
        }
    } else if !defaultTrackedMethods[value] {
        value = ml.Other
    }

    if len(value) == 0 {
        value = DefaultOther
    }

    l.Add(ml.name(), value)
}

func (ml MethodLabeller) LabelNames() []string {
    return []string{ml.name()}
}

func (ml MethodLabeller) ServerLabels(_ http.ResponseWriter, request *http.Request, l *xmetrics.Labels) {
    ml.labels(request, l)
}

func (ml MethodLabeller) ClientLabels(_ *http.Response, request *http.Request, l *xmetrics.Labels) {
    ml.labels(request, l)
}