-
Notifications
You must be signed in to change notification settings - Fork 109
/
panel.go
144 lines (119 loc) · 3.46 KB
/
panel.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
144
package circuit
import (
"fmt"
"sync"
"time"
)
var defaultStatsPrefixf = "circuit.%s"
// Statter interface provides a way to gather statistics from breakers
type Statter interface {
Counter(sampleRate float32, bucket string, n ...int)
Timing(sampleRate float32, bucket string, d ...time.Duration)
Gauge(sampleRate float32, bucket string, value ...string)
}
// PanelEvent wraps a BreakerEvent and provides the string name of the breaker
type PanelEvent struct {
Name string
Event BreakerEvent
}
// Panel tracks a group of circuit breakers by name.
type Panel struct {
Statter Statter
StatsPrefixf string
Circuits map[string]*Breaker
lastTripTimes map[string]time.Time
tripTimesLock sync.RWMutex
panelLock sync.RWMutex
eventReceivers []chan PanelEvent
}
// NewPanel creates a new Panel
func NewPanel() *Panel {
return &Panel{
Circuits: make(map[string]*Breaker),
Statter: &noopStatter{},
StatsPrefixf: defaultStatsPrefixf,
lastTripTimes: make(map[string]time.Time)}
}
// Add sets the name as a reference to the given circuit breaker.
func (p *Panel) Add(name string, cb *Breaker) {
p.panelLock.Lock()
p.Circuits[name] = cb
p.panelLock.Unlock()
events := cb.Subscribe()
go func() {
for event := range events {
for _, receiver := range p.eventReceivers {
receiver <- PanelEvent{name, event}
}
switch event {
case BreakerTripped:
p.breakerTripped(name)
case BreakerReset:
p.breakerReset(name)
case BreakerFail:
p.breakerFail(name)
case BreakerReady:
p.breakerReady(name)
}
}
}()
}
// Get retrieves a circuit breaker by name. If no circuit breaker exists, it
// returns the NoOp one and sets ok to false.
func (p *Panel) Get(name string) (*Breaker, bool) {
p.panelLock.RLock()
cb, ok := p.Circuits[name]
p.panelLock.RUnlock()
if ok {
return cb, ok
}
return NewBreaker(), ok
}
// Subscribe returns a channel of PanelEvents. Whenever a breaker changes state,
// the PanelEvent will be sent over the channel. See BreakerEvent for the types of events.
func (p *Panel) Subscribe() <-chan PanelEvent {
eventReader := make(chan PanelEvent)
output := make(chan PanelEvent, 100)
go func() {
for v := range eventReader {
select {
case output <- v:
default:
<-output
output <- v
}
}
}()
p.eventReceivers = append(p.eventReceivers, eventReader)
return output
}
func (p *Panel) breakerTripped(name string) {
p.Statter.Counter(1.0, fmt.Sprintf(p.StatsPrefixf, name)+".tripped", 1)
p.tripTimesLock.Lock()
p.lastTripTimes[name] = time.Now()
p.tripTimesLock.Unlock()
}
func (p *Panel) breakerReset(name string) {
bucket := fmt.Sprintf(p.StatsPrefixf, name)
p.Statter.Counter(1.0, bucket+".reset", 1)
p.tripTimesLock.RLock()
lastTrip := p.lastTripTimes[name]
p.tripTimesLock.RUnlock()
if !lastTrip.IsZero() {
p.Statter.Timing(1.0, bucket+".trip-time", time.Since(lastTrip))
p.tripTimesLock.Lock()
p.lastTripTimes[name] = time.Time{}
p.tripTimesLock.Unlock()
}
}
func (p *Panel) breakerFail(name string) {
p.Statter.Counter(1.0, fmt.Sprintf(p.StatsPrefixf, name)+".fail", 1)
}
func (p *Panel) breakerReady(name string) {
p.Statter.Counter(1.0, fmt.Sprintf(p.StatsPrefixf, name)+".ready", 1)
}
type noopStatter struct {
}
func (*noopStatter) Counter(sampleRate float32, bucket string, n ...int) {}
func (*noopStatter) Timing(sampleRate float32, bucket string, d ...time.Duration) {}
func (*noopStatter) Gauge(sampleRate float32, bucket string, value ...string) {}