@@ -16,10 +16,14 @@ package static
16
16
17
17
import (
18
18
"bytes"
19
+ "context"
19
20
"encoding/gob"
20
21
"encoding/json"
21
22
"fmt"
22
23
"io/ioutil"
24
+ "path/filepath"
25
+ "sync/atomic"
26
+ "time"
23
27
24
28
"go.uber.org/zap"
25
29
@@ -30,31 +34,93 @@ import (
30
34
type strategyStore struct {
31
35
logger * zap.Logger
32
36
37
+ storedStrategies atomic.Value // holds *storedStrategies
38
+
39
+ ctx context.Context
40
+ cancelFunc context.CancelFunc
41
+ }
42
+
43
+ type storedStrategies struct {
33
44
defaultStrategy * sampling.SamplingStrategyResponse
34
45
serviceStrategies map [string ]* sampling.SamplingStrategyResponse
35
46
}
36
47
37
48
// NewStrategyStore creates a strategy store that holds static sampling strategies.
38
49
func NewStrategyStore (options Options , logger * zap.Logger ) (ss.StrategyStore , error ) {
50
+ ctx , cancelFunc := context .WithCancel (context .Background ())
39
51
h := & strategyStore {
40
- logger : logger ,
52
+ logger : logger ,
53
+ ctx : ctx ,
54
+ cancelFunc : cancelFunc ,
55
+ }
56
+ newStore := & storedStrategies {
41
57
serviceStrategies : make (map [string ]* sampling.SamplingStrategyResponse ),
42
58
}
59
+ newStore .defaultStrategy = defaultStrategyResponse ()
60
+ h .storedStrategies .Store (newStore )
61
+
43
62
strategies , err := loadStrategies (options .StrategiesFile )
44
63
if err != nil {
45
64
return nil , err
46
65
}
47
66
h .parseStrategies (strategies )
67
+
68
+ if options .ReloadInterval > 0 {
69
+ go h .autoUpdateStrategy (options .ReloadInterval , options .StrategiesFile )
70
+ }
48
71
return h , nil
49
72
}
50
73
51
74
// GetSamplingStrategy implements StrategyStore#GetSamplingStrategy.
52
75
func (h * strategyStore ) GetSamplingStrategy (serviceName string ) (* sampling.SamplingStrategyResponse , error ) {
53
- if strategy , ok := h .serviceStrategies [serviceName ]; ok {
76
+ ss := h .storedStrategies .Load ().(* storedStrategies )
77
+ serviceStrategies := ss .serviceStrategies
78
+ if strategy , ok := serviceStrategies [serviceName ]; ok {
54
79
return strategy , nil
55
80
}
56
81
h .logger .Debug ("sampling strategy not found, using default" , zap .String ("service" , serviceName ))
57
- return h .defaultStrategy , nil
82
+ return ss .defaultStrategy , nil
83
+ }
84
+
85
+ // StopUpdateStrategy stops updating the strategy
86
+ func (h * strategyStore ) StopUpdateStrategy () {
87
+ h .cancelFunc ()
88
+ }
89
+
90
+ func (h * strategyStore ) autoUpdateStrategy (interval time.Duration , filePath string ) {
91
+ lastString := ""
92
+ ticker := time .NewTicker (interval )
93
+ defer ticker .Stop ()
94
+ for {
95
+ select {
96
+ case <- ticker .C :
97
+ if currBytes , err := ioutil .ReadFile (filepath .Clean (filePath )); err == nil {
98
+ currStr := string (currBytes )
99
+ if lastString == currStr {
100
+ continue
101
+ }
102
+ err := h .updateSamplingStrategy (currBytes )
103
+ if err != nil {
104
+ h .logger .Error ("UpdateSamplingStrategy failed" , zap .Error (err ))
105
+ }
106
+ lastString = currStr
107
+ } else {
108
+ h .logger .Error ("UpdateSamplingStrategy failed" , zap .Error (err ))
109
+ }
110
+ case <- h .ctx .Done ():
111
+ return
112
+ }
113
+ }
114
+ }
115
+
116
+ func (h * strategyStore ) updateSamplingStrategy (bytes []byte ) error {
117
+ var strategies strategies
118
+ if err := json .Unmarshal (bytes , & strategies ); err != nil {
119
+ return fmt .Errorf ("failed to unmarshal strategies: %w" , err )
120
+ }
121
+ h .parseStrategies (& strategies )
122
+ h .logger .Info ("Updated strategy:" + string (bytes ))
123
+ return nil
58
124
}
59
125
60
126
// TODO good candidate for a global util function
@@ -74,40 +140,45 @@ func loadStrategies(strategiesFile string) (*strategies, error) {
74
140
}
75
141
76
142
func (h * strategyStore ) parseStrategies (strategies * strategies ) {
77
- h .defaultStrategy = defaultStrategyResponse ()
143
+ newStore := & storedStrategies {
144
+ serviceStrategies : make (map [string ]* sampling.SamplingStrategyResponse ),
145
+ }
146
+ newStore .defaultStrategy = defaultStrategyResponse ()
147
+ h .storedStrategies .Store (newStore )
78
148
if strategies == nil {
79
149
h .logger .Info ("No sampling strategies provided, using defaults" )
80
150
return
81
151
}
82
152
if strategies .DefaultStrategy != nil {
83
- h .defaultStrategy = h .parseServiceStrategies (strategies .DefaultStrategy )
153
+ newStore .defaultStrategy = h .parseServiceStrategies (strategies .DefaultStrategy )
84
154
}
85
155
86
156
merge := true
87
- if h .defaultStrategy .OperationSampling == nil ||
88
- h .defaultStrategy .OperationSampling .PerOperationStrategies == nil {
157
+ if newStore .defaultStrategy .OperationSampling == nil ||
158
+ newStore .defaultStrategy .OperationSampling .PerOperationStrategies == nil {
89
159
merge = false
90
160
}
91
161
92
162
for _ , s := range strategies .ServiceStrategies {
93
- h .serviceStrategies [s .Service ] = h .parseServiceStrategies (s )
163
+ newStore .serviceStrategies [s .Service ] = h .parseServiceStrategies (s )
94
164
95
165
// Merge with the default operation strategies, because only merging with
96
166
// the default strategy has no effect on service strategies (the default strategy
97
167
// is not merged with and only used as a fallback).
98
- opS := h .serviceStrategies [s .Service ].OperationSampling
168
+ opS := newStore .serviceStrategies [s .Service ].OperationSampling
99
169
if opS == nil {
100
170
// Service has no per-operation strategies, so just reference the default settings.
101
- h .serviceStrategies [s .Service ].OperationSampling = h .defaultStrategy .OperationSampling
171
+ newStore .serviceStrategies [s .Service ].OperationSampling = newStore .defaultStrategy .OperationSampling
102
172
continue
103
173
}
104
174
105
175
if merge {
106
176
opS .PerOperationStrategies = mergePerOperationSamplingStrategies (
107
177
opS .PerOperationStrategies ,
108
- h .defaultStrategy .OperationSampling .PerOperationStrategies )
178
+ newStore .defaultStrategy .OperationSampling .PerOperationStrategies )
109
179
}
110
180
}
181
+ h .storedStrategies .Store (newStore )
111
182
}
112
183
113
184
// mergePerOperationStrategies merges two operation strategies a and b, where a takes precedence over b.
0 commit comments