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