func safeJoinLabels(labels []string) string {
	sanitizedLabels := make([]string, len(labels))
	for idx, label := range labels {
		sanitizedLabels[idx] = safeLabel(label)
	}
	return strings.Join(sanitizedLabels, ".")
}
safeJoinLabels joins the label values with ".", but first replaces any existing
"." characters in the labels with the proper replacement, to avoid issues parsing
them apart later.
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
}