-
Notifications
You must be signed in to change notification settings - Fork 18
/
diagnostic_events.go
272 lines (250 loc) · 10.1 KB
/
diagnostic_events.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package ldclient
import (
"os"
"runtime"
"sync"
"time"
"github.com/google/uuid"
"gopkg.in/launchdarkly/go-sdk-common.v1/ldvalue"
)
type diagnosticId struct {
DiagnosticID string `json:"diagnosticId"`
SDKKeySuffix string `json:"sdkKeySuffix,omitempty"`
}
type diagnosticSDKData struct {
Name string `json:"name"`
Version string `json:"version"`
WrapperName string `json:"wrapperName,omitempty"`
WrapperVersion string `json:"wrapperVersion,omitempty"`
}
type diagnosticPlatformData struct {
Name string `json:"name"`
GoVersion string `json:"goVersion"`
OSArch string `json:"osArch"`
OSName string `json:"osName"`
OSVersion string `json:"osVersion"`
}
type milliseconds int
type diagnosticConfigData struct {
CustomBaseURI bool `json:"customBaseURI"`
CustomStreamURI bool `json:"customStreamURI"`
CustomEventsURI bool `json:"customEventsURI"`
DataStoreType ldvalue.OptionalString `json:"dataStoreType"`
EventsCapacity int `json:"eventsCapacity"`
ConnectTimeoutMillis milliseconds `json:"connectTimeoutMillis"`
SocketTimeoutMillis milliseconds `json:"socketTimeoutMillis"`
EventsFlushIntervalMillis milliseconds `json:"eventsFlushIntervalMillis"`
PollingIntervalMillis milliseconds `json:"pollingIntervalMillis"`
StartWaitMillis milliseconds `json:"startWaitMillis"`
SamplingInterval int32 `json:"samplingInterval"`
ReconnectTimeMillis milliseconds `json:"reconnectTimeMillis"`
StreamingDisabled bool `json:"streamingDisabled"`
UsingRelayDaemon bool `json:"usingRelayDaemon"`
Offline bool `json:"offline"`
AllAttributesPrivate bool `json:"allAttributesPrivate"`
InlineUsersInEvents bool `json:"inlineUsersInEvents"`
UserKeysCapacity int `json:"userKeysCapacity"`
UserKeysFlushIntervalMillis milliseconds `json:"userKeysFlushIntervalMillis"`
UsingProxy bool `json:"usingProxy"`
// UsingProxyAuthenticator bool `json:"usingProxyAuthenticator"` // not knowable in Go SDK
DiagnosticRecordingIntervalMillis milliseconds `json:"diagnosticRecordingIntervalMillis"`
}
type diagnosticBaseEvent struct {
Kind string `json:"kind"`
ID diagnosticId `json:"id"`
CreationDate uint64 `json:"creationDate"`
}
type diagnosticInitEvent struct {
diagnosticBaseEvent
SDK diagnosticSDKData `json:"sdk"`
Configuration diagnosticConfigData `json:"configuration"`
Platform diagnosticPlatformData `json:"platform"`
}
type diagnosticPeriodicEvent struct {
diagnosticBaseEvent
DataSinceDate uint64 `json:"dataSinceDate"`
DroppedEvents int `json:"droppedEvents"`
DeduplicatedUsers int `json:"deduplicatedUsers"`
EventsInLastBatch int `json:"eventsInLastBatch"`
StreamInits []diagnosticStreamInitInfo `json:"streamInits"`
}
type diagnosticStreamInitInfo struct {
Timestamp uint64 `json:"timestamp"`
Failed bool `json:"failed"`
DurationMillis milliseconds `json:"durationMillis"`
}
type diagnosticsManager struct {
id diagnosticId
config Config
startWaitTime time.Duration // this is passed in separately because in Go, it's not part of the Config
startTime uint64
dataSinceTime uint64
streamInits []diagnosticStreamInitInfo
periodicEventGate <-chan struct{}
lock sync.Mutex
}
// Optional interface that can be implemented by components whose types can't be easily
// determined by looking at the config object.
type diagnosticsComponentDescriptor interface {
GetDiagnosticsComponentTypeName() string
}
func durationToMillis(d time.Duration) milliseconds {
return milliseconds(d / time.Millisecond)
}
func newDiagnosticId(sdkKey string) diagnosticId {
uuid, _ := uuid.NewRandom()
id := diagnosticId{
DiagnosticID: uuid.String(),
}
if len(sdkKey) > 6 {
id.SDKKeySuffix = sdkKey[len(sdkKey)-6:]
} else {
id.SDKKeySuffix = sdkKey
}
return id
}
func newDiagnosticsManager(
id diagnosticId,
config Config,
startWaitTime time.Duration,
startTime time.Time,
periodicEventGate <-chan struct{}, // periodicEventGate is test instrumentation - see CanSendStatsEvent
) *diagnosticsManager {
timestamp := toUnixMillis(startTime)
m := &diagnosticsManager{
id: id,
config: config,
startWaitTime: startWaitTime,
startTime: timestamp,
dataSinceTime: timestamp,
periodicEventGate: periodicEventGate,
}
return m
}
// Called by the stream processor when a stream connection has either succeeded or failed.
func (m *diagnosticsManager) RecordStreamInit(timestamp uint64, failed bool, durationMillis milliseconds) {
m.lock.Lock()
defer m.lock.Unlock()
m.streamInits = append(m.streamInits, diagnosticStreamInitInfo{
Timestamp: timestamp,
Failed: failed,
DurationMillis: durationMillis,
})
}
// Called by DefaultEventProcessor to create the initial diagnostics event that includes the configuration.
func (m *diagnosticsManager) CreateInitEvent() diagnosticInitEvent {
sdkData := diagnosticSDKData{
Name: "go-server-sdk",
Version: Version,
}
// Notes on configData
// - reconnectTimeMillis: hard-coded in eventsource because we're not overriding StreamOptionInitialRetry.
// - usingProxy: there are many ways to implement an HTTP proxy in Go, but the only one we're capable of
// detecting is the HTTP_PROXY environment variable; programmatic approaches involve using a custom
// transport, which we have no way of distinguishing from other kinds of custom transports (for the
// same reason, we cannot detect if proxy authentication is being used).
configData := diagnosticConfigData{
CustomBaseURI: m.config.BaseUri != DefaultConfig.BaseUri,
CustomStreamURI: m.config.StreamUri != DefaultConfig.StreamUri,
CustomEventsURI: m.config.EventsUri != DefaultConfig.EventsUri,
DataStoreType: getComponentTypeName(m.config.FeatureStore),
EventsCapacity: m.config.Capacity,
ConnectTimeoutMillis: durationToMillis(m.config.Timeout),
SocketTimeoutMillis: durationToMillis(m.config.Timeout),
EventsFlushIntervalMillis: durationToMillis(m.config.FlushInterval),
PollingIntervalMillis: durationToMillis(m.config.PollInterval),
StartWaitMillis: durationToMillis(m.startWaitTime),
SamplingInterval: m.config.SamplingInterval,
ReconnectTimeMillis: durationToMillis(m.config.StreamInitialReconnectDelay),
StreamingDisabled: !m.config.Stream,
UsingRelayDaemon: m.config.UseLdd,
Offline: m.config.Offline,
AllAttributesPrivate: m.config.AllAttributesPrivate,
InlineUsersInEvents: m.config.InlineUsersInEvents,
UserKeysCapacity: m.config.UserKeysCapacity,
UserKeysFlushIntervalMillis: durationToMillis(m.config.UserKeysFlushInterval),
UsingProxy: os.Getenv("HTTP_PROXY") != "",
DiagnosticRecordingIntervalMillis: durationToMillis(m.config.DiagnosticRecordingInterval),
}
// Notes on platformData
// - osArch: in Go, GOARCH is set at compile time, not at runtime (unlike GOOS, whiich is runtime).
// - osVersion: Go provides no portable way to get this property.
platformData := diagnosticPlatformData{
Name: "Go",
GoVersion: runtime.Version(),
OSName: normalizeOSName(runtime.GOOS),
OSArch: runtime.GOARCH,
//OSVersion: // not available, see above
}
return diagnosticInitEvent{
diagnosticBaseEvent: diagnosticBaseEvent{
Kind: "diagnostic-init",
ID: m.id,
CreationDate: m.startTime,
},
SDK: sdkData,
Configuration: configData,
Platform: platformData,
}
}
// This is strictly for test instrumentation. In unit tests, we need to be able to stop DefaultEventProcessor
// from constructing the periodic event until the test has finished setting up its preconditions. This is done
// by passing in a periodicEventGate channel which the test will push to when it's ready.
func (m *diagnosticsManager) CanSendStatsEvent() bool {
if m.periodicEventGate != nil {
select {
case <-m.periodicEventGate: // non-blocking receive
return true
default:
return false
}
}
return true
}
// Called by DefaultEventProcessor to create the periodic event containing usage statistics. Some of the
// statistics are passed in as parameters because DefaultEventProcessor owns them and can more easily keep
// track of them internally - pushing them into diagnosticsManager would require frequent lock usage.
func (m *diagnosticsManager) CreateStatsEventAndReset(
droppedEvents int,
deduplicatedUsers int,
eventsInLastBatch int,
) diagnosticPeriodicEvent {
m.lock.Lock()
defer m.lock.Unlock()
timestamp := now()
event := diagnosticPeriodicEvent{
diagnosticBaseEvent: diagnosticBaseEvent{
Kind: "diagnostic",
ID: m.id,
CreationDate: timestamp,
},
DataSinceDate: m.dataSinceTime,
EventsInLastBatch: eventsInLastBatch,
DroppedEvents: droppedEvents,
DeduplicatedUsers: deduplicatedUsers,
StreamInits: m.streamInits,
}
m.streamInits = nil
m.dataSinceTime = timestamp
return event
}
func getComponentTypeName(component interface{}) ldvalue.OptionalString {
if component != nil {
if dcd, ok := component.(diagnosticsComponentDescriptor); ok {
return ldvalue.NewOptionalString(dcd.GetDiagnosticsComponentTypeName())
}
return ldvalue.NewOptionalString("custom")
}
return ldvalue.OptionalString{}
}
func normalizeOSName(osName string) string {
switch osName {
case "darwin":
return "MacOS"
case "windows":
return "Windows"
case "linux":
return "Linux"
}
return osName
}