/* Copyright (c) 2012, Matt T. Proud All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package registry import ( "encoding/base64" "encoding/json" "github.com/matttproud/golang_instrumentation/metrics" "log" "net/http" "strings" "sync" "time" ) const ( authorization = "Authorization" contentType = "Content-Type" jsonContentType = "application/json" jsonSuffix = ".json" ) /* This callback accumulates the microsecond duration of the reporting framework's overhead such that it can be reported. */ var requestLatencyAccumulator metrics.CompletionCallback = func(duration time.Duration) { microseconds := float64(duration / time.Millisecond) requestLatencyLogarithmicAccumulating.Add(microseconds) requestLatencyEqualAccumulating.Add(microseconds) requestLatencyLogarithmicTallying.Add(microseconds) requestLatencyEqualTallying.Add(microseconds) } /* Registry is, as the name implies, a registrar where metrics are listed. In most situations, using DefaultRegistry is sufficient versus creating one's own. */ type Registry struct { mutex sync.RWMutex NameToMetric map[string]metrics.Metric } /* This builds a new metric registry. It is not needed in the majority of cases. */ func NewRegistry() *Registry { return &Registry{ NameToMetric: make(map[string]metrics.Metric), } } /* This is the default registry with which Metric objects are associated. It is primarily a read-only object after server instantiation. */ var DefaultRegistry = NewRegistry() /* Associate a Metric with the DefaultRegistry. */ func Register(name, unusedDocstring string, unusedBaseLabels map[string]string, metric metrics.Metric) { DefaultRegistry.Register(name, unusedDocstring, unusedBaseLabels, metric) } /* Register a metric with a given name. Name should be globally unique. */ func (r *Registry) Register(name, unusedDocstring string, unusedBaseLabels map[string]string, metric metrics.Metric) { r.mutex.Lock() defer r.mutex.Unlock() if _, present := r.NameToMetric[name]; !present { r.NameToMetric[name] = metric log.Printf("Registered %s.\n", name) } else { log.Printf("Attempted to register duplicate %s metric.\n", name) } } func (register *Registry) YieldBasicAuthExporter(username, password string) http.HandlerFunc { exporter := register.YieldExporter() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authenticated := false if auth := r.Header.Get(authorization); auth != "" { base64Encoded := strings.SplitAfter(auth, " ")[1] decoded, err := base64.URLEncoding.DecodeString(base64Encoded) if err == nil { usernamePassword := strings.Split(string(decoded), ":") if usernamePassword[0] == username && usernamePassword[1] == password { authenticated = true } } } if authenticated { exporter.ServeHTTP(w, r) } else { w.Header().Add("WWW-Authenticate", "Basic") http.Error(w, "access forbidden", 401) } }) } /* Create a http.HandlerFunc that is tied to r Registry such that requests against it generate a representation of the housed metrics. */ func (registry *Registry) YieldExporter() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var instrumentable metrics.InstrumentableCall = func() { requestCount.Increment() url := r.URL if strings.HasSuffix(url.Path, jsonSuffix) { w.Header().Set(contentType, jsonContentType) composite := make(map[string]interface{}, len(registry.NameToMetric)) for name, metric := range registry.NameToMetric { composite[name] = metric.Marshallable() } data, _ := json.Marshal(composite) w.Write(data) } else { w.WriteHeader(http.StatusNotFound) } } metrics.InstrumentCall(instrumentable, requestLatencyAccumulator) } }