func (mt *MultiTimings) Record(names []string, startTime time.Time) {
	if len(names) != len(mt.labels) {
		panic("MultiTimings: wrong number of values in Record")
	}
	mt.Timings.Record(safeJoinLabels(names), startTime)
}
Record is a convenience function that records completion
timing data based on the provided start time of an event.
package stats
import (
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"
	"github.com/nooncall/shazam/util/sync2"
)
type Timings struct {
	totalCount sync2.AtomicInt64
	totalTime  sync2.AtomicInt64
	mu         sync.RWMutex
	histograms map[string]*Histogram
	hook       func(string, time.Duration)
	help       string
	label      string
}
func NewTimings(name, help, label string, categories ...string) *Timings {
	t := &Timings{
		histograms: make(map[string]*Histogram),
		help:       help,
		label:      label,
	}
	for _, cat := range categories {
		t.histograms[cat] = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time")
	}
	if name != "" {
		publish(name, t)
	}
	return t
}
func (t *Timings) Add(name string, elapsed time.Duration) {
	t.mu.RLock()
	hist, ok := t.histograms[name]
	hook := t.hook
	t.mu.RUnlock()
	if !ok {
		t.mu.Lock()
		hist, ok = t.histograms[name]
		if !ok {
			hist = NewGenericHistogram("", "", bucketCutoffs, bucketLabels, "Count", "Time")
			t.histograms[name] = hist
		}
		t.mu.Unlock()
	}
	elapsedNs := int64(elapsed)
	hist.Add(elapsedNs)
	t.totalCount.Add(1)
	t.totalTime.Add(elapsedNs)
	if hook != nil {
		hook(name, elapsed)
	}
}
func (t *Timings) Record(name string, startTime time.Time) {
	t.Add(name, time.Now().Sub(startTime))
}
func (t *Timings) String() string {
	t.mu.RLock()
	defer t.mu.RUnlock()
	tm := struct {
		TotalCount int64
		TotalTime  int64
		Histograms map[string]*Histogram
	}{
		t.totalCount.Get(),
		t.totalTime.Get(),
		t.histograms,
	}
	data, err := json.Marshal(tm)
	if err != nil {
		data, _ = json.Marshal(err.Error())
	}
	return string(data)
}
func (t *Timings) Histograms() (h map[string]*Histogram) {
	t.mu.RLock()
	defer t.mu.RUnlock()
	h = make(map[string]*Histogram, len(t.histograms))
	for k, v := range t.histograms {
		h[k] = v
	}
	return
}
func (t *Timings) Count() int64 {
	return t.totalCount.Get()
}
func (t *Timings) Time() int64 {
	return t.totalTime.Get()
}
func (t *Timings) Counts() map[string]int64 {
	t.mu.RLock()
	defer t.mu.RUnlock()
	counts := make(map[string]int64, len(t.histograms)+1)
	for k, v := range t.histograms {
		counts[k] = v.Count()
	}
	counts["All"] = t.totalCount.Get()
	return counts
}
func (t *Timings) Cutoffs() []int64 {
	return bucketCutoffs
}
func (t *Timings) Help() string {
	return t.help
}
func (t *Timings) Label() string {
	return t.label
}
var bucketCutoffs = []int64{5e5, 1e6, 5e6, 1e7, 5e7, 1e8, 5e8, 1e9, 5e9, 1e10}
var bucketLabels []string
func init() {
	bucketLabels = make([]string, len(bucketCutoffs)+1)
	for i, v := range bucketCutoffs {
		bucketLabels[i] = fmt.Sprintf("%d", v)
	}
	bucketLabels[len(bucketLabels)-1] = "inf"
}
type MultiTimings struct {
	Timings
	labels []string
}
func NewMultiTimings(name string, help string, labels []string) *MultiTimings {
	t := &MultiTimings{
		Timings: Timings{
			histograms: make(map[string]*Histogram),
			help:       help,
		},
		labels: labels,
	}
	if name != "" {
		publish(name, t)
	}
	return t
}
func (mt *MultiTimings) Labels() []string {
	return mt.labels
}
func safeJoinLabels(labels []string) string {
	sanitizedLabels := make([]string, len(labels))
	for idx, label := range labels {
		sanitizedLabels[idx] = safeLabel(label)
	}
	return strings.Join(sanitizedLabels, ".")
}
func (mt *MultiTimings) Add(names []string, elapsed time.Duration) {
	if len(names) != len(mt.labels) {
		panic("MultiTimings: wrong number of values in Add")
	}
	mt.Timings.Add(safeJoinLabels(names), elapsed)
}