-
Notifications
You must be signed in to change notification settings - Fork 0
/
metric.go
143 lines (122 loc) · 3.71 KB
/
metric.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Stats package is used to collect in-request count and timing metrics.
// It's influenced by StatsD and provides a similar interface.
//
// Attach stats to a request via the router middleware functionality. The middleware
// APIs are developed and tested around gorilla mux, but should work with any middleware
// API that accepts http.Handler implementations.
//
// stats pushes data upstream using a background goroutine. Background processing is
// supported in the Appengine Standard Environment, but not in all scaling modes. See
// https://cloud.google.com/appengine/docs/standard/go/modules/runtime#RunInBackground
// for more information about background activities and scaling modes in Appengine.
//
// Backends are pluggable. A metrics storage backend need only implement the Sink interface.
// There is a Sink implementation for Stackdriver at https://github.com/efixler/stats/stackdriver.
//
// See the examples for usage info and more details.
package stats
import (
"errors"
"fmt"
"regexp"
"time"
)
var (
TimerNotStarted = errors.New("Timer was never started")
TimerNotFinished = errors.New("Timer was never finished")
RequestMetricsNotInitted = errors.New("Request metrics were not initialized (see stats.Metrics)")
legalMetricName = regexp.MustCompile(`^[a-z]+[\w./]+[a-zA-Z0-9]$`)
IllegalMetricName = errors.New(fmt.Sprintf("Names must match %s and not have consecutive dots or slashes", legalMetricName))
NoSink = errors.New("No sink set up for storing metrics")
NoSuchMetric = errors.New("No metric by that name")
)
const (
n2ms int64 = 1000000
msRnd = n2ms / 2
)
// Core interface for all metrics. This interface is public primarily for access by Sink implementations.
// It is not used directly by stats event producers.
type Metric interface {
Name() string
Data() int
}
type metric struct {
name string
data int
}
func (m *metric) Name() string {
return m.name
}
func (m *metric) Data() int { // this should maybe be an int64
return m.data
}
// Counter metric. This interface is public primarily for access by Sink implementations.
// It is not used directly by stats event producers.
type Counter struct {
*metric
}
func (c *Counter) Increment() {
c.data++
}
func (c *Counter) Decrement() {
c.data--
}
// Timer metric. This interface is public primarily for access by Sink implementations.
// It is not used directly by stats event producers.
type Timer struct {
*metric
startTime int64
}
// Nanoseconds
func (t *Timer) Duration() int64 {
return int64(t.data)
}
func (t *Timer) Milliseconds() int {
d64 := int64(t.data)
msec := d64 / n2ms
if d64%n2ms >= msRnd {
msec++
}
return int(msec)
}
func (t *Timer) String() string {
return fmt.Sprintf("T%s: %s", t.name, time.Duration(int64(t.data)))
}
func newCounter(bucket string) (*Counter, error) {
if err := checkMetricName(bucket); err != nil {
return nil, err
}
c := &Counter{metric: &metric{name: bucket, data: 0}}
return c, nil
}
func newTimer(bucket string) (*Timer, error) {
if err := checkMetricName(bucket); err != nil {
return nil, err
}
t := &Timer{metric: &metric{name: bucket}, startTime: time.Now().UnixNano()}
return t, nil
}
var ccds = regexp.MustCompile(`[./]{2,}?`)
func checkMetricName(n string) error {
if !legalMetricName.MatchString(n) {
return IllegalMetricName
} else if ccds.MatchString(n) {
return IllegalMetricName
}
return nil
}
func (t *Timer) Finish() error {
if !t.Started() {
return TimerNotStarted
} else if !t.Finished() {
now := time.Now().UnixNano()
t.data = int(now - t.startTime)
}
return nil // second+ finish will noop
}
func (t *Timer) Started() bool {
return t.startTime != 0
}
func (t *Timer) Finished() bool {
return t.data != 0
}