Update split SDK to 6.0.2 to fix sync bug (#17060)
* Update split SDK to 6.0.2 to fix sync bug * Vendor and tidy
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
fa2ecad0a9
Коммит
aba00a3cfd
13
vendor/github.com/splitio/go-split-commons/v3/LICENSE
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/splitio/go-split-commons/v3/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,13 @@
|
||||
Copyright © 2020 Split Software, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
89
vendor/github.com/splitio/go-split-commons/v3/conf/conf.go
сгенерированный
поставляемый
Обычный файл
89
vendor/github.com/splitio/go-split-commons/v3/conf/conf.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,89 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
// RedisConfig struct is used to cofigure the redis parameters
|
||||
type RedisConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
Database int
|
||||
Password string
|
||||
Prefix string
|
||||
|
||||
// The network type, either tcp or unix.
|
||||
// Default is tcp.
|
||||
Network string
|
||||
|
||||
// Maximum number of retries before giving up.
|
||||
// Default is to not retry failed commands.
|
||||
MaxRetries int
|
||||
|
||||
// Dial timeout for establishing new connections.
|
||||
// Default is 5 seconds.
|
||||
DialTimeout int
|
||||
|
||||
// Timeout for socket reads. If reached, commands will fail
|
||||
// with a timeout instead of blocking.
|
||||
// Default is 10 seconds.
|
||||
ReadTimeout int
|
||||
|
||||
// Timeout for socket writes. If reached, commands will fail
|
||||
// with a timeout instead of blocking.
|
||||
// Default is 3 seconds.
|
||||
WriteTimeout int
|
||||
|
||||
// Maximum number of socket connections.
|
||||
// Default is 10 connections.
|
||||
PoolSize int
|
||||
|
||||
// Redis sentinel replication support
|
||||
SentinelAddresses []string
|
||||
SentinelMaster string
|
||||
|
||||
// Redis cluster replication support
|
||||
ClusterNodes []string
|
||||
ClusterKeyHashTag string
|
||||
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// TaskPeriods struct is used to configure the period for each synchronization task
|
||||
type TaskPeriods struct {
|
||||
SplitSync int
|
||||
SegmentSync int
|
||||
ImpressionSync int
|
||||
GaugeSync int
|
||||
CounterSync int
|
||||
LatencySync int
|
||||
EventsSync int
|
||||
}
|
||||
|
||||
// AdvancedConfig exposes more configurable parameters that can be used to further tailor the sdk to the user's needs
|
||||
// - HTTPTimeout - Timeout for HTTP requests when doing synchronization
|
||||
// - SegmentQueueSize - How many segments can be queued for updating (should be >= # segments the user has)
|
||||
// - SegmentWorkers - How many workers will be used when performing segments sync.
|
||||
type AdvancedConfig struct {
|
||||
HTTPTimeout int
|
||||
SegmentQueueSize int
|
||||
SegmentWorkers int
|
||||
SdkURL string
|
||||
EventsURL string
|
||||
EventsBulkSize int64
|
||||
EventsQueueSize int
|
||||
ImpressionsQueueSize int
|
||||
ImpressionsBulkSize int64
|
||||
StreamingEnabled bool
|
||||
AuthServiceURL string
|
||||
StreamingServiceURL string
|
||||
SplitUpdateQueueSize int64
|
||||
SegmentUpdateQueueSize int64
|
||||
}
|
||||
|
||||
// ManagerConfig exposes configurable parameters for ImpressionManager
|
||||
type ManagerConfig struct {
|
||||
OperationMode string
|
||||
ImpressionsMode string
|
||||
ListenerEnabled bool
|
||||
}
|
||||
52
vendor/github.com/splitio/go-split-commons/v3/conf/defaults.go
сгенерированный
поставляемый
Обычный файл
52
vendor/github.com/splitio/go-split-commons/v3/conf/defaults.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,52 @@
|
||||
package conf
|
||||
|
||||
const (
|
||||
defaultHTTPTimeout = 30
|
||||
defaultSegmentQueueSize = 500
|
||||
defaultSegmentWorkers = 10
|
||||
defaultEventsBulkSize = 5000
|
||||
defaultEventsQueueSize = 10000
|
||||
defaultImpressionsQueueSize = 10000
|
||||
defaultImpressionsBulkSize = 5000
|
||||
defaultStreamingEnabled = true
|
||||
defaultSplitUpdateQueueSize = 5000
|
||||
defaultSegmentUpdateQueueSize = 5000
|
||||
defaultAuthServiceURL = "https://auth.split.io"
|
||||
defaultEventsURL = "https://events.split.io/api"
|
||||
defaultSdkURL = "https://sdk.split.io/api"
|
||||
defaultStreamingServiceURL = "https://streaming.split.io/sse"
|
||||
)
|
||||
|
||||
const (
|
||||
// ImpressionsModeOptimized will avoid sending duplicated events
|
||||
ImpressionsModeOptimized = "optimized"
|
||||
// ImpressionsModeDebug will send all the impressions generated
|
||||
ImpressionsModeDebug = "debug"
|
||||
)
|
||||
|
||||
const (
|
||||
// Standalone mode
|
||||
Standalone = "inmemory-standalone"
|
||||
// ProducerSync mode
|
||||
ProducerSync = "producer-sync"
|
||||
)
|
||||
|
||||
// GetDefaultAdvancedConfig returns default conf
|
||||
func GetDefaultAdvancedConfig() AdvancedConfig {
|
||||
return AdvancedConfig{
|
||||
EventsQueueSize: defaultEventsQueueSize,
|
||||
HTTPTimeout: defaultHTTPTimeout,
|
||||
EventsBulkSize: defaultEventsBulkSize,
|
||||
ImpressionsBulkSize: defaultImpressionsBulkSize,
|
||||
ImpressionsQueueSize: defaultImpressionsQueueSize,
|
||||
SegmentQueueSize: defaultSegmentQueueSize,
|
||||
SegmentUpdateQueueSize: defaultSegmentUpdateQueueSize,
|
||||
SegmentWorkers: defaultSegmentWorkers,
|
||||
SplitUpdateQueueSize: defaultSplitUpdateQueueSize,
|
||||
StreamingEnabled: defaultStreamingEnabled,
|
||||
AuthServiceURL: defaultAuthServiceURL,
|
||||
EventsURL: defaultEventsURL,
|
||||
SdkURL: defaultSdkURL,
|
||||
StreamingServiceURL: defaultStreamingServiceURL,
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/splitio/go-split-commons/v3/dtos/event.go
сгенерированный
поставляемый
Обычный файл
35
vendor/github.com/splitio/go-split-commons/v3/dtos/event.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,35 @@
|
||||
package dtos
|
||||
|
||||
// EventDTO struct mapping events json
|
||||
type EventDTO struct {
|
||||
Key string `json:"key"`
|
||||
TrafficTypeName string `json:"trafficTypeName"`
|
||||
EventTypeID string `json:"eventTypeId"`
|
||||
Value interface{} `json:"value"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Properties map[string]interface{} `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// Size returns a relatively accurate estimation of the size of the event
|
||||
func (e *EventDTO) Size() int {
|
||||
size := 1024
|
||||
if e.Properties == nil {
|
||||
return size
|
||||
}
|
||||
|
||||
for key, value := range e.Properties {
|
||||
size += len(key)
|
||||
switch typedValue := value.(type) {
|
||||
case string:
|
||||
size += len(typedValue)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// QueueStoredEventDTO maps the stored JSON object in redis by SDKs
|
||||
type QueueStoredEventDTO struct {
|
||||
Metadata Metadata `json:"m"`
|
||||
Event EventDTO `json:"e"`
|
||||
}
|
||||
12
vendor/github.com/splitio/go-split-commons/v3/dtos/http.go
сгенерированный
поставляемый
Обычный файл
12
vendor/github.com/splitio/go-split-commons/v3/dtos/http.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,12 @@
|
||||
package dtos
|
||||
|
||||
// HTTPError represents a http error
|
||||
type HTTPError struct {
|
||||
Code int
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error implements golang error interface
|
||||
func (h HTTPError) Error() string {
|
||||
return h.Message
|
||||
}
|
||||
48
vendor/github.com/splitio/go-split-commons/v3/dtos/impression.go
сгенерированный
поставляемый
Обычный файл
48
vendor/github.com/splitio/go-split-commons/v3/dtos/impression.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,48 @@
|
||||
package dtos
|
||||
|
||||
// Impression struct to map an impression
|
||||
type Impression struct {
|
||||
KeyName string `json:"k"`
|
||||
BucketingKey string `json:"b"`
|
||||
FeatureName string `json:"f"`
|
||||
Treatment string `json:"t"`
|
||||
Label string `json:"r"`
|
||||
ChangeNumber int64 `json:"c"`
|
||||
Time int64 `json:"m"`
|
||||
Pt int64 `json:"pt,omitempty"`
|
||||
}
|
||||
|
||||
// ImpressionQueueObject struct mapping impressions
|
||||
type ImpressionQueueObject struct {
|
||||
Metadata Metadata `json:"m"`
|
||||
Impression Impression `json:"i"`
|
||||
}
|
||||
|
||||
// ImpressionDTO struct to map an impression
|
||||
type ImpressionDTO struct {
|
||||
KeyName string `json:"k"`
|
||||
Treatment string `json:"t"`
|
||||
Time int64 `json:"m"`
|
||||
ChangeNumber int64 `json:"c"`
|
||||
Label string `json:"r"`
|
||||
BucketingKey string `json:"b,omitempty"`
|
||||
Pt int64 `json:"pt,omitempty"`
|
||||
}
|
||||
|
||||
// ImpressionsDTO struct mapping impressions to post
|
||||
type ImpressionsDTO struct {
|
||||
TestName string `json:"f"`
|
||||
KeyImpressions []ImpressionDTO `json:"i"`
|
||||
}
|
||||
|
||||
// ImpressionsInTimeFrameDTO struct mapping impressionsCount in time window
|
||||
type ImpressionsInTimeFrameDTO struct {
|
||||
FeatureName string `json:"f"`
|
||||
TimeFrame int64 `json:"m"`
|
||||
RawCount int64 `json:"rc"`
|
||||
}
|
||||
|
||||
// ImpressionsCountDTO struct mapping impressions count to post
|
||||
type ImpressionsCountDTO struct {
|
||||
PerFeature []ImpressionsInTimeFrameDTO `json:"pf"`
|
||||
}
|
||||
8
vendor/github.com/splitio/go-split-commons/v3/dtos/metadata.go
сгенерированный
поставляемый
Обычный файл
8
vendor/github.com/splitio/go-split-commons/v3/dtos/metadata.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,8 @@
|
||||
package dtos
|
||||
|
||||
// Metadata is used to store sdk metadata
|
||||
type Metadata struct {
|
||||
SDKVersion string `json:"s"`
|
||||
MachineIP string `json:"i"`
|
||||
MachineName string `json:"n"`
|
||||
}
|
||||
132
vendor/github.com/splitio/go-split-commons/v3/dtos/metrics.go
сгенерированный
поставляемый
Обычный файл
132
vendor/github.com/splitio/go-split-commons/v3/dtos/metrics.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,132 @@
|
||||
package dtos
|
||||
|
||||
// LatenciesDTO struct mapping latencies post
|
||||
type LatenciesDTO struct {
|
||||
MetricName string `json:"name"`
|
||||
Latencies []int64 `json:"latencies"`
|
||||
}
|
||||
|
||||
// CounterDTO struct mapping counts post
|
||||
type CounterDTO struct {
|
||||
MetricName string `json:"name"`
|
||||
Count int64 `json:"delta"`
|
||||
}
|
||||
|
||||
// GaugeDTO struct mapping gauges post
|
||||
type GaugeDTO struct {
|
||||
MetricName string `json:"name"`
|
||||
Gauge float64 `json:"value"`
|
||||
}
|
||||
|
||||
const maxBuckets = 23
|
||||
|
||||
// LatencyDataBulk holds all latencies fetched from storage sorted properly.
|
||||
type LatencyDataBulk struct {
|
||||
data map[string]map[string]map[string][]int64
|
||||
}
|
||||
|
||||
// PutLatency adds a new latency to the structure
|
||||
func (l *LatencyDataBulk) PutLatency(sdk string, machineIP string, metricName string, bucketNumber int, value int64) {
|
||||
|
||||
if _, ok := l.data[sdk]; !ok {
|
||||
l.data[sdk] = make(map[string]map[string][]int64)
|
||||
}
|
||||
|
||||
if _, ok := l.data[sdk][machineIP]; !ok {
|
||||
l.data[sdk][machineIP] = make(map[string][]int64)
|
||||
}
|
||||
|
||||
if _, ok := l.data[sdk][machineIP][metricName]; !ok {
|
||||
l.data[sdk][machineIP][metricName] = make([]int64, maxBuckets)
|
||||
}
|
||||
|
||||
l.data[sdk][machineIP][metricName][bucketNumber] = value
|
||||
}
|
||||
|
||||
// ForEach iterates thru all latencies
|
||||
func (l *LatencyDataBulk) ForEach(callback func(string, string, map[string][]int64)) {
|
||||
for sdk, byIP := range l.data {
|
||||
for ip, byName := range byIP {
|
||||
callback(sdk, ip, byName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewLatencyDataBulk creates a new Latency holding structure
|
||||
func NewLatencyDataBulk() *LatencyDataBulk {
|
||||
return &LatencyDataBulk{
|
||||
data: make(map[string]map[string]map[string][]int64),
|
||||
}
|
||||
}
|
||||
|
||||
// CounterDataBulk holds all counters fetched from storage sorted properly.
|
||||
type CounterDataBulk struct {
|
||||
data map[string]map[string]map[string]int64
|
||||
}
|
||||
|
||||
// PutCounter adds a counter to the structure
|
||||
func (l *CounterDataBulk) PutCounter(sdk string, machineIP string, metricName string, value int64) {
|
||||
|
||||
if _, ok := l.data[sdk]; !ok {
|
||||
l.data[sdk] = make(map[string]map[string]int64)
|
||||
}
|
||||
|
||||
if _, ok := l.data[sdk][machineIP]; !ok {
|
||||
l.data[sdk][machineIP] = make(map[string]int64)
|
||||
}
|
||||
|
||||
l.data[sdk][machineIP][metricName] = value
|
||||
}
|
||||
|
||||
// ForEach iterates thru all counters
|
||||
func (l *CounterDataBulk) ForEach(callback func(string, string, map[string]int64)) {
|
||||
for sdk, byIP := range l.data {
|
||||
for ip, byName := range byIP {
|
||||
callback(sdk, ip, byName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounterDataBulk creates a new Counter holding structure
|
||||
func NewCounterDataBulk() *CounterDataBulk {
|
||||
return &CounterDataBulk{
|
||||
data: make(map[string]map[string]map[string]int64),
|
||||
}
|
||||
}
|
||||
|
||||
// GaugeDataBulk holds all gauges fetched from storage sorted properly.
|
||||
type GaugeDataBulk struct {
|
||||
data map[string]map[string]map[string]float64
|
||||
}
|
||||
|
||||
// PutGauge adds a gauge to the structure
|
||||
func (l *GaugeDataBulk) PutGauge(sdk string, machineIP string, metricName string, value float64) {
|
||||
|
||||
if _, ok := l.data[sdk]; !ok {
|
||||
l.data[sdk] = make(map[string]map[string]float64)
|
||||
}
|
||||
|
||||
if _, ok := l.data[sdk][machineIP]; !ok {
|
||||
l.data[sdk][machineIP] = make(map[string]float64)
|
||||
}
|
||||
|
||||
l.data[sdk][machineIP][metricName] = value
|
||||
}
|
||||
|
||||
// ForEach iterates thru all gauges
|
||||
func (l *GaugeDataBulk) ForEach(callback func(string, string, string, float64)) {
|
||||
for sdk, byIP := range l.data {
|
||||
for ip, byName := range byIP {
|
||||
for name, value := range byName {
|
||||
callback(sdk, ip, name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewGaugeDataBulk creates a new Gauge holding structure
|
||||
func NewGaugeDataBulk() *GaugeDataBulk {
|
||||
return &GaugeDataBulk{
|
||||
data: make(map[string]map[string]map[string]float64),
|
||||
}
|
||||
}
|
||||
151
vendor/github.com/splitio/go-split-commons/v3/dtos/notification.go
сгенерированный
поставляемый
Обычный файл
151
vendor/github.com/splitio/go-split-commons/v3/dtos/notification.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,151 @@
|
||||
package dtos
|
||||
|
||||
/*
|
||||
const (
|
||||
// SplitUpdate used when split is updated
|
||||
SplitUpdate = "SPLIT_UPDATE"
|
||||
// SplitKill used when split is killed
|
||||
SplitKill = "SPLIT_KILL"
|
||||
// SegmentUpdate used when segment is updated
|
||||
SegmentUpdate = "SEGMENT_UPDATE"
|
||||
// MySegmentsUpdate used when mySegment is updated
|
||||
MySegmentsUpdate = "MY_SEGMENTS_UPDATE"
|
||||
// Control for control
|
||||
Control = "CONTROL"
|
||||
// StreamingPause for controlType
|
||||
StreamingPause = "STREAMING_PAUSED"
|
||||
// StreamingResumed for controlType
|
||||
StreamingResumed = "STREAMING_RESUMED"
|
||||
// StreamingDisabled for controlType
|
||||
StreamingDisabled = "STREAMING_DISABLED"
|
||||
)
|
||||
|
||||
// IncomingNotification struct for incoming notification from streaming
|
||||
type IncomingNotification struct {
|
||||
Channel string `json:"channel"`
|
||||
ChangeNumber *int64 `json:"changeNumber,omitempty"`
|
||||
ControlType *string `json:"controlType,omitempty"`
|
||||
DefaultTreatment *string `json:"defaultTreatment,omitempty"`
|
||||
SegmentName *string `json:"segmentName,omitempty"`
|
||||
SplitName *string `json:"splitName,omitempty"`
|
||||
Timestamp *int64 `json:"timestamp,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Notification should be implemented by all notification types
|
||||
type Notification interface {
|
||||
ChannelName() string
|
||||
NotificationType() string
|
||||
}
|
||||
|
||||
// base struct with added logic that wraps around a DTO
|
||||
type base struct {
|
||||
channelName string
|
||||
notificationType string
|
||||
}
|
||||
|
||||
// ChannelName returns channel name
|
||||
func (b base) ChannelName() string {
|
||||
return b.channelName
|
||||
}
|
||||
|
||||
// NotificationType returns the type of the notification
|
||||
func (b base) NotificationType() string {
|
||||
return b.notificationType
|
||||
}
|
||||
|
||||
// ControlNotification notification for control channels
|
||||
type ControlNotification struct {
|
||||
base
|
||||
ControlType string
|
||||
}
|
||||
|
||||
// NewControlNotification builds a notification for controlling connection
|
||||
func NewControlNotification(channelName string, controlType string) ControlNotification {
|
||||
return ControlNotification{
|
||||
base: base{
|
||||
channelName: channelName,
|
||||
notificationType: Control,
|
||||
},
|
||||
ControlType: controlType,
|
||||
}
|
||||
}
|
||||
|
||||
// MySegmentsNotification notification when MySegments is updated
|
||||
type MySegmentsNotification struct {
|
||||
base
|
||||
IncludesPayload bool
|
||||
Payload []string
|
||||
ChangeNumber int64
|
||||
}
|
||||
|
||||
// NewMySegmentsNotification builds a MySegments notification
|
||||
func NewMySegmentsNotification(channelName string, includesPayload bool, payload []string, changeNumber int64) MySegmentsNotification {
|
||||
return MySegmentsNotification{
|
||||
base: base{
|
||||
channelName: channelName,
|
||||
notificationType: MySegmentsUpdate,
|
||||
},
|
||||
IncludesPayload: includesPayload,
|
||||
Payload: payload,
|
||||
ChangeNumber: changeNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// SegmentChangeNotification notification when a Segment is updated
|
||||
type SegmentChangeNotification struct {
|
||||
base
|
||||
ChangeNumber int64
|
||||
SegmentName string
|
||||
}
|
||||
|
||||
// NewSegmentChangeNotification builds a segment change notification
|
||||
func NewSegmentChangeNotification(channelName string, changeNumber int64, segmentName string) SegmentChangeNotification {
|
||||
return SegmentChangeNotification{
|
||||
base: base{
|
||||
channelName: channelName,
|
||||
notificationType: SegmentUpdate,
|
||||
},
|
||||
ChangeNumber: changeNumber,
|
||||
SegmentName: segmentName,
|
||||
}
|
||||
}
|
||||
|
||||
// SplitChangeNotification notification to send a fetch to splitChanges
|
||||
type SplitChangeNotification struct {
|
||||
base
|
||||
ChangeNumber int64
|
||||
}
|
||||
|
||||
// NewSplitChangeNotification builds a split change notification
|
||||
func NewSplitChangeNotification(channelName string, changeNumber int64) SplitChangeNotification {
|
||||
return SplitChangeNotification{
|
||||
base: base{
|
||||
channelName: channelName,
|
||||
notificationType: SplitUpdate,
|
||||
},
|
||||
ChangeNumber: changeNumber,
|
||||
}
|
||||
}
|
||||
|
||||
// SplitKillNotification notification when Split is killed
|
||||
type SplitKillNotification struct {
|
||||
base
|
||||
ChangeNumber int64
|
||||
DefaultTreatment string
|
||||
SplitName string
|
||||
}
|
||||
|
||||
// NewSplitKillNotification builds a killed split notification
|
||||
func NewSplitKillNotification(channelName string, changeNumber int64, defaultTreatment string, splitName string) SplitKillNotification {
|
||||
return SplitKillNotification{
|
||||
base: base{
|
||||
channelName: channelName,
|
||||
notificationType: SplitKill,
|
||||
},
|
||||
ChangeNumber: changeNumber,
|
||||
DefaultTreatment: defaultTreatment,
|
||||
SplitName: splitName,
|
||||
}
|
||||
}
|
||||
*/
|
||||
22
vendor/github.com/splitio/go-split-commons/v3/dtos/segment.go
сгенерированный
поставляемый
Обычный файл
22
vendor/github.com/splitio/go-split-commons/v3/dtos/segment.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,22 @@
|
||||
package dtos
|
||||
|
||||
// SegmentChangesDTO struct to map a segment change message.
|
||||
type SegmentChangesDTO struct {
|
||||
Name string `json:"name"`
|
||||
Added []string `json:"added"`
|
||||
Removed []string `json:"removed"`
|
||||
Since int64 `json:"since"`
|
||||
Till int64 `json:"till"`
|
||||
}
|
||||
|
||||
// SegmentKeyDTO maps key data
|
||||
type SegmentKeyDTO struct {
|
||||
Name string `json:"name"`
|
||||
LastModified int64 `json:"lastModified"`
|
||||
Removed bool `json:"removed"`
|
||||
}
|
||||
|
||||
// MySegmentDTO struct mapping segment data for mySegments endpoint
|
||||
type MySegmentDTO struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
100
vendor/github.com/splitio/go-split-commons/v3/dtos/split.go
сгенерированный
поставляемый
Обычный файл
100
vendor/github.com/splitio/go-split-commons/v3/dtos/split.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,100 @@
|
||||
package dtos
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// SplitChangesDTO structure to map JSON message sent by Split servers.
|
||||
type SplitChangesDTO struct {
|
||||
Till int64 `json:"till"`
|
||||
Since int64 `json:"since"`
|
||||
Splits []SplitDTO `json:"splits"`
|
||||
}
|
||||
|
||||
// SplitDTO structure to map an Split definition fetched from JSON message.
|
||||
type SplitDTO struct {
|
||||
ChangeNumber int64 `json:"changeNumber"`
|
||||
TrafficTypeName string `json:"trafficTypeName"`
|
||||
Name string `json:"name"`
|
||||
TrafficAllocation int `json:"trafficAllocation"`
|
||||
TrafficAllocationSeed int64 `json:"trafficAllocationSeed"`
|
||||
Seed int64 `json:"seed"`
|
||||
Status string `json:"status"`
|
||||
Killed bool `json:"killed"`
|
||||
DefaultTreatment string `json:"defaultTreatment"`
|
||||
Algo int `json:"algo"`
|
||||
Conditions []ConditionDTO `json:"conditions"`
|
||||
Configurations map[string]string `json:"configurations"`
|
||||
}
|
||||
|
||||
// MarshalBinary exports SplitDTO to JSON string
|
||||
func (s SplitDTO) MarshalBinary() (data []byte, err error) {
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
// ConditionDTO structure to map a Condition fetched from JSON message.
|
||||
type ConditionDTO struct {
|
||||
ConditionType string `json:"conditionType"`
|
||||
MatcherGroup MatcherGroupDTO `json:"matcherGroup"`
|
||||
Partitions []PartitionDTO `json:"partitions"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
// PartitionDTO structure to map a Partition definition fetched from JSON message.
|
||||
type PartitionDTO struct {
|
||||
Treatment string `json:"treatment"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
// MatcherGroupDTO structure to map a Matcher Group definition fetched from JSON message.
|
||||
type MatcherGroupDTO struct {
|
||||
Combiner string `json:"combiner"`
|
||||
Matchers []MatcherDTO `json:"matchers"`
|
||||
}
|
||||
|
||||
// MatcherDTO structure to map a Matcher definition fetched from JSON message.
|
||||
type MatcherDTO struct {
|
||||
KeySelector *KeySelectorDTO `json:"keySelector"`
|
||||
MatcherType string `json:"matcherType"`
|
||||
Negate bool `json:"negate"`
|
||||
UserDefinedSegment *UserDefinedSegmentMatcherDataDTO `json:"userDefinedSegmentMatcherData"`
|
||||
Whitelist *WhitelistMatcherDataDTO `json:"whitelistMatcherData"`
|
||||
UnaryNumeric *UnaryNumericMatcherDataDTO `json:"unaryNumericMatcherData"`
|
||||
Between *BetweenMatcherDataDTO `json:"betweenMatcherData"`
|
||||
Dependency *DependencyMatcherDataDTO `json:"dependencyMatcherData"`
|
||||
Boolean *bool `json:"booleanMatcherData"`
|
||||
String *string `json:"stringMatcherData"`
|
||||
}
|
||||
|
||||
// UserDefinedSegmentMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
|
||||
type UserDefinedSegmentMatcherDataDTO struct {
|
||||
SegmentName string `json:"segmentName"`
|
||||
}
|
||||
|
||||
// BetweenMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
|
||||
type BetweenMatcherDataDTO struct {
|
||||
DataType string `json:"dataType"` //NUMBER or DATETIME
|
||||
Start int64 `json:"start"`
|
||||
End int64 `json:"end"`
|
||||
}
|
||||
|
||||
// UnaryNumericMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
|
||||
type UnaryNumericMatcherDataDTO struct {
|
||||
DataType string `json:"dataType"` //NUMBER or DATETIME
|
||||
Value int64 `json:"value"`
|
||||
}
|
||||
|
||||
// WhitelistMatcherDataDTO structure to map a Matcher definition fetched from JSON message.
|
||||
type WhitelistMatcherDataDTO struct {
|
||||
Whitelist []string `json:"whitelist"`
|
||||
}
|
||||
|
||||
// DependencyMatcherDataDTO structure to map matcher definition fetched from JSON message.
|
||||
type DependencyMatcherDataDTO struct {
|
||||
Split string `json:"split"`
|
||||
Treatments []string `json:"treatments"`
|
||||
}
|
||||
|
||||
// KeySelectorDTO structure to map a Key slector definition fetched from JSON message.
|
||||
type KeySelectorDTO struct {
|
||||
TrafficType string `json:"trafficType"`
|
||||
Attribute *string `json:"attribute"`
|
||||
}
|
||||
103
vendor/github.com/splitio/go-split-commons/v3/dtos/token.go
сгенерированный
поставляемый
Обычный файл
103
vendor/github.com/splitio/go-split-commons/v3/dtos/token.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,103 @@
|
||||
package dtos
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const gracePeriod = 10 * time.Minute
|
||||
const metadataPlaceHolder = "channel-metadata:publishers"
|
||||
const occupancy = "[?occupancy=metrics.publishers]"
|
||||
|
||||
// Token dto
|
||||
type Token struct {
|
||||
Token string `json:"token"`
|
||||
PushEnabled bool `json:"pushEnabled"`
|
||||
}
|
||||
|
||||
// TokenPayload payload dto
|
||||
type TokenPayload struct {
|
||||
Capabilitites string `json:"x-ably-capability"`
|
||||
Exp int64 `json:"exp"`
|
||||
Iat int64 `json:"iat"`
|
||||
}
|
||||
|
||||
// ParsedCapabilities capabilities
|
||||
type ParsedCapabilities map[string][]string
|
||||
|
||||
func isMetadataType(capabilities []string) bool {
|
||||
for _, capability := range capabilities {
|
||||
if capability == metadataPlaceHolder {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ChannelList grabs the channel list from capabilities
|
||||
func (t *Token) ChannelList() ([]string, error) {
|
||||
if !t.PushEnabled || t.Token == "" {
|
||||
return nil, errors.New("Push disabled or no token set")
|
||||
}
|
||||
|
||||
tokenParts := strings.Split(t.Token, ".")
|
||||
if len(tokenParts) < 2 {
|
||||
return nil, errors.New("Cannot decode token")
|
||||
}
|
||||
decodedPayload, err := base64.RawURLEncoding.DecodeString(tokenParts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parsedPayload TokenPayload
|
||||
err = json.Unmarshal(decodedPayload, &parsedPayload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parsedCapabilities ParsedCapabilities
|
||||
err = json.Unmarshal([]byte(parsedPayload.Capabilitites), &parsedCapabilities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channelList := make([]string, 0, len(parsedCapabilities))
|
||||
for channelName := range parsedCapabilities {
|
||||
if isMetadataType(parsedCapabilities[channelName]) {
|
||||
channelList = append(channelList, fmt.Sprintf("%s%s", occupancy, channelName))
|
||||
} else {
|
||||
channelList = append(channelList, channelName)
|
||||
}
|
||||
}
|
||||
|
||||
return channelList, nil
|
||||
}
|
||||
|
||||
// CalculateNextTokenExpiration calculates next token expiration
|
||||
func (t *Token) CalculateNextTokenExpiration() (time.Duration, error) {
|
||||
if !t.PushEnabled || t.Token == "" {
|
||||
return 0, errors.New("Push disabled or no token set")
|
||||
}
|
||||
|
||||
tokenParts := strings.Split(t.Token, ".")
|
||||
if len(tokenParts) < 2 {
|
||||
return 0, errors.New("Cannot decode token")
|
||||
}
|
||||
decodedPayload, err := base64.RawURLEncoding.DecodeString(tokenParts[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var parsedPayload TokenPayload
|
||||
err = json.Unmarshal(decodedPayload, &parsedPayload)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tokenDuration := parsedPayload.Exp - parsedPayload.Iat
|
||||
return time.Duration(tokenDuration)*time.Second - gracePeriod, nil
|
||||
}
|
||||
59
vendor/github.com/splitio/go-split-commons/v3/provisional/impcounter.go
сгенерированный
поставляемый
Обычный файл
59
vendor/github.com/splitio/go-split-commons/v3/provisional/impcounter.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,59 @@
|
||||
package provisional
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/util"
|
||||
)
|
||||
|
||||
// Key struct for mapping each key to an amount
|
||||
type Key struct {
|
||||
FeatureName string
|
||||
TimeFrame int64
|
||||
}
|
||||
|
||||
// ImpressionsCounter struct for storing generated impressions counts
|
||||
type ImpressionsCounter struct {
|
||||
impressionsCounts map[Key]int64
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewImpressionsCounter creates new ImpressionsCounter
|
||||
func NewImpressionsCounter() *ImpressionsCounter {
|
||||
return &ImpressionsCounter{
|
||||
impressionsCounts: make(map[Key]int64),
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func makeKey(splitName string, timeFrame int64) Key {
|
||||
return Key{
|
||||
FeatureName: splitName,
|
||||
TimeFrame: util.TruncateTimeFrame(timeFrame),
|
||||
}
|
||||
}
|
||||
|
||||
// Inc increments the quantity of impressions with the passed splitName and timeFrame
|
||||
func (i *ImpressionsCounter) Inc(splitName string, timeFrame int64, amount int64) {
|
||||
i.mutex.Lock()
|
||||
defer i.mutex.Unlock()
|
||||
key := makeKey(splitName, timeFrame)
|
||||
currentAmount, _ := i.impressionsCounts[key]
|
||||
i.impressionsCounts[key] = currentAmount + amount
|
||||
}
|
||||
|
||||
// PopAll returns all the elements stored in the cache and resets the cache
|
||||
func (i *ImpressionsCounter) PopAll() map[Key]int64 {
|
||||
i.mutex.Lock()
|
||||
defer i.mutex.Unlock()
|
||||
toReturn := i.impressionsCounts
|
||||
i.impressionsCounts = make(map[Key]int64)
|
||||
return toReturn
|
||||
}
|
||||
|
||||
// Size returns how many keys are stored in cache
|
||||
func (i *ImpressionsCounter) Size() int {
|
||||
i.mutex.RLock()
|
||||
defer i.mutex.RUnlock()
|
||||
return len(i.impressionsCounts)
|
||||
}
|
||||
43
vendor/github.com/splitio/go-split-commons/v3/provisional/imphasher.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v3/provisional/imphasher.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package provisional
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/provisional/hashing"
|
||||
)
|
||||
|
||||
const hashKeyTemplate = "%s:%s:%s:%s:%d"
|
||||
|
||||
func unknownIfEmpty(s string) string {
|
||||
if len(strings.TrimSpace(s)) == 0 {
|
||||
return "UNKNOWN"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ImpressionHasher interface
|
||||
type ImpressionHasher interface {
|
||||
Process(featureName string, impression *dtos.Impression) (int64, error)
|
||||
}
|
||||
|
||||
// ImpressionHasherImpl implements the hasher interface, mapping certain fields to an int64
|
||||
type ImpressionHasherImpl struct{}
|
||||
|
||||
// Process an impression and return the 64 LSBs of a murmur3-128 digest
|
||||
func (h *ImpressionHasherImpl) Process(featureName string, impression *dtos.Impression) (int64, error) {
|
||||
if impression == nil {
|
||||
return 0, fmt.Errorf("keyImpression cannot be nil")
|
||||
}
|
||||
|
||||
toHash := fmt.Sprintf(hashKeyTemplate,
|
||||
unknownIfEmpty(impression.KeyName),
|
||||
unknownIfEmpty(featureName),
|
||||
unknownIfEmpty(impression.Treatment),
|
||||
unknownIfEmpty(impression.Label),
|
||||
impression.ChangeNumber)
|
||||
|
||||
h1, _ := hashing.Sum128([]byte(toHash))
|
||||
return int64(h1), nil
|
||||
}
|
||||
76
vendor/github.com/splitio/go-split-commons/v3/provisional/impmanager.go
сгенерированный
поставляемый
Обычный файл
76
vendor/github.com/splitio/go-split-commons/v3/provisional/impmanager.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,76 @@
|
||||
package provisional
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/util"
|
||||
)
|
||||
|
||||
const lastSeenCacheSize = 500000 // cache up to 500k impression hashes
|
||||
|
||||
// ImpressionManager interface
|
||||
type ImpressionManager interface {
|
||||
ProcessImpressions(impressions []dtos.Impression) ([]dtos.Impression, []dtos.Impression)
|
||||
}
|
||||
|
||||
// ImpressionManagerImpl implements
|
||||
type ImpressionManagerImpl struct {
|
||||
impressionObserver ImpressionObserver
|
||||
impressionsCounter *ImpressionsCounter
|
||||
shouldAddPreviousTime bool
|
||||
isOptimized bool
|
||||
listenerEnabled bool
|
||||
}
|
||||
|
||||
// NewImpressionManager creates new ImpManager
|
||||
func NewImpressionManager(managerConfig conf.ManagerConfig, impressionCounter *ImpressionsCounter) (ImpressionManager, error) {
|
||||
impressionObserver, err := NewImpressionObserver(lastSeenCacheSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
impManager := &ImpressionManagerImpl{
|
||||
impressionObserver: impressionObserver,
|
||||
impressionsCounter: impressionCounter,
|
||||
shouldAddPreviousTime: util.ShouldAddPreviousTime(managerConfig),
|
||||
isOptimized: impressionCounter != nil && util.ShouldBeOptimized(managerConfig),
|
||||
listenerEnabled: managerConfig.ListenerEnabled,
|
||||
}
|
||||
|
||||
return impManager, nil
|
||||
}
|
||||
|
||||
func (i *ImpressionManagerImpl) processImpression(impression dtos.Impression, forLog []dtos.Impression, forListener []dtos.Impression) ([]dtos.Impression, []dtos.Impression) {
|
||||
if i.shouldAddPreviousTime {
|
||||
impression.Pt, _ = i.impressionObserver.TestAndSet(impression.FeatureName, &impression) // Adds previous time if it is enabled
|
||||
}
|
||||
|
||||
now := time.Now().UTC().UnixNano()
|
||||
if i.isOptimized { // isOptimized
|
||||
i.impressionsCounter.Inc(impression.FeatureName, now, 1) // Increments impression counter per featureName
|
||||
}
|
||||
|
||||
if !i.isOptimized || impression.Pt == 0 || impression.Pt < util.TruncateTimeFrame(now) {
|
||||
forLog = append(forLog, impression)
|
||||
}
|
||||
|
||||
if i.listenerEnabled {
|
||||
forListener = append(forListener, impression)
|
||||
}
|
||||
|
||||
return forLog, forListener
|
||||
}
|
||||
|
||||
// ProcessImpressions bulk processes
|
||||
func (i *ImpressionManagerImpl) ProcessImpressions(impressions []dtos.Impression) ([]dtos.Impression, []dtos.Impression) {
|
||||
forLog := make([]dtos.Impression, 0, len(impressions))
|
||||
forListener := make([]dtos.Impression, 0, len(impressions))
|
||||
|
||||
for _, impression := range impressions {
|
||||
forLog, forListener = i.processImpression(impression, forLog, forListener)
|
||||
}
|
||||
|
||||
return forLog, forListener
|
||||
}
|
||||
61
vendor/github.com/splitio/go-split-commons/v3/provisional/impobserver.go
сгенерированный
поставляемый
Обычный файл
61
vendor/github.com/splitio/go-split-commons/v3/provisional/impobserver.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,61 @@
|
||||
package provisional
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/provisional/int64cache"
|
||||
)
|
||||
|
||||
// ImpressionObserver is used to check wether an impression has been previously seen
|
||||
type ImpressionObserver interface {
|
||||
TestAndSet(featureName string, impression *dtos.Impression) (int64, error)
|
||||
}
|
||||
|
||||
// ImpressionObserverImpl is an implementation of the ImpressionObserver interface
|
||||
type ImpressionObserverImpl struct {
|
||||
cache int64cache.Int64Cache
|
||||
hasher ImpressionHasher
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Atomically fetch cache data and update it
|
||||
func (o *ImpressionObserverImpl) testAndSet(key int64, newValue int64) (int64, error) {
|
||||
o.mutex.Lock()
|
||||
defer o.mutex.Unlock()
|
||||
old, err := o.cache.Get(key)
|
||||
o.cache.Set(key, newValue)
|
||||
return old, err
|
||||
}
|
||||
|
||||
// TestAndSet hashes the impression, updates the cache and returns the previous value
|
||||
func (o *ImpressionObserverImpl) TestAndSet(featureName string, impression *dtos.Impression) (int64, error) {
|
||||
hash, err := o.hasher.Process(featureName, impression)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error hashing impression: %s", err.Error())
|
||||
}
|
||||
|
||||
return o.testAndSet(hash, impression.Time)
|
||||
}
|
||||
|
||||
// NewImpressionObserver constructs a new ImpressionObserver
|
||||
func NewImpressionObserver(size int) (*ImpressionObserverImpl, error) {
|
||||
cache, err := int64cache.NewInt64Cache(size)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building cache: %s", err.Error())
|
||||
}
|
||||
return &ImpressionObserverImpl{
|
||||
cache: cache,
|
||||
hasher: &ImpressionHasherImpl{},
|
||||
mutex: sync.Mutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ImpressionObserverNoOp is an implementation of the ImpressionObserver interface
|
||||
type ImpressionObserverNoOp struct{}
|
||||
|
||||
// TestAndSet that does nothing
|
||||
func (o *ImpressionObserverNoOp) TestAndSet(featureName string, impression *dtos.Impression) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
13
vendor/github.com/splitio/go-split-commons/v3/push/borrowed.go
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/splitio/go-split-commons/v3/push/borrowed.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,13 @@
|
||||
package push
|
||||
|
||||
// Borrowed synchronizer interface to break circular dependencies
|
||||
type synchronizerInterface interface {
|
||||
SyncAll(requestNoCache bool) error
|
||||
SynchronizeSplits(till *int64, requestNoCache bool) error
|
||||
LocalKill(splitName string, defaultTreatment string, changeNumber int64)
|
||||
SynchronizeSegment(segmentName string, till *int64, requestNoCache bool) error
|
||||
StartPeriodicFetching()
|
||||
StopPeriodicFetching()
|
||||
StartPeriodicDataRecording()
|
||||
StopPeriodicDataRecording()
|
||||
}
|
||||
14
vendor/github.com/splitio/go-split-commons/v3/push/constants.go
сгенерированный
поставляемый
Обычный файл
14
vendor/github.com/splitio/go-split-commons/v3/push/constants.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,14 @@
|
||||
package push
|
||||
|
||||
const (
|
||||
workerStatusIdle = iota
|
||||
workerStatusRunning
|
||||
workerStatusShuttingDown
|
||||
)
|
||||
|
||||
const (
|
||||
pushManagerStatusIdle = iota
|
||||
pushManagerStatusInitializing
|
||||
pushManagerStatusRunning
|
||||
pushManagerStatusShuttingDown
|
||||
)
|
||||
237
vendor/github.com/splitio/go-split-commons/v3/push/manager.go
сгенерированный
поставляемый
Обычный файл
237
vendor/github.com/splitio/go-split-commons/v3/push/manager.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,237 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/service/api/sse"
|
||||
"github.com/splitio/go-toolkit/v4/common"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
)
|
||||
|
||||
// Status update contants that will be propagated to the push manager's user
|
||||
const (
|
||||
StatusUp = iota
|
||||
StatusDown
|
||||
StatusRetryableError
|
||||
StatusNonRetryableError
|
||||
)
|
||||
|
||||
// ErrAlreadyRunning is the error to be returned when .Start() is called on an already running instance
|
||||
var ErrAlreadyRunning = errors.New("push manager already running")
|
||||
|
||||
// ErrNotRunning is the error to be returned when .Stop() is called on a non-running instance
|
||||
var ErrNotRunning = errors.New("push manager not running")
|
||||
|
||||
// Manager interface contains public methods for push manager
|
||||
type Manager interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
StopWorkers()
|
||||
StartWorkers()
|
||||
}
|
||||
|
||||
// ManagerImpl implements the manager interface
|
||||
type ManagerImpl struct {
|
||||
parser NotificationParser
|
||||
sseClient sse.StreamingClient
|
||||
authAPI service.AuthClient
|
||||
processor Processor
|
||||
statusTracker StatusTracker
|
||||
feedback FeedbackLoop
|
||||
nextRefresh *time.Timer
|
||||
refreshTokenMutex sync.Mutex
|
||||
/*
|
||||
running *gtSync.AtomicBool
|
||||
status int32
|
||||
shutdownWaiter chan struct{}
|
||||
*/
|
||||
lifecycle lifecycle.Manager
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// FeedbackLoop is a type alias for the type of chan that must be supplied for push status tobe propagated
|
||||
type FeedbackLoop = chan<- int64
|
||||
|
||||
// NewManager constructs a new push manager
|
||||
func NewManager(
|
||||
logger logging.LoggerInterface,
|
||||
synchronizer synchronizerInterface,
|
||||
cfg *conf.AdvancedConfig,
|
||||
feedbackLoop chan<- int64,
|
||||
authAPI service.AuthClient,
|
||||
) (*ManagerImpl, error) {
|
||||
|
||||
processor, err := NewProcessor(cfg.SplitUpdateQueueSize, cfg.SegmentUpdateQueueSize, synchronizer, logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error instantiating processor: %w", err)
|
||||
}
|
||||
|
||||
statusTracker := NewStatusTracker(logger)
|
||||
parser := &NotificationParserImpl{
|
||||
logger: logger,
|
||||
onSplitUpdate: processor.ProcessSplitChangeUpdate,
|
||||
onSplitKill: processor.ProcessSplitKillUpdate,
|
||||
onSegmentUpdate: processor.ProcessSegmentChangeUpdate,
|
||||
onControlUpdate: statusTracker.HandleControl,
|
||||
onOccupancyMesage: statusTracker.HandleOccupancy,
|
||||
onAblyError: statusTracker.HandleAblyError,
|
||||
}
|
||||
|
||||
manager := &ManagerImpl{
|
||||
authAPI: authAPI,
|
||||
sseClient: sse.NewStreamingClient(cfg, logger),
|
||||
statusTracker: statusTracker,
|
||||
feedback: feedbackLoop,
|
||||
processor: processor,
|
||||
parser: parser,
|
||||
logger: logger,
|
||||
}
|
||||
manager.lifecycle.Setup()
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
// Start initiates the authentication flow and if successful initiates a connection
|
||||
func (m *ManagerImpl) Start() error {
|
||||
if !m.lifecycle.BeginInitialization() {
|
||||
return ErrAlreadyRunning
|
||||
}
|
||||
m.triggerConnectionFlow()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop method stops the sse client and it's status monitoring goroutine
|
||||
func (m *ManagerImpl) Stop() error {
|
||||
if !m.lifecycle.BeginShutdown() {
|
||||
return ErrNotRunning
|
||||
}
|
||||
m.statusTracker.NotifySSEShutdownExpected()
|
||||
m.withRefreshTokenLock(func() {
|
||||
if m.nextRefresh != nil {
|
||||
m.nextRefresh.Stop()
|
||||
}
|
||||
})
|
||||
m.StopWorkers()
|
||||
m.sseClient.StopStreaming()
|
||||
m.lifecycle.AwaitShutdownComplete()
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartWorkers start the splits & segments workers
|
||||
func (m *ManagerImpl) StartWorkers() {
|
||||
m.processor.StartWorkers()
|
||||
}
|
||||
|
||||
// StopWorkers stops the splits & segments workers
|
||||
func (m *ManagerImpl) StopWorkers() {
|
||||
m.processor.StopWorkers()
|
||||
}
|
||||
|
||||
func (m *ManagerImpl) performAuthentication() (*dtos.Token, *int64) {
|
||||
token, err := m.authAPI.Authenticate()
|
||||
if err != nil {
|
||||
if errType, ok := err.(dtos.HTTPError); ok {
|
||||
if errType.Code >= http.StatusInternalServerError {
|
||||
m.logger.Error(fmt.Sprintf("Error authenticating: %s", err.Error()))
|
||||
return nil, common.Int64Ref(StatusRetryableError)
|
||||
}
|
||||
return nil, common.Int64Ref(StatusNonRetryableError) // 400, 401, etc
|
||||
}
|
||||
// Not an HTTP eerror, most likely a tcp/bad connection. Should retry
|
||||
return nil, common.Int64Ref(StatusRetryableError)
|
||||
}
|
||||
if !token.PushEnabled {
|
||||
return nil, common.Int64Ref(StatusNonRetryableError)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (m *ManagerImpl) eventHandler(e sse.IncomingMessage) {
|
||||
newStatus, err := m.parser.ParseAndForward(e)
|
||||
if newStatus != nil {
|
||||
m.feedback <- *newStatus
|
||||
} else if err != nil {
|
||||
m.logger.Error("error parsing message: ", err)
|
||||
m.logger.Debug("failed message: ", e)
|
||||
m.feedback <- StatusRetryableError
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ManagerImpl) triggerConnectionFlow() {
|
||||
token, status := m.performAuthentication()
|
||||
if status != nil {
|
||||
m.lifecycle.AbnormalShutdown()
|
||||
defer m.lifecycle.ShutdownComplete()
|
||||
m.feedback <- *status
|
||||
return
|
||||
}
|
||||
|
||||
tokenList, err := token.ChannelList()
|
||||
if err != nil {
|
||||
m.logger.Error("error parsing channel list: ", err)
|
||||
m.lifecycle.AbnormalShutdown()
|
||||
defer m.lifecycle.ShutdownComplete()
|
||||
m.feedback <- StatusRetryableError
|
||||
return
|
||||
}
|
||||
|
||||
m.statusTracker.Reset()
|
||||
sseStatus := make(chan int, 100)
|
||||
m.sseClient.ConnectStreaming(token.Token, sseStatus, tokenList, m.eventHandler)
|
||||
go func() {
|
||||
defer m.lifecycle.ShutdownComplete()
|
||||
if !m.lifecycle.InitializationComplete() {
|
||||
return
|
||||
}
|
||||
for {
|
||||
message := <-sseStatus
|
||||
switch message {
|
||||
case sse.StatusFirstEventOk:
|
||||
when, err := token.CalculateNextTokenExpiration()
|
||||
if err != nil || when <= 0 {
|
||||
m.logger.Warning("Failed to calculate next token expiration time. Defaulting to 50 minutes")
|
||||
when = 50 * time.Minute
|
||||
}
|
||||
m.withRefreshTokenLock(func() {
|
||||
m.nextRefresh = time.AfterFunc(when, func() {
|
||||
m.logger.Info("Refreshing SSE auth token.")
|
||||
m.Stop()
|
||||
m.Start()
|
||||
})
|
||||
})
|
||||
m.feedback <- StatusUp
|
||||
case sse.StatusConnectionFailed:
|
||||
m.lifecycle.AbnormalShutdown()
|
||||
m.logger.Error("SSE Connection failed")
|
||||
m.feedback <- StatusRetryableError
|
||||
return
|
||||
case sse.StatusDisconnected:
|
||||
m.logger.Debug("propagating sse disconnection event")
|
||||
status := m.statusTracker.HandleDisconnection()
|
||||
if status != nil { // connection ended unexpectedly
|
||||
m.lifecycle.AbnormalShutdown()
|
||||
m.feedback <- *status
|
||||
}
|
||||
return
|
||||
case sse.StatusUnderlyingClientInUse:
|
||||
m.lifecycle.AbnormalShutdown()
|
||||
m.logger.Error("unexpected error in streaming. Switching to polling")
|
||||
m.feedback <- StatusNonRetryableError
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *ManagerImpl) withRefreshTokenLock(f func()) {
|
||||
m.refreshTokenMutex.Lock()
|
||||
defer m.refreshTokenMutex.Unlock()
|
||||
f()
|
||||
}
|
||||
399
vendor/github.com/splitio/go-split-commons/v3/push/parser.go
сгенерированный
поставляемый
Обычный файл
399
vendor/github.com/splitio/go-split-commons/v3/push/parser.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,399 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/service/api/sse"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// SSE event type constants
|
||||
const (
|
||||
SSEEventTypeSync = "sync"
|
||||
SSEEventTypeMessage = "message"
|
||||
SSEEventTypeError = "error"
|
||||
)
|
||||
|
||||
// Message type constants
|
||||
const (
|
||||
MessageTypeUpdate = iota
|
||||
MessageTypeControl
|
||||
MessageTypeOccupancy
|
||||
)
|
||||
|
||||
// Update type constants
|
||||
const (
|
||||
UpdateTypeSplitChange = "SPLIT_UPDATE"
|
||||
UpdateTypeSplitKill = "SPLIT_KILL"
|
||||
UpdateTypeSegmentChange = "SEGMENT_UPDATE"
|
||||
UpdateTypeContol = "CONTROL"
|
||||
)
|
||||
|
||||
// Control type constants
|
||||
const (
|
||||
ControlTypeStreamingEnabled = "STREAMING_ENABLED"
|
||||
ControlTypeStreamingPaused = "STREAMING_PAUSED"
|
||||
ControlTypeStreamingDisabled = "STREAMING_DISABLED"
|
||||
)
|
||||
|
||||
const (
|
||||
occupancuName = "[meta]occupancy"
|
||||
occupancyPrefix = "[?occupancy=metrics.publishers]"
|
||||
)
|
||||
|
||||
// ErrEmptyEvent indicates an event without message and event fields
|
||||
var ErrEmptyEvent = errors.New("empty incoming event")
|
||||
|
||||
// NotificationParser interface
|
||||
type NotificationParser interface {
|
||||
ParseAndForward(sse.IncomingMessage) (*int64, error)
|
||||
}
|
||||
|
||||
// NotificationParserImpl implementas the NotificationParser interface
|
||||
type NotificationParserImpl struct {
|
||||
logger logging.LoggerInterface
|
||||
onSplitUpdate func(*SplitChangeUpdate) error
|
||||
onSplitKill func(*SplitKillUpdate) error
|
||||
onSegmentUpdate func(*SegmentChangeUpdate) error
|
||||
onControlUpdate func(*ControlUpdate) *int64
|
||||
onOccupancyMesage func(*OccupancyMessage) *int64
|
||||
onAblyError func(*AblyError) *int64
|
||||
}
|
||||
|
||||
// ParseAndForward accepts an incoming RAW event and returns a properly parsed & typed event
|
||||
func (p *NotificationParserImpl) ParseAndForward(raw sse.IncomingMessage) (*int64, error) {
|
||||
|
||||
if raw.Event() == "" {
|
||||
if raw.ID() == "" {
|
||||
return nil, ErrEmptyEvent
|
||||
}
|
||||
// If it has ID its a sync event, which we're not using not. Ignore.
|
||||
p.logger.Debug("Ignoring sync event")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data := genericData{}
|
||||
err := json.Unmarshal([]byte(raw.Data()), &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing JSON: %w", err)
|
||||
}
|
||||
|
||||
switch raw.Event() {
|
||||
case SSEEventTypeError:
|
||||
return p.parseError(&data)
|
||||
case SSEEventTypeMessage:
|
||||
return p.parseMessage(&data)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
func (p *NotificationParserImpl) parseError(data *genericData) (*int64, error) {
|
||||
return p.onAblyError(&AblyError{
|
||||
code: data.Code,
|
||||
statusCode: data.StatusCode,
|
||||
message: data.Message,
|
||||
href: data.Href,
|
||||
timestamp: data.Timestamp,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *NotificationParserImpl) parseMessage(data *genericData) (*int64, error) {
|
||||
var nested genericMessageData
|
||||
err := json.Unmarshal([]byte(data.Data), &nested)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing message nested json data: %w", err)
|
||||
}
|
||||
|
||||
if data.Name == occupancuName {
|
||||
return p.onOccupancyMesage(&OccupancyMessage{
|
||||
BaseMessage: BaseMessage{
|
||||
timestamp: data.Timestamp,
|
||||
channel: data.Channel,
|
||||
},
|
||||
publishers: nested.Metrics.Publishers,
|
||||
}), nil
|
||||
}
|
||||
|
||||
return p.parseUpdate(data, &nested)
|
||||
}
|
||||
|
||||
func (p *NotificationParserImpl) parseUpdate(data *genericData, nested *genericMessageData) (*int64, error) {
|
||||
if data == nil || nested == nil {
|
||||
return nil, errors.New("parseUpdate: data cannot be nil")
|
||||
}
|
||||
|
||||
base := BaseUpdate{
|
||||
BaseMessage: BaseMessage{timestamp: data.Timestamp, channel: data.Channel},
|
||||
changeNumber: nested.ChangeNumber,
|
||||
}
|
||||
|
||||
switch nested.Type {
|
||||
case UpdateTypeSplitChange:
|
||||
return nil, p.onSplitUpdate(&SplitChangeUpdate{BaseUpdate: base})
|
||||
case UpdateTypeSplitKill:
|
||||
return nil, p.onSplitKill(&SplitKillUpdate{BaseUpdate: base, splitName: nested.SplitName, defaultTreatment: nested.DefaultTreatment})
|
||||
case UpdateTypeSegmentChange:
|
||||
return nil, p.onSegmentUpdate(&SegmentChangeUpdate{BaseUpdate: base, segmentName: nested.SegmentName})
|
||||
case UpdateTypeContol:
|
||||
return p.onControlUpdate(&ControlUpdate{BaseMessage: base.BaseMessage, controlType: nested.ControlType}), nil
|
||||
default:
|
||||
// TODO: log full event in debug mode
|
||||
return nil, fmt.Errorf("invalid update type: %s", nested.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Event basic interface
|
||||
type Event interface {
|
||||
fmt.Stringer
|
||||
EventType() string
|
||||
Timestamp() int64
|
||||
}
|
||||
|
||||
// SSESyncEvent represents an SSE Sync event with only id (used for resuming connections)
|
||||
type SSESyncEvent struct {
|
||||
id string
|
||||
timestamp int64
|
||||
}
|
||||
|
||||
// EventType always returns SSEEventTypeSync for SSESyncEvents
|
||||
func (e *SSESyncEvent) EventType() string { return SSEEventTypeSync }
|
||||
|
||||
// Timestamp returns the timestamp of the event parsing
|
||||
func (e *SSESyncEvent) Timestamp() int64 { return e.timestamp }
|
||||
|
||||
// String returns the string represenation of the event
|
||||
func (e *SSESyncEvent) String() string {
|
||||
return fmt.Sprintf("SSESync(id=%s,timestamp=%d)", e.id, e.timestamp)
|
||||
}
|
||||
|
||||
// AblyError struct
|
||||
type AblyError struct {
|
||||
code int
|
||||
statusCode int
|
||||
message string
|
||||
href string
|
||||
timestamp int64
|
||||
}
|
||||
|
||||
// EventType always returns SSEEventTypeError for AblyError
|
||||
func (a *AblyError) EventType() string { return SSEEventTypeError }
|
||||
|
||||
// Code returns the error code
|
||||
func (a *AblyError) Code() int { return a.code }
|
||||
|
||||
// StatusCode returns the status code
|
||||
func (a *AblyError) StatusCode() int { return a.statusCode }
|
||||
|
||||
// Message returns the error message
|
||||
func (a *AblyError) Message() string { return a.message }
|
||||
|
||||
// Href returns the documentation link
|
||||
func (a *AblyError) Href() string { return a.href }
|
||||
|
||||
// Timestamp returns the error timestamp
|
||||
func (a *AblyError) Timestamp() int64 { return a.timestamp }
|
||||
|
||||
// IsRetryable returns whether the error is recoverable via a push subsystem restart
|
||||
func (a *AblyError) IsRetryable() bool { return a.code >= 40140 && a.code <= 40149 }
|
||||
|
||||
// String returns the string representation of the ably error
|
||||
func (a *AblyError) String() string {
|
||||
return fmt.Sprintf("AblyError(code=%d,statusCode=%d,message=%s,timestamp=%d,isRetryable=%t)",
|
||||
a.code, a.statusCode, a.message, a.timestamp, a.IsRetryable())
|
||||
}
|
||||
|
||||
// Message basic interface
|
||||
type Message interface {
|
||||
Event
|
||||
MessageType() int64
|
||||
Channel() string
|
||||
}
|
||||
|
||||
// BaseMessage contains the basic message-specific fields and methods
|
||||
type BaseMessage struct {
|
||||
timestamp int64
|
||||
channel string
|
||||
}
|
||||
|
||||
// EventType always returns SSEEventTypeMessage for BaseMessage and embedding types
|
||||
func (m *BaseMessage) EventType() string { return SSEEventTypeMessage }
|
||||
|
||||
// Timestamp returns the timestamp of the message reception
|
||||
func (m *BaseMessage) Timestamp() int64 { return m.timestamp }
|
||||
|
||||
// Channel returns which channel the message was received in
|
||||
func (m *BaseMessage) Channel() string { return m.channel }
|
||||
|
||||
// OccupancyMessage contains fields & methods related to ocupancy messages
|
||||
type OccupancyMessage struct {
|
||||
BaseMessage
|
||||
publishers int64
|
||||
}
|
||||
|
||||
// MessageType always returns MessageTypeOccupancy for Occupancy messages
|
||||
func (o *OccupancyMessage) MessageType() int64 { return MessageTypeOccupancy }
|
||||
|
||||
// ChannelWithoutPrefix returns the original channel namem without the metadata prefix
|
||||
func (o *OccupancyMessage) ChannelWithoutPrefix() string {
|
||||
return strings.Replace(o.Channel(), occupancyPrefix, "", 1)
|
||||
}
|
||||
|
||||
// Publishers returbs the amount of publishers in the current channel
|
||||
func (o *OccupancyMessage) Publishers() int64 {
|
||||
return o.publishers
|
||||
}
|
||||
|
||||
// Strings returns the string representation of an occupancy message
|
||||
func (o *OccupancyMessage) String() string {
|
||||
return fmt.Sprintf("Occupancy(channel=%s,publishers=%d,timestamp=%d)",
|
||||
o.Channel(), o.publishers, o.Timestamp())
|
||||
}
|
||||
|
||||
// Update basic interface
|
||||
type Update interface {
|
||||
Message
|
||||
UpdateType() string
|
||||
ChangeNumber() int64
|
||||
}
|
||||
|
||||
// BaseUpdate contains fields & methods related to update-based messages
|
||||
type BaseUpdate struct {
|
||||
BaseMessage
|
||||
changeNumber int64
|
||||
}
|
||||
|
||||
// MessageType alwats returns MessageType for Update messages
|
||||
func (b *BaseUpdate) MessageType() int64 { return MessageTypeUpdate }
|
||||
|
||||
// ChangeNumber returns the changeNumber of the update
|
||||
func (b *BaseUpdate) ChangeNumber() int64 { return b.changeNumber }
|
||||
|
||||
// SplitChangeUpdate represents a SplitChange notification generated in the split servers
|
||||
type SplitChangeUpdate struct {
|
||||
BaseUpdate
|
||||
}
|
||||
|
||||
// UpdateType always returns UpdateTypeSplitChange for SplitKillUpdate messages
|
||||
func (u *SplitChangeUpdate) UpdateType() string { return UpdateTypeSplitChange }
|
||||
|
||||
// String returns the String representation of a split change notification
|
||||
func (u *SplitChangeUpdate) String() string {
|
||||
return fmt.Sprintf("SplitChange(channel=%s,changeNumber=%d,timestamp=%d)",
|
||||
u.Channel(), u.ChangeNumber(), u.Timestamp())
|
||||
}
|
||||
|
||||
// SplitKillUpdate represents a SplitKill notification generated in the split servers
|
||||
type SplitKillUpdate struct {
|
||||
BaseUpdate
|
||||
splitName string
|
||||
defaultTreatment string
|
||||
}
|
||||
|
||||
// UpdateType always returns UpdateTypeSplitKill for SplitKillUpdate messages
|
||||
func (u *SplitKillUpdate) UpdateType() string { return UpdateTypeSplitKill }
|
||||
|
||||
// SplitName returns the name of the killed split
|
||||
func (u *SplitKillUpdate) SplitName() string { return u.splitName }
|
||||
|
||||
// DefaultTreatment returns the last default treatment seen in the split servers for this split
|
||||
func (u *SplitKillUpdate) DefaultTreatment() string { return u.defaultTreatment }
|
||||
|
||||
// ToSplitChangeUpdate Maps this kill notification to a split change one
|
||||
func (u *SplitKillUpdate) ToSplitChangeUpdate() *SplitChangeUpdate {
|
||||
return &SplitChangeUpdate{BaseUpdate: u.BaseUpdate}
|
||||
}
|
||||
|
||||
// String returns the string representation of this update
|
||||
func (u *SplitKillUpdate) String() string {
|
||||
return fmt.Sprintf("SplitKill(channel=%s,changeNumber=%d,splitName=%s,defaultTreatment=%s,timestamp=%d)",
|
||||
u.Channel(), u.ChangeNumber(), u.SplitName(), u.DefaultTreatment(), u.Timestamp())
|
||||
}
|
||||
|
||||
// SegmentChangeUpdate represents a segment change notification generated in the split servers.
|
||||
type SegmentChangeUpdate struct {
|
||||
BaseUpdate
|
||||
segmentName string
|
||||
}
|
||||
|
||||
// UpdateType is always UpdateTypeSegmentChange for Segmet Updates
|
||||
func (u *SegmentChangeUpdate) UpdateType() string { return UpdateTypeSegmentChange }
|
||||
|
||||
// SegmentName returns the name of the updated segment
|
||||
func (u *SegmentChangeUpdate) SegmentName() string { return u.segmentName }
|
||||
|
||||
// String returns the string representation of a segment update notification
|
||||
func (u *SegmentChangeUpdate) String() string {
|
||||
return fmt.Sprintf("SegmentChange(channel=%s,changeNumber=%d,segmentName=%s,timestamp=%d",
|
||||
u.Channel(), u.ChangeNumber(), u.segmentName, u.Timestamp())
|
||||
}
|
||||
|
||||
// ControlUpdate represents a control notification generated by the split push subsystem
|
||||
type ControlUpdate struct {
|
||||
BaseMessage
|
||||
controlType string
|
||||
}
|
||||
|
||||
// MessageType always returns MessageTypeControl for Control messages
|
||||
func (u *ControlUpdate) MessageType() int64 { return MessageTypeControl }
|
||||
|
||||
// ControlType returns the type of control notification received
|
||||
func (u *ControlUpdate) ControlType() string { return u.controlType }
|
||||
|
||||
// String returns a string representation of this notification
|
||||
func (u *ControlUpdate) String() string {
|
||||
return fmt.Sprintf("Control(channel=%s,type=%s,timestamp=%d)",
|
||||
u.Channel(), u.controlType, u.Timestamp())
|
||||
}
|
||||
|
||||
type genericData struct {
|
||||
|
||||
// Error associated data
|
||||
Code int `json:"code"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
Message string `json:"message"`
|
||||
Href string `json:"href"`
|
||||
|
||||
ClientID string `json:"clientId"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Encoding string `json:"encoding"`
|
||||
Channel string `json:"channel"`
|
||||
Data string `json:"data"`
|
||||
|
||||
//"id":"tO4rXGE4CX:0:0","timestamp":1612897630627,"encoding":"json","channel":"[?occupancy=metrics.publishers]control_sec","data":"{\"metrics\":{\"publishers\":0}}","name":"[meta]occupancy"}
|
||||
|
||||
}
|
||||
|
||||
type metrics struct {
|
||||
Publishers int64 `json:"publishers"`
|
||||
}
|
||||
|
||||
type genericMessageData struct {
|
||||
Metrics metrics `json:"metrics"`
|
||||
Type string `json:"type"`
|
||||
ChangeNumber int64 `json:"changeNumber"`
|
||||
SplitName string `json:"splitName"`
|
||||
DefaultTreatment string `json:"defaultTreatment"`
|
||||
SegmentName string `json:"segmentName"`
|
||||
ControlType string `json:"controlType"`
|
||||
|
||||
// {\"type\":\"SPLIT_UPDATE\",\"changeNumber\":1612909342671}"}
|
||||
}
|
||||
|
||||
// Compile-type assertions of interface requirements
|
||||
var _ Event = &AblyError{}
|
||||
var _ Message = &OccupancyMessage{}
|
||||
var _ Message = &SplitChangeUpdate{}
|
||||
var _ Message = &SplitKillUpdate{}
|
||||
var _ Message = &SegmentChangeUpdate{}
|
||||
var _ Message = &ControlUpdate{}
|
||||
var _ Update = &SplitChangeUpdate{}
|
||||
var _ Update = &SplitKillUpdate{}
|
||||
var _ Update = &SegmentChangeUpdate{}
|
||||
107
vendor/github.com/splitio/go-split-commons/v3/push/processor.go
сгенерированный
поставляемый
Обычный файл
107
vendor/github.com/splitio/go-split-commons/v3/push/processor.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,107 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
splitQueueMinSize = 5000
|
||||
segmentQueueMinSize = 5000
|
||||
)
|
||||
|
||||
// Processor provides the interface for an update-message processor
|
||||
type Processor interface {
|
||||
ProcessSplitChangeUpdate(update *SplitChangeUpdate) error
|
||||
ProcessSplitKillUpdate(update *SplitKillUpdate) error
|
||||
ProcessSegmentChangeUpdate(update *SegmentChangeUpdate) error
|
||||
StartWorkers()
|
||||
StopWorkers()
|
||||
}
|
||||
|
||||
// ProcessorImpl struct for notification processor
|
||||
type ProcessorImpl struct {
|
||||
segmentQueue chan SegmentChangeUpdate
|
||||
splitQueue chan SplitChangeUpdate
|
||||
splitWorker *SplitUpdateWorker
|
||||
segmentWorker *SegmentUpdateWorker
|
||||
synchronizer synchronizerInterface
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// NewProcessor creates new processor
|
||||
func NewProcessor(
|
||||
splitQueueSize int64,
|
||||
segmentQueueSize int64,
|
||||
synchronizer synchronizerInterface,
|
||||
logger logging.LoggerInterface,
|
||||
) (*ProcessorImpl, error) {
|
||||
if segmentQueueSize < segmentQueueMinSize {
|
||||
return nil, errors.New("Small size of segmentQueue")
|
||||
}
|
||||
if splitQueueSize < splitQueueMinSize {
|
||||
return nil, errors.New("Small size of splitQueue")
|
||||
}
|
||||
|
||||
splitQueue := make(chan SplitChangeUpdate, splitQueueSize)
|
||||
splitWorker, err := NewSplitUpdateWorker(splitQueue, synchronizer, logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error instantiating split worker: %w", err)
|
||||
}
|
||||
|
||||
segmentQueue := make(chan SegmentChangeUpdate, segmentQueueSize)
|
||||
segmentWorker, err := NewSegmentUpdateWorker(segmentQueue, synchronizer, logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error instantiating split worker: %w", err)
|
||||
}
|
||||
|
||||
return &ProcessorImpl{
|
||||
splitQueue: splitQueue,
|
||||
splitWorker: splitWorker,
|
||||
segmentQueue: segmentQueue,
|
||||
segmentWorker: segmentWorker,
|
||||
synchronizer: synchronizer,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ProcessSplitChangeUpdate accepts a split change notifications and schedules a fetch
|
||||
func (p *ProcessorImpl) ProcessSplitChangeUpdate(update *SplitChangeUpdate) error {
|
||||
if update == nil {
|
||||
return errors.New("split change update cannot be nil")
|
||||
}
|
||||
p.splitQueue <- *update
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessSplitKillUpdate accepts a split kill notification, issues a local kill and schedules a fetch
|
||||
func (p *ProcessorImpl) ProcessSplitKillUpdate(update *SplitKillUpdate) error {
|
||||
if update == nil {
|
||||
return errors.New("split change update cannot be nil")
|
||||
}
|
||||
p.synchronizer.LocalKill(update.SplitName(), update.DefaultTreatment(), update.ChangeNumber())
|
||||
return p.ProcessSplitChangeUpdate(update.ToSplitChangeUpdate())
|
||||
}
|
||||
|
||||
// ProcessSegmentChangeUpdate accepts a segment change notification and schedules a fetch
|
||||
func (p *ProcessorImpl) ProcessSegmentChangeUpdate(update *SegmentChangeUpdate) error {
|
||||
if update == nil {
|
||||
return errors.New("split change update cannot be nil")
|
||||
}
|
||||
p.segmentQueue <- *update
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartWorkers enables split & segments workers
|
||||
func (p *ProcessorImpl) StartWorkers() {
|
||||
p.splitWorker.Start()
|
||||
p.segmentWorker.Start()
|
||||
}
|
||||
|
||||
// StopWorkers pauses split & segments workers
|
||||
func (p *ProcessorImpl) StopWorkers() {
|
||||
p.splitWorker.Stop()
|
||||
p.segmentWorker.Stop()
|
||||
}
|
||||
82
vendor/github.com/splitio/go-split-commons/v3/push/segment.go
сгенерированный
поставляемый
Обычный файл
82
vendor/github.com/splitio/go-split-commons/v3/push/segment.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,82 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/common"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
)
|
||||
|
||||
// SegmentUpdateWorker struct
|
||||
type SegmentUpdateWorker struct {
|
||||
segmentQueue chan SegmentChangeUpdate
|
||||
sync synchronizerInterface
|
||||
logger logging.LoggerInterface
|
||||
lifecycle lifecycle.Manager
|
||||
}
|
||||
|
||||
// NewSegmentUpdateWorker creates SegmentUpdateWorker
|
||||
func NewSegmentUpdateWorker(
|
||||
segmentQueue chan SegmentChangeUpdate,
|
||||
synchronizer synchronizerInterface,
|
||||
logger logging.LoggerInterface,
|
||||
) (*SegmentUpdateWorker, error) {
|
||||
if cap(segmentQueue) < 5000 {
|
||||
return nil, errors.New("")
|
||||
}
|
||||
running := atomic.Value{}
|
||||
running.Store(false)
|
||||
|
||||
worker := &SegmentUpdateWorker{
|
||||
segmentQueue: segmentQueue,
|
||||
sync: synchronizer,
|
||||
logger: logger,
|
||||
}
|
||||
worker.lifecycle.Setup()
|
||||
return worker, nil
|
||||
}
|
||||
|
||||
// Start starts worker
|
||||
func (s *SegmentUpdateWorker) Start() {
|
||||
if !s.lifecycle.BeginInitialization() {
|
||||
s.logger.Info("Segment worker is already running")
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if !s.lifecycle.InitializationComplete() {
|
||||
return
|
||||
}
|
||||
defer s.lifecycle.ShutdownComplete()
|
||||
for {
|
||||
select {
|
||||
case segmentUpdate := <-s.segmentQueue:
|
||||
s.logger.Debug("Received Segment update and proceding to perform fetch")
|
||||
s.logger.Debug(fmt.Sprintf("SegmentName: %s\nChangeNumber: %d", segmentUpdate.SegmentName(), segmentUpdate.ChangeNumber()))
|
||||
err := s.sync.SynchronizeSegment(segmentUpdate.SegmentName(), common.Int64Ref(segmentUpdate.ChangeNumber()), true)
|
||||
if err != nil {
|
||||
s.logger.Error(err)
|
||||
}
|
||||
case <-s.lifecycle.ShutdownRequested():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops worker
|
||||
func (s *SegmentUpdateWorker) Stop() {
|
||||
if !s.lifecycle.BeginShutdown() {
|
||||
s.logger.Debug("Split worker not runnning. Ignoring.")
|
||||
return
|
||||
}
|
||||
s.lifecycle.AwaitShutdownComplete()
|
||||
}
|
||||
|
||||
// IsRunning indicates if worker is running or not
|
||||
func (s *SegmentUpdateWorker) IsRunning() bool {
|
||||
return s.lifecycle.IsRunning()
|
||||
}
|
||||
80
vendor/github.com/splitio/go-split-commons/v3/push/split.go
сгенерированный
поставляемый
Обычный файл
80
vendor/github.com/splitio/go-split-commons/v3/push/split.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,80 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/common"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
)
|
||||
|
||||
// SplitUpdateWorker struct
|
||||
type SplitUpdateWorker struct {
|
||||
splitQueue chan SplitChangeUpdate
|
||||
sync synchronizerInterface
|
||||
logger logging.LoggerInterface
|
||||
lifecycle lifecycle.Manager
|
||||
}
|
||||
|
||||
// NewSplitUpdateWorker creates SplitUpdateWorker
|
||||
func NewSplitUpdateWorker(
|
||||
splitQueue chan SplitChangeUpdate,
|
||||
synchronizer synchronizerInterface,
|
||||
logger logging.LoggerInterface,
|
||||
) (*SplitUpdateWorker, error) {
|
||||
if cap(splitQueue) < 5000 {
|
||||
return nil, errors.New("")
|
||||
}
|
||||
|
||||
worker := &SplitUpdateWorker{
|
||||
splitQueue: splitQueue,
|
||||
sync: synchronizer,
|
||||
logger: logger,
|
||||
}
|
||||
worker.lifecycle.Setup()
|
||||
return worker, nil
|
||||
}
|
||||
|
||||
// Start starts worker
|
||||
func (s *SplitUpdateWorker) Start() {
|
||||
if !s.lifecycle.BeginInitialization() {
|
||||
s.logger.Info("Split worker is already running")
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Debug("Started SplitUpdateWorker")
|
||||
go func() {
|
||||
defer s.lifecycle.ShutdownComplete()
|
||||
if !s.lifecycle.InitializationComplete() {
|
||||
return
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case splitUpdate := <-s.splitQueue:
|
||||
s.logger.Debug("Received Split update and proceding to perform fetch")
|
||||
s.logger.Debug(fmt.Sprintf("ChangeNumber: %d", splitUpdate.ChangeNumber()))
|
||||
err := s.sync.SynchronizeSplits(common.Int64Ref(splitUpdate.ChangeNumber()), true)
|
||||
if err != nil {
|
||||
s.logger.Error(err)
|
||||
}
|
||||
case <-s.lifecycle.ShutdownRequested():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops worker
|
||||
func (s *SplitUpdateWorker) Stop() {
|
||||
if !s.lifecycle.BeginShutdown() {
|
||||
s.logger.Debug("Split worker not runnning. Ignoring.")
|
||||
return
|
||||
}
|
||||
s.lifecycle.AwaitShutdownComplete()
|
||||
}
|
||||
|
||||
// IsRunning indicates if worker is running or not
|
||||
func (s *SplitUpdateWorker) IsRunning() bool {
|
||||
return s.lifecycle.IsRunning()
|
||||
}
|
||||
158
vendor/github.com/splitio/go-split-commons/v3/push/statustracker.go
сгенерированный
поставляемый
Обычный файл
158
vendor/github.com/splitio/go-split-commons/v3/push/statustracker.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,158 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/common"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// StatusTracker keeps track of the status of the push subsystem and generates appropriate status change notifications.
|
||||
type StatusTracker interface {
|
||||
HandleOccupancy(*OccupancyMessage) *int64
|
||||
HandleControl(*ControlUpdate) *int64
|
||||
HandleAblyError(*AblyError) *int64
|
||||
HandleDisconnection() *int64
|
||||
NotifySSEShutdownExpected()
|
||||
Reset()
|
||||
}
|
||||
|
||||
// StatusTrackerImpl is a concrete implementation of the StatusTracker interface
|
||||
type StatusTrackerImpl struct {
|
||||
logger logging.LoggerInterface
|
||||
mutex sync.Mutex
|
||||
occupancy map[string]int64
|
||||
lastControlTimestamp int64
|
||||
lastOccupancyTimestamp int64
|
||||
lastControlMessage string
|
||||
lastStatusPropagated int64
|
||||
shutdownExpected bool
|
||||
}
|
||||
|
||||
// NotifySSEShutdownExpected should be called when we are forcefully closing the SSE client
|
||||
func (p *StatusTrackerImpl) NotifySSEShutdownExpected() {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
p.shutdownExpected = true
|
||||
}
|
||||
|
||||
// Reset should be called on initialization and when the a new connection is being established (to start from scratch)
|
||||
func (p *StatusTrackerImpl) Reset() {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
p.occupancy = map[string]int64{"control_pri": 2, "control_sec": 2}
|
||||
p.lastControlMessage = ControlTypeStreamingEnabled
|
||||
p.lastStatusPropagated = StatusUp
|
||||
p.shutdownExpected = false
|
||||
}
|
||||
|
||||
// HandleOccupancy should be called for every occupancy notification received
|
||||
func (p *StatusTrackerImpl) HandleOccupancy(message *OccupancyMessage) (newStatus *int64) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
if p.shutdownExpected {
|
||||
return nil // we don't care about occupancy if we're disconnecting
|
||||
}
|
||||
|
||||
channel := message.ChannelWithoutPrefix()
|
||||
if _, ok := p.occupancy[channel]; !ok {
|
||||
p.logger.Warning(fmt.Sprintf("received occupancy on non-registered channel '%s'. Ignoring", channel))
|
||||
return nil
|
||||
}
|
||||
|
||||
p.lastOccupancyTimestamp = message.Timestamp()
|
||||
p.occupancy[channel] = message.Publishers()
|
||||
return p.updateStatus()
|
||||
}
|
||||
|
||||
// HandleAblyError should be called whenever an ably error is received
|
||||
func (p *StatusTrackerImpl) HandleAblyError(errorEvent *AblyError) (newStatus *int64) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
if p.shutdownExpected {
|
||||
return nil // we don't care about occupancy if we're disconnecting
|
||||
}
|
||||
|
||||
// Regardless of whether the error is retryable or not, we're going to close the connection
|
||||
p.shutdownExpected = true
|
||||
|
||||
if errorEvent.IsRetryable() {
|
||||
p.logger.Info("Received retryable error message. Restarting SSE connection with backoff")
|
||||
return p.propagateStatus(StatusRetryableError)
|
||||
}
|
||||
|
||||
p.logger.Info("Received non-retryable error message. Disabling streaming")
|
||||
return p.propagateStatus(StatusNonRetryableError)
|
||||
}
|
||||
|
||||
// HandleControl should be called whenever a control notification is received
|
||||
func (p *StatusTrackerImpl) HandleControl(controlUpdate *ControlUpdate) *int64 {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
if p.shutdownExpected {
|
||||
return nil // we don't care about occupancy if we're disconnecting
|
||||
}
|
||||
|
||||
if p.lastControlTimestamp > controlUpdate.timestamp {
|
||||
p.logger.Warning("Received an old control update. Ignoring")
|
||||
return nil
|
||||
}
|
||||
|
||||
p.lastControlMessage = controlUpdate.controlType
|
||||
p.lastControlTimestamp = controlUpdate.timestamp
|
||||
return p.updateStatus()
|
||||
}
|
||||
|
||||
// HandleDisconnection should be called whenver the SSE client gets disconnected
|
||||
func (p *StatusTrackerImpl) HandleDisconnection() *int64 {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
if !p.shutdownExpected {
|
||||
return p.propagateStatus(StatusRetryableError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStatusTracker returns a new StatusTracker
|
||||
func NewStatusTracker(logger logging.LoggerInterface) *StatusTrackerImpl {
|
||||
tracker := &StatusTrackerImpl{logger: logger}
|
||||
tracker.Reset()
|
||||
return tracker
|
||||
}
|
||||
|
||||
func (p *StatusTrackerImpl) occupancyOk() bool {
|
||||
for _, v := range p.occupancy {
|
||||
if v > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *StatusTrackerImpl) updateStatus() *int64 {
|
||||
if p.lastStatusPropagated == StatusUp {
|
||||
if !p.occupancyOk() || p.lastControlMessage == ControlTypeStreamingPaused {
|
||||
return p.propagateStatus(StatusDown)
|
||||
}
|
||||
if p.lastControlMessage == ControlTypeStreamingDisabled {
|
||||
return p.propagateStatus(StatusNonRetryableError)
|
||||
}
|
||||
}
|
||||
if p.lastStatusPropagated == StatusDown {
|
||||
if p.occupancyOk() && p.lastControlMessage == ControlTypeStreamingEnabled {
|
||||
return p.propagateStatus(StatusUp)
|
||||
}
|
||||
if p.lastControlMessage == ControlTypeStreamingDisabled {
|
||||
return p.propagateStatus(StatusNonRetryableError)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *StatusTrackerImpl) propagateStatus(newStatus int64) *int64 {
|
||||
p.lastStatusPropagated = newStatus
|
||||
return common.Int64Ref(newStatus)
|
||||
}
|
||||
|
||||
var _ StatusTracker = &StatusTrackerImpl{}
|
||||
39
vendor/github.com/splitio/go-split-commons/v3/service/api/auth.go
сгенерированный
поставляемый
Обычный файл
39
vendor/github.com/splitio/go-split-commons/v3/service/api/auth.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,39 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// AuthAPIClient struct is responsible for authenticating client for push services
|
||||
type AuthAPIClient struct {
|
||||
client Client
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// NewAuthAPIClient instantiates and return an AuthAPIClient
|
||||
func NewAuthAPIClient(apikey string, cfg conf.AdvancedConfig, logger logging.LoggerInterface, metadata dtos.Metadata) *AuthAPIClient {
|
||||
return &AuthAPIClient{
|
||||
client: NewHTTPClient(apikey, cfg, cfg.AuthServiceURL, logger, metadata),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate performs authentication for push services
|
||||
func (a *AuthAPIClient) Authenticate() (*dtos.Token, error) {
|
||||
raw, err := a.client.Get("/api/auth", map[string]string{CacheControlHeader: CacheControlNoCache})
|
||||
if err != nil {
|
||||
a.logger.Error("Error while authenticating for streaming", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := dtos.Token{}
|
||||
err = json.Unmarshal(raw, &token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
169
vendor/github.com/splitio/go-split-commons/v3/service/api/client.go
сгенерированный
поставляемый
Обычный файл
169
vendor/github.com/splitio/go-split-commons/v3/service/api/client.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,169 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// Cache control header constants
|
||||
const (
|
||||
CacheControlHeader = "Cache-Control"
|
||||
CacheControlNoCache = "no-cache"
|
||||
)
|
||||
|
||||
// Client interface for HTTPClient
|
||||
type Client interface {
|
||||
Get(service string, headers map[string]string) ([]byte, error)
|
||||
Post(service string, body []byte, headers map[string]string) error
|
||||
}
|
||||
|
||||
// HTTPClient structure to wrap up the net/http.Client
|
||||
type HTTPClient struct {
|
||||
url string
|
||||
httpClient *http.Client
|
||||
headers map[string]string
|
||||
logger logging.LoggerInterface
|
||||
apikey string
|
||||
metadata dtos.Metadata
|
||||
}
|
||||
|
||||
// NewHTTPClient instance of HttpClient
|
||||
func NewHTTPClient(
|
||||
apikey string,
|
||||
cfg conf.AdvancedConfig,
|
||||
endpoint string,
|
||||
logger logging.LoggerInterface,
|
||||
metadata dtos.Metadata,
|
||||
) Client {
|
||||
var timeout int
|
||||
timeout = cfg.HTTPTimeout
|
||||
client := &http.Client{Timeout: time.Duration(timeout) * time.Second}
|
||||
return &HTTPClient{
|
||||
url: endpoint,
|
||||
httpClient: client,
|
||||
logger: logger,
|
||||
apikey: apikey,
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Get method is a get call to an url
|
||||
func (c *HTTPClient) Get(service string, headers map[string]string) ([]byte, error) {
|
||||
serviceURL := c.url + service
|
||||
c.logger.Debug("[GET] ", serviceURL)
|
||||
req, _ := http.NewRequest("GET", serviceURL, nil)
|
||||
|
||||
authorization := c.apikey
|
||||
c.logger.Debug("Authorization [ApiKey]: ", logging.ObfuscateAPIKey(authorization))
|
||||
req.Header.Add("Accept-Encoding", "gzip")
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("SplitSDKVersion", c.metadata.SDKVersion)
|
||||
req.Header.Add("SplitSDKMachineName", c.metadata.MachineName)
|
||||
req.Header.Add("SplitSDKMachineIP", c.metadata.MachineIP)
|
||||
|
||||
for headerName, headerValue := range headers {
|
||||
req.Header.Add(headerName, headerValue)
|
||||
}
|
||||
|
||||
c.logger.Debug(fmt.Sprintf("Headers: %v", req.Header))
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+authorization)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
c.logger.Error("Error requesting data to API: ", req.URL.String(), err.Error())
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check that the server actually sent compressed data
|
||||
var reader io.ReadCloser
|
||||
switch resp.Header.Get("Content-Encoding") {
|
||||
case "gzip":
|
||||
reader, err = gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing gzip resopnse body: %w", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
default:
|
||||
reader = resp.Body
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
c.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.logger.Verbose("[RESPONSE_BODY]", string(body), "[END_RESPONSE_BODY]")
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
c.logger.Error(fmt.Sprintf("GET method: Status Code: %d - %s", resp.StatusCode, resp.Status))
|
||||
return nil, &dtos.HTTPError{
|
||||
Code: resp.StatusCode,
|
||||
Message: resp.Status,
|
||||
}
|
||||
}
|
||||
|
||||
// Post performs a HTTP POST request
|
||||
func (c *HTTPClient) Post(service string, body []byte, headers map[string]string) error {
|
||||
|
||||
serviceURL := c.url + service
|
||||
c.logger.Debug("[POST] ", serviceURL)
|
||||
req, _ := http.NewRequest("POST", serviceURL, bytes.NewBuffer(body))
|
||||
//****************
|
||||
req.Close = true // To prevent EOF error when connection is closed
|
||||
//****************
|
||||
authorization := c.apikey
|
||||
c.logger.Debug("Authorization [ApiKey]: ", logging.ObfuscateAPIKey(authorization))
|
||||
|
||||
req.Header.Add("Accept-Encoding", "gzip")
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
for headerName, headerValue := range headers {
|
||||
req.Header.Add(headerName, headerValue)
|
||||
}
|
||||
|
||||
c.logger.Debug(fmt.Sprintf("Headers: %v", req.Header))
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+authorization)
|
||||
|
||||
c.logger.Verbose("[REQUEST_BODY]", string(body), "[END_REQUEST_BODY]")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
c.logger.Error("Error posting data to API: ", req.URL.String(), err.Error())
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Verbose("[RESPONSE_BODY]", string(respBody), "[END_RESPONSE_BODY]")
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.logger.Error(fmt.Sprintf("POST method: Status Code: %d - %s", resp.StatusCode, resp.Status))
|
||||
return &dtos.HTTPError{
|
||||
Code: resp.StatusCode,
|
||||
Message: resp.Status,
|
||||
}
|
||||
}
|
||||
115
vendor/github.com/splitio/go-split-commons/v3/service/api/http_fetchers.go
сгенерированный
поставляемый
Обычный файл
115
vendor/github.com/splitio/go-split-commons/v3/service/api/http_fetchers.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,115 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
type httpFetcherBase struct {
|
||||
client Client
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
func (h *httpFetcherBase) fetchRaw(url string, since int64, requestNoCache bool) ([]byte, error) {
|
||||
var bufferQuery bytes.Buffer
|
||||
bufferQuery.WriteString(url)
|
||||
|
||||
if since >= -1 {
|
||||
bufferQuery.WriteString("?since=")
|
||||
bufferQuery.WriteString(strconv.FormatInt(since, 10))
|
||||
}
|
||||
|
||||
var extraHeaders map[string]string
|
||||
if requestNoCache {
|
||||
extraHeaders = map[string]string{CacheControlHeader: CacheControlNoCache}
|
||||
}
|
||||
data, err := h.client.Get(bufferQuery.String(), extraHeaders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// HTTPSplitFetcher struct is responsible for fetching splits from the backend via HTTP protocol
|
||||
type HTTPSplitFetcher struct {
|
||||
httpFetcherBase
|
||||
}
|
||||
|
||||
// NewHTTPSplitFetcher instantiates and return an HTTPSplitFetcher
|
||||
func NewHTTPSplitFetcher(
|
||||
apikey string,
|
||||
cfg conf.AdvancedConfig,
|
||||
logger logging.LoggerInterface,
|
||||
metadata dtos.Metadata,
|
||||
) *HTTPSplitFetcher {
|
||||
return &HTTPSplitFetcher{
|
||||
httpFetcherBase: httpFetcherBase{
|
||||
client: NewHTTPClient(apikey, cfg, cfg.SdkURL, logger, metadata),
|
||||
logger: logger,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch makes an http call to the split backend and returns the list of updated splits
|
||||
func (f *HTTPSplitFetcher) Fetch(since int64, requestNoCache bool) (*dtos.SplitChangesDTO, error) {
|
||||
data, err := f.fetchRaw("/splitChanges", since, requestNoCache)
|
||||
if err != nil {
|
||||
f.logger.Error("Error fetching split changes ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var splitChangesDto dtos.SplitChangesDTO
|
||||
err = json.Unmarshal(data, &splitChangesDto)
|
||||
if err != nil {
|
||||
f.logger.Error("Error parsing split changes JSON ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &splitChangesDto, nil
|
||||
}
|
||||
|
||||
// HTTPSegmentFetcher struct is responsible for fetching segment by name from the API via HTTP method
|
||||
type HTTPSegmentFetcher struct {
|
||||
httpFetcherBase
|
||||
}
|
||||
|
||||
// NewHTTPSegmentFetcher instantiates and returns a new HTTPSegmentFetcher.
|
||||
func NewHTTPSegmentFetcher(
|
||||
apikey string,
|
||||
cfg conf.AdvancedConfig,
|
||||
logger logging.LoggerInterface,
|
||||
metadata dtos.Metadata,
|
||||
) *HTTPSegmentFetcher {
|
||||
return &HTTPSegmentFetcher{
|
||||
httpFetcherBase: httpFetcherBase{
|
||||
client: NewHTTPClient(apikey, cfg, cfg.SdkURL, logger, metadata),
|
||||
logger: logger,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch issues a GET request to the split backend and returns the contents of a particular segment
|
||||
func (f *HTTPSegmentFetcher) Fetch(segmentName string, since int64, requestNoCache bool) (*dtos.SegmentChangesDTO, error) {
|
||||
var bufferQuery bytes.Buffer
|
||||
bufferQuery.WriteString("/segmentChanges/")
|
||||
bufferQuery.WriteString(segmentName)
|
||||
|
||||
data, err := f.fetchRaw(bufferQuery.String(), since, requestNoCache)
|
||||
if err != nil {
|
||||
f.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
var segmentChangesDto dtos.SegmentChangesDTO
|
||||
err = json.Unmarshal(data, &segmentChangesDto)
|
||||
if err != nil {
|
||||
f.logger.Error("Error parsing segment changes JSON for segment ", segmentName, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &segmentChangesDto, nil
|
||||
}
|
||||
197
vendor/github.com/splitio/go-split-commons/v3/service/api/http_recorders.go
сгенерированный
поставляемый
Обычный файл
197
vendor/github.com/splitio/go-split-commons/v3/service/api/http_recorders.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,197 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
type httpRecorderBase struct {
|
||||
client Client
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// RecordRaw records raw data
|
||||
func (h *httpRecorderBase) RecordRaw(url string, data []byte, metadata dtos.Metadata, extraHeaders map[string]string) error {
|
||||
headers := make(map[string]string)
|
||||
headers["SplitSDKVersion"] = metadata.SDKVersion
|
||||
if metadata.MachineName != "NA" && metadata.MachineName != "unknown" {
|
||||
headers["SplitSDKMachineName"] = metadata.MachineName
|
||||
}
|
||||
if metadata.MachineIP != "NA" && metadata.MachineIP != "unknown" {
|
||||
headers["SplitSDKMachineIP"] = metadata.MachineIP
|
||||
}
|
||||
if extraHeaders != nil {
|
||||
for header, value := range extraHeaders {
|
||||
headers[header] = value
|
||||
}
|
||||
}
|
||||
return h.client.Post(url, data, headers)
|
||||
}
|
||||
|
||||
// HTTPImpressionRecorder is a struct responsible for submitting impression bulks to the backend
|
||||
type HTTPImpressionRecorder struct {
|
||||
httpRecorderBase
|
||||
}
|
||||
|
||||
// Record sends an array (or slice) of impressionsRecord to the backend
|
||||
func (i *HTTPImpressionRecorder) Record(impressions []dtos.ImpressionsDTO, metadata dtos.Metadata, extraHeaders map[string]string) error {
|
||||
data, err := json.Marshal(impressions)
|
||||
if err != nil {
|
||||
i.logger.Error("Error marshaling JSON", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = i.RecordRaw("/testImpressions/bulk", data, metadata, extraHeaders)
|
||||
if err != nil {
|
||||
i.logger.Error("Error posting impressions", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordImpressionsCount sens impressionsCount
|
||||
func (i *HTTPImpressionRecorder) RecordImpressionsCount(pf dtos.ImpressionsCountDTO, metadata dtos.Metadata) error {
|
||||
if len(pf.PerFeature) == 0 {
|
||||
return nil
|
||||
}
|
||||
data, err := json.Marshal(pf)
|
||||
if err != nil {
|
||||
i.logger.Error("Error marshaling JSON", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = i.RecordRaw("/testImpressions/count", data, metadata, nil)
|
||||
if err != nil {
|
||||
i.logger.Error("Error posting impressionsCount", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewHTTPImpressionRecorder instantiates an HTTPImpressionRecorder
|
||||
func NewHTTPImpressionRecorder(
|
||||
apikey string,
|
||||
cfg conf.AdvancedConfig,
|
||||
logger logging.LoggerInterface,
|
||||
) *HTTPImpressionRecorder {
|
||||
client := NewHTTPClient(apikey, cfg, cfg.EventsURL, logger, dtos.Metadata{})
|
||||
return &HTTPImpressionRecorder{
|
||||
httpRecorderBase: httpRecorderBase{
|
||||
client: client,
|
||||
logger: logger,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPMetricsRecorder is a struct responsible for submitting metrics (latency, gauge, counters) to the backend
|
||||
type HTTPMetricsRecorder struct {
|
||||
httpRecorderBase
|
||||
}
|
||||
|
||||
// RecordCounters method submits counter metrics to the backend
|
||||
func (m *HTTPMetricsRecorder) RecordCounters(counters []dtos.CounterDTO, metadata dtos.Metadata) error {
|
||||
data, err := json.Marshal(counters)
|
||||
if err != nil {
|
||||
m.logger.Error("Error marshaling JSON", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.RecordRaw("/metrics/counters", data, metadata, nil)
|
||||
if err != nil {
|
||||
m.logger.Error("Error posting counters", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordLatencies method submits latency metrics to the backend
|
||||
func (m *HTTPMetricsRecorder) RecordLatencies(latencies []dtos.LatenciesDTO, metadata dtos.Metadata) error {
|
||||
data, err := json.Marshal(latencies)
|
||||
if err != nil {
|
||||
m.logger.Error("Error marshaling JSON", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.RecordRaw("/metrics/times", data, metadata, nil)
|
||||
if err != nil {
|
||||
m.logger.Error("Error posting latencies", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordGauge method submits gauge metrics to the backend
|
||||
func (m *HTTPMetricsRecorder) RecordGauge(gauge dtos.GaugeDTO, metadata dtos.Metadata) error {
|
||||
data, err := json.Marshal(gauge)
|
||||
if err != nil {
|
||||
m.logger.Error("Error marshaling JSON", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.RecordRaw("/metrics/gauge", data, metadata, nil)
|
||||
if err != nil {
|
||||
m.logger.Error("Error posting gauges", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewHTTPMetricsRecorder instantiates an HTTPMetricsRecorder
|
||||
func NewHTTPMetricsRecorder(
|
||||
apikey string,
|
||||
cfg conf.AdvancedConfig,
|
||||
logger logging.LoggerInterface,
|
||||
) *HTTPMetricsRecorder {
|
||||
client := NewHTTPClient(apikey, cfg, cfg.EventsURL, logger, dtos.Metadata{})
|
||||
return &HTTPMetricsRecorder{
|
||||
httpRecorderBase: httpRecorderBase{
|
||||
client: client,
|
||||
logger: logger,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPEventsRecorder is a struct responsible for submitting events bulks to the backend
|
||||
type HTTPEventsRecorder struct {
|
||||
httpRecorderBase
|
||||
}
|
||||
|
||||
// Record sends an array (or slice) of dtos.EventDTO to the backend
|
||||
func (i *HTTPEventsRecorder) Record(events []dtos.EventDTO, metadata dtos.Metadata) error {
|
||||
data, err := json.Marshal(events)
|
||||
if err != nil {
|
||||
i.logger.Error("Error marshaling JSON", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = i.RecordRaw("/events/bulk", data, metadata, nil)
|
||||
if err != nil {
|
||||
i.logger.Error("Error posting events", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewHTTPEventsRecorder instantiates an HTTPEventsRecorder
|
||||
func NewHTTPEventsRecorder(
|
||||
apikey string,
|
||||
cfg conf.AdvancedConfig,
|
||||
logger logging.LoggerInterface,
|
||||
) *HTTPEventsRecorder {
|
||||
client := NewHTTPClient(apikey, cfg, cfg.EventsURL, logger, dtos.Metadata{})
|
||||
return &HTTPEventsRecorder{
|
||||
httpRecorderBase: httpRecorderBase{
|
||||
client: client,
|
||||
logger: logger,
|
||||
},
|
||||
}
|
||||
}
|
||||
123
vendor/github.com/splitio/go-split-commons/v3/service/api/sse/client.go
сгенерированный
поставляемый
Обычный файл
123
vendor/github.com/splitio/go-split-commons/v3/service/api/sse/client.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,123 @@
|
||||
package sse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/sse"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
gtSync "github.com/splitio/go-toolkit/v4/sync"
|
||||
)
|
||||
|
||||
const (
|
||||
version = "1.1"
|
||||
keepAlive = 70
|
||||
)
|
||||
|
||||
// StreamingClient interface
|
||||
type StreamingClient interface {
|
||||
ConnectStreaming(token string, streamingStatus chan int, channelList []string, handleIncomingMessage func(IncomingMessage))
|
||||
StopStreaming()
|
||||
IsRunning() bool
|
||||
}
|
||||
|
||||
// StreamingClientImpl struct
|
||||
type StreamingClientImpl struct {
|
||||
sseClient *sse.Client
|
||||
logger logging.LoggerInterface
|
||||
lifecycle lifecycle.Manager
|
||||
}
|
||||
|
||||
// Status constants
|
||||
const (
|
||||
StatusConnectionFailed = iota
|
||||
StatusUnderlyingClientInUse
|
||||
StatusFirstEventOk
|
||||
StatusDisconnected
|
||||
)
|
||||
|
||||
// IncomingMessage is an alias of sse.RawEvent
|
||||
type IncomingMessage = sse.RawEvent
|
||||
|
||||
// NewStreamingClient creates new SSE Client
|
||||
func NewStreamingClient(cfg *conf.AdvancedConfig, logger logging.LoggerInterface) *StreamingClientImpl {
|
||||
sseClient, _ := sse.NewClient(cfg.StreamingServiceURL, keepAlive, logger)
|
||||
|
||||
client := &StreamingClientImpl{
|
||||
sseClient: sseClient,
|
||||
logger: logger,
|
||||
}
|
||||
client.lifecycle.Setup()
|
||||
return client
|
||||
}
|
||||
|
||||
// ConnectStreaming connects to streaming
|
||||
func (s *StreamingClientImpl) ConnectStreaming(token string, streamingStatus chan int, channelList []string, handleIncomingMessage func(IncomingMessage)) {
|
||||
|
||||
if !s.lifecycle.BeginInitialization() {
|
||||
s.logger.Info("Connection is already in process/running. Ignoring")
|
||||
return
|
||||
}
|
||||
|
||||
params := make(map[string]string)
|
||||
params["channels"] = strings.Join(append(channelList), ",")
|
||||
params["accessToken"] = token
|
||||
params["v"] = version
|
||||
|
||||
go func() {
|
||||
defer s.lifecycle.ShutdownComplete()
|
||||
if !s.lifecycle.InitializationComplete() {
|
||||
return
|
||||
}
|
||||
firstEventReceived := gtSync.NewAtomicBool(false)
|
||||
out := s.sseClient.Do(params, func(m IncomingMessage) {
|
||||
if firstEventReceived.TestAndSet() && !m.IsError() {
|
||||
streamingStatus <- StatusFirstEventOk
|
||||
}
|
||||
handleIncomingMessage(m)
|
||||
})
|
||||
|
||||
if out == nil { // all good
|
||||
streamingStatus <- StatusDisconnected
|
||||
return
|
||||
}
|
||||
|
||||
// Something didn'g go as expected
|
||||
s.lifecycle.AbnormalShutdown()
|
||||
|
||||
asConnectionFailedError := &sse.ErrConnectionFailed{}
|
||||
if errors.As(out, &asConnectionFailedError) {
|
||||
streamingStatus <- StatusConnectionFailed
|
||||
return
|
||||
}
|
||||
|
||||
switch out {
|
||||
case sse.ErrNotIdle:
|
||||
// If this happens we have a bug
|
||||
streamingStatus <- StatusUnderlyingClientInUse
|
||||
case sse.ErrReadingStream:
|
||||
streamingStatus <- StatusDisconnected
|
||||
case sse.ErrTimeout:
|
||||
streamingStatus <- StatusDisconnected
|
||||
default:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StopStreaming stops streaming
|
||||
func (s *StreamingClientImpl) StopStreaming() {
|
||||
if !s.lifecycle.BeginShutdown() {
|
||||
s.logger.Info("SSE client wrapper not running. Ignoring")
|
||||
return
|
||||
}
|
||||
s.sseClient.Shutdown(true)
|
||||
s.lifecycle.AwaitShutdownComplete()
|
||||
s.logger.Info("Stopped streaming")
|
||||
}
|
||||
|
||||
// IsRunning returns true if the client is running
|
||||
func (s *StreamingClientImpl) IsRunning() bool {
|
||||
return s.lifecycle.IsRunning()
|
||||
}
|
||||
38
vendor/github.com/splitio/go-split-commons/v3/service/interfaces.go
сгенерированный
поставляемый
Обычный файл
38
vendor/github.com/splitio/go-split-commons/v3/service/interfaces.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,38 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
)
|
||||
|
||||
// AuthClient inteface to be implemneted by AuthClient
|
||||
type AuthClient interface {
|
||||
Authenticate() (*dtos.Token, error)
|
||||
}
|
||||
|
||||
// SplitFetcher interface to be implemented by Split Fetchers
|
||||
type SplitFetcher interface {
|
||||
Fetch(changeNumber int64, requstNoCache bool) (*dtos.SplitChangesDTO, error)
|
||||
}
|
||||
|
||||
// SegmentFetcher interface to be implemented by Split Fetchers
|
||||
type SegmentFetcher interface {
|
||||
Fetch(name string, changeNumber int64, requestNoCace bool) (*dtos.SegmentChangesDTO, error)
|
||||
}
|
||||
|
||||
// ImpressionsRecorder interface to be implemented by Impressions loggers
|
||||
type ImpressionsRecorder interface {
|
||||
Record(impressions []dtos.ImpressionsDTO, metadata dtos.Metadata, extraHeaders map[string]string) error
|
||||
RecordImpressionsCount(pf dtos.ImpressionsCountDTO, metadata dtos.Metadata) error
|
||||
}
|
||||
|
||||
// MetricsRecorder interface to be implemented by Metrics loggers
|
||||
type MetricsRecorder interface {
|
||||
RecordLatencies(latencies []dtos.LatenciesDTO, metadata dtos.Metadata) error
|
||||
RecordCounters(counters []dtos.CounterDTO, metadata dtos.Metadata) error
|
||||
RecordGauge(gauge dtos.GaugeDTO, metadata dtos.Metadata) error
|
||||
}
|
||||
|
||||
// EventsRecorder interface to post events
|
||||
type EventsRecorder interface {
|
||||
Record(events []dtos.EventDTO, metadata dtos.Metadata) error
|
||||
}
|
||||
261
vendor/github.com/splitio/go-split-commons/v3/service/local/splitFetcher.go
сгенерированный
поставляемый
Обычный файл
261
vendor/github.com/splitio/go-split-commons/v3/service/local/splitFetcher.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,261 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// SplitFileFormatClassic represents the file format of the standard split definition file <feature treatment>
|
||||
SplitFileFormatClassic = iota
|
||||
// SplitFileFormatJSON represents the file format of a JSON representation of split dtos
|
||||
SplitFileFormatJSON
|
||||
// SplitFileFormatYAML represents the file format of a YAML representation of split dtos
|
||||
SplitFileFormatYAML
|
||||
)
|
||||
|
||||
// FileSplitFetcher struct fetches splits from a file
|
||||
type FileSplitFetcher struct {
|
||||
splitFile string
|
||||
fileFormat int
|
||||
lastChangeNumber int64
|
||||
}
|
||||
|
||||
// NewFileSplitFetcher returns a new instance of LocalFileSplitFetcher
|
||||
func NewFileSplitFetcher(splitFile string, logger logging.LoggerInterface) *FileSplitFetcher {
|
||||
var r = regexp.MustCompile("(?i)(.yml$|.yaml$)")
|
||||
if r.MatchString(splitFile) {
|
||||
return &FileSplitFetcher{
|
||||
splitFile: splitFile,
|
||||
fileFormat: SplitFileFormatYAML,
|
||||
}
|
||||
}
|
||||
logger.Warning("Localhost mode: .split mocks will be deprecated soon in favor of YAML files, which provide more targeting power. Take a look in our documentation.")
|
||||
return &FileSplitFetcher{
|
||||
splitFile: splitFile,
|
||||
fileFormat: SplitFileFormatClassic,
|
||||
}
|
||||
}
|
||||
|
||||
func parseSplitsClassic(data string) []dtos.SplitDTO {
|
||||
splits := make([]dtos.SplitDTO, 0)
|
||||
lines := strings.Split(data, "\n")
|
||||
for _, line := range lines {
|
||||
words := strings.Fields(line)
|
||||
if len(words) < 2 || len(words[0]) < 1 || words[0][0] == '#' {
|
||||
// Skip the line if it has less than two words, the words are empty strings or
|
||||
// it begins with '#' character
|
||||
continue
|
||||
}
|
||||
splitName := words[0]
|
||||
treatment := words[1]
|
||||
splits = append(splits, createSplit(
|
||||
splitName,
|
||||
treatment,
|
||||
createRolloutCondition(treatment),
|
||||
make(map[string]string),
|
||||
))
|
||||
}
|
||||
return splits
|
||||
}
|
||||
|
||||
func createSplit(splitName string, treatment string, condition dtos.ConditionDTO, configurations map[string]string) dtos.SplitDTO {
|
||||
split := dtos.SplitDTO{
|
||||
Name: splitName,
|
||||
TrafficAllocation: 100,
|
||||
Conditions: []dtos.ConditionDTO{condition},
|
||||
Status: "ACTIVE",
|
||||
DefaultTreatment: "control",
|
||||
Configurations: configurations,
|
||||
}
|
||||
return split
|
||||
}
|
||||
|
||||
func createWhitelistedCondition(treatment string, keys interface{}) dtos.ConditionDTO {
|
||||
var whitelist []string
|
||||
switch keys := keys.(type) {
|
||||
case string:
|
||||
whitelist = []string{keys}
|
||||
case []string:
|
||||
whitelist = keys
|
||||
case []interface{}:
|
||||
whitelist = make([]string, 0)
|
||||
for _, key := range keys {
|
||||
k, ok := key.(string)
|
||||
if ok {
|
||||
whitelist = append(whitelist, k)
|
||||
}
|
||||
}
|
||||
default:
|
||||
whitelist = make([]string, 0)
|
||||
}
|
||||
return dtos.ConditionDTO{
|
||||
ConditionType: "WHITELIST",
|
||||
Label: "LOCAL_",
|
||||
MatcherGroup: dtos.MatcherGroupDTO{
|
||||
Combiner: "AND",
|
||||
Matchers: []dtos.MatcherDTO{
|
||||
{
|
||||
MatcherType: "WHITELIST",
|
||||
Negate: false,
|
||||
Whitelist: &dtos.WhitelistMatcherDataDTO{
|
||||
Whitelist: whitelist,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Partitions: []dtos.PartitionDTO{
|
||||
{
|
||||
Size: 100,
|
||||
Treatment: treatment,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createRolloutCondition(treatment string) dtos.ConditionDTO {
|
||||
return dtos.ConditionDTO{
|
||||
ConditionType: "ROLLOUT",
|
||||
Label: "LOCAL_ROLLOUT",
|
||||
MatcherGroup: dtos.MatcherGroupDTO{
|
||||
Combiner: "AND",
|
||||
Matchers: []dtos.MatcherDTO{
|
||||
{
|
||||
MatcherType: "ALL_KEYS",
|
||||
Negate: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Partitions: []dtos.PartitionDTO{
|
||||
{
|
||||
Size: 100,
|
||||
Treatment: treatment,
|
||||
},
|
||||
{
|
||||
Size: 0,
|
||||
Treatment: "_",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createCondition(keys interface{}, treatment string) dtos.ConditionDTO {
|
||||
if keys != nil {
|
||||
return createWhitelistedCondition(treatment, keys)
|
||||
}
|
||||
return createRolloutCondition(treatment)
|
||||
}
|
||||
|
||||
func parseSplitsYAML(data string) (d []dtos.SplitDTO) {
|
||||
// Set up a guard deferred function to recover if some error occurs during parsing
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// At this point we'll only trust that the logger isn't panicking trust
|
||||
// that the logger isn't panicking
|
||||
log.Fatalf("Localhost Parsing: %v", string(debug.Stack()))
|
||||
d = make([]dtos.SplitDTO, 0)
|
||||
}
|
||||
}()
|
||||
|
||||
splits := make([]dtos.SplitDTO, 0)
|
||||
|
||||
var splitsFromYAML []map[string]map[string]interface{}
|
||||
err := yaml.Unmarshal([]byte(data), &splitsFromYAML)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
return splits
|
||||
}
|
||||
|
||||
splitsToParse := make(map[string]dtos.SplitDTO, 0)
|
||||
|
||||
for _, splitMap := range splitsFromYAML {
|
||||
for splitName, splitParsed := range splitMap {
|
||||
split, ok := splitsToParse[splitName]
|
||||
treatment, isString := splitParsed["treatment"].(string)
|
||||
if !isString {
|
||||
break
|
||||
}
|
||||
config, isValidConfig := splitParsed["config"].(string)
|
||||
if !ok {
|
||||
configurations := make(map[string]string)
|
||||
if isValidConfig {
|
||||
configurations[treatment] = config
|
||||
}
|
||||
splitsToParse[splitName] = createSplit(
|
||||
splitName,
|
||||
treatment,
|
||||
createCondition(splitParsed["keys"], treatment),
|
||||
configurations,
|
||||
)
|
||||
} else {
|
||||
newCondition := createCondition(splitParsed["keys"], treatment)
|
||||
if newCondition.ConditionType == "ROLLOUT" {
|
||||
split.Conditions = append(split.Conditions, newCondition)
|
||||
} else {
|
||||
split.Conditions = append([]dtos.ConditionDTO{newCondition}, split.Conditions...)
|
||||
}
|
||||
configurations := split.Configurations
|
||||
if isValidConfig {
|
||||
configurations[treatment] = config
|
||||
}
|
||||
split.Configurations = configurations
|
||||
splitsToParse[splitName] = split
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for _, split := range splitsToParse {
|
||||
splits = append(splits, split)
|
||||
}
|
||||
|
||||
return splits
|
||||
}
|
||||
|
||||
// Fetch parses the file and returns the appropriate structures
|
||||
func (s *FileSplitFetcher) Fetch(changeNumber int64, _ bool) (*dtos.SplitChangesDTO, error) {
|
||||
fileContents, err := ioutil.ReadFile(s.splitFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var splits []dtos.SplitDTO
|
||||
var till int64
|
||||
since := s.lastChangeNumber
|
||||
if s.lastChangeNumber != 0 {
|
||||
//The first time we should return since == till
|
||||
till = since + 1
|
||||
}
|
||||
|
||||
data := string(fileContents)
|
||||
switch s.fileFormat {
|
||||
case SplitFileFormatClassic:
|
||||
splits = parseSplitsClassic(data)
|
||||
case SplitFileFormatYAML:
|
||||
splits = parseSplitsYAML(data)
|
||||
case SplitFileFormatJSON:
|
||||
return nil, fmt.Errorf("JSON is not yet supported")
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported file format")
|
||||
|
||||
}
|
||||
|
||||
s.lastChangeNumber++
|
||||
return &dtos.SplitChangesDTO{
|
||||
Splits: splits,
|
||||
Since: since,
|
||||
Till: till,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ service.SplitFetcher = &FileSplitFetcher{}
|
||||
35
vendor/github.com/splitio/go-split-commons/v3/service/split.go
сгенерированный
поставляемый
Обычный файл
35
vendor/github.com/splitio/go-split-commons/v3/service/split.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,35 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service/api"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// SplitAPI struct for fetchers and recorders
|
||||
type SplitAPI struct {
|
||||
AuthClient AuthClient
|
||||
SplitFetcher SplitFetcher
|
||||
SegmentFetcher SegmentFetcher
|
||||
ImpressionRecorder ImpressionsRecorder
|
||||
EventRecorder EventsRecorder
|
||||
MetricRecorder MetricsRecorder
|
||||
}
|
||||
|
||||
// NewSplitAPI creates new splitAPI
|
||||
func NewSplitAPI(
|
||||
apikey string,
|
||||
conf conf.AdvancedConfig,
|
||||
logger logging.LoggerInterface,
|
||||
metadata dtos.Metadata,
|
||||
) *SplitAPI {
|
||||
return &SplitAPI{
|
||||
AuthClient: api.NewAuthAPIClient(apikey, conf, logger, metadata),
|
||||
SplitFetcher: api.NewHTTPSplitFetcher(apikey, conf, logger, metadata),
|
||||
SegmentFetcher: api.NewHTTPSegmentFetcher(apikey, conf, logger, metadata),
|
||||
ImpressionRecorder: api.NewHTTPImpressionRecorder(apikey, conf, logger),
|
||||
EventRecorder: api.NewHTTPEventsRecorder(apikey, conf, logger),
|
||||
MetricRecorder: api.NewHTTPMetricsRecorder(apikey, conf, logger),
|
||||
}
|
||||
}
|
||||
118
vendor/github.com/splitio/go-split-commons/v3/storage/interfaces.go
сгенерированный
поставляемый
Обычный файл
118
vendor/github.com/splitio/go-split-commons/v3/storage/interfaces.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,118 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
)
|
||||
|
||||
// SplitStorageProducer should be implemented by structs that offer writing splits in storage
|
||||
type SplitStorageProducer interface {
|
||||
KillLocally(splitName string, defaultTreatment string, changeNumber int64)
|
||||
PutMany(splits []dtos.SplitDTO, changeNumber int64)
|
||||
Remove(splitName string)
|
||||
SetChangeNumber(changeNumber int64) error
|
||||
}
|
||||
|
||||
// SplitStorageConsumer should be implemented by structs that offer reading splits from storage
|
||||
type SplitStorageConsumer interface {
|
||||
All() []dtos.SplitDTO
|
||||
ChangeNumber() (int64, error)
|
||||
FetchMany(splitNames []string) map[string]*dtos.SplitDTO
|
||||
SegmentNames() *set.ThreadUnsafeSet // Not in Spec
|
||||
Split(splitName string) *dtos.SplitDTO
|
||||
SplitNames() []string
|
||||
TrafficTypeExists(trafficType string) bool
|
||||
}
|
||||
|
||||
// SegmentStorageProducer interface should be implemented by all structs that offer writing segments
|
||||
type SegmentStorageProducer interface {
|
||||
Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, changeNumber int64) error
|
||||
SetChangeNumber(segmentName string, till int64) error
|
||||
}
|
||||
|
||||
// SegmentStorageConsumer interface should be implemented by all structs that ofer reading segments
|
||||
type SegmentStorageConsumer interface {
|
||||
ChangeNumber(segmentName string) (int64, error)
|
||||
CountRemovedKeys(segmentName string) int64
|
||||
Keys(segmentName string) *set.ThreadUnsafeSet
|
||||
SegmentContainsKey(segmentName string, key string) (bool, error)
|
||||
}
|
||||
|
||||
// ImpressionStorageProducer interface should be impemented by structs that accept incoming impressions
|
||||
type ImpressionStorageProducer interface {
|
||||
LogImpressions(impressions []dtos.Impression) error
|
||||
}
|
||||
|
||||
// ImpressionStorageConsumer interface should be implemented by structs that offer popping impressions
|
||||
type ImpressionStorageConsumer interface {
|
||||
Count() int64
|
||||
Drop(size *int64) error
|
||||
Empty() bool
|
||||
PopN(n int64) ([]dtos.Impression, error)
|
||||
PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error)
|
||||
}
|
||||
|
||||
// MetricsStorageProducer interface should be impemented by structs that accept incoming metrics
|
||||
type MetricsStorageProducer interface {
|
||||
PutGauge(key string, gauge float64)
|
||||
IncLatency(metricName string, index int)
|
||||
IncCounter(key string)
|
||||
}
|
||||
|
||||
// MetricsStorageConsumer interface should be implemented by structs that offer popping metrics
|
||||
type MetricsStorageConsumer interface {
|
||||
PeekCounters() map[string]int64
|
||||
PeekLatencies() map[string][]int64
|
||||
PopGauges() []dtos.GaugeDTO
|
||||
PopLatencies() []dtos.LatenciesDTO
|
||||
PopCounters() []dtos.CounterDTO
|
||||
PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error)
|
||||
PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error)
|
||||
PopCountersWithMetadata() (*dtos.CounterDataBulk, error)
|
||||
}
|
||||
|
||||
// EventStorageProducer interface should be implemented by structs that accept incoming events
|
||||
type EventStorageProducer interface {
|
||||
Push(event dtos.EventDTO, size int) error
|
||||
}
|
||||
|
||||
// EventStorageConsumer interface should be implemented by structs that offer popping impressions
|
||||
type EventStorageConsumer interface {
|
||||
Count() int64
|
||||
Drop(size *int64) error
|
||||
Empty() bool
|
||||
PopN(n int64) ([]dtos.EventDTO, error)
|
||||
PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error)
|
||||
}
|
||||
|
||||
// --- Wide Interfaces
|
||||
|
||||
// SplitStorage wraps consumer & producer interfaces
|
||||
type SplitStorage interface {
|
||||
SplitStorageProducer
|
||||
SplitStorageConsumer
|
||||
}
|
||||
|
||||
// SegmentStorage wraps consumer and producer interfaces
|
||||
type SegmentStorage interface {
|
||||
SegmentStorageProducer
|
||||
SegmentStorageConsumer
|
||||
}
|
||||
|
||||
// ImpressionStorage wraps consumer & producer interfaces
|
||||
type ImpressionStorage interface {
|
||||
ImpressionStorageConsumer
|
||||
ImpressionStorageProducer
|
||||
}
|
||||
|
||||
// MetricsStorage wraps consumer and producer interfaces
|
||||
type MetricsStorage interface {
|
||||
MetricsStorageConsumer
|
||||
MetricsStorageProducer
|
||||
}
|
||||
|
||||
// EventsStorage wraps consumer and producer interfaces
|
||||
type EventsStorage interface {
|
||||
EventStorageConsumer
|
||||
EventStorageProducer
|
||||
}
|
||||
125
vendor/github.com/splitio/go-split-commons/v3/storage/metricWrapper.go
сгенерированный
поставляемый
Обычный файл
125
vendor/github.com/splitio/go-split-commons/v3/storage/metricWrapper.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,125 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// MetricWrapper struct
|
||||
type MetricWrapper struct {
|
||||
Telemetry MetricsStorage
|
||||
LocalTelemetry MetricsStorage
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
const (
|
||||
// SplitChangesCounter counters
|
||||
SplitChangesCounter = iota
|
||||
// SplitChangesLatency latencies
|
||||
SplitChangesLatency
|
||||
// SegmentChangesCounter counters
|
||||
SegmentChangesCounter
|
||||
// SegmentChangesLatency latencies
|
||||
SegmentChangesLatency
|
||||
// TestImpressionsCounter counter
|
||||
TestImpressionsCounter
|
||||
// TestImpressionsLatency latencies
|
||||
TestImpressionsLatency
|
||||
// PostEventsCounter counter
|
||||
PostEventsCounter
|
||||
//PostEventsLatency latencies
|
||||
PostEventsLatency
|
||||
// MySegmentsCounter counters
|
||||
MySegmentsCounter
|
||||
// MySegmentsLatency latencies
|
||||
MySegmentsLatency
|
||||
)
|
||||
|
||||
const (
|
||||
counter = "backend::request.{status}"
|
||||
|
||||
splitChangesCounter = "splitChangeFetcher.status.{status}"
|
||||
splitChangesLatency = "splitChangeFetcher.time"
|
||||
localSplitChangesLatency = "backend::/api/splitChanges"
|
||||
|
||||
segmentChangesCounter = "segmentChangeFetcher.status.{status}"
|
||||
segmentChangesLatency = "segmentChangeFetcher.time"
|
||||
localSegmentChangesLatency = "backend::/api/segmentChanges"
|
||||
|
||||
testImpressionsCounter = "testImpressions.status.{status}"
|
||||
testImpressionsLatency = "testImpressions.time"
|
||||
localTestImpressionsLatency = "backend::/api/testImpressions/bulk"
|
||||
|
||||
postEventsCounter = "events.status.{status}"
|
||||
postEventsLatency = "events.time"
|
||||
localPostEventsLatency = "backend::/api/events/bulk"
|
||||
|
||||
mySegmentsCounter = "mySegments.status.{status}"
|
||||
mySegmentsLatency = "mySegments.time"
|
||||
localMySegmentsLatency = "backend::/api/mySegments"
|
||||
)
|
||||
|
||||
// NewMetricWrapper builds new wrapper
|
||||
func NewMetricWrapper(telemetry MetricsStorage, localTelemetry MetricsStorage, logger logging.LoggerInterface) *MetricWrapper {
|
||||
return &MetricWrapper{
|
||||
LocalTelemetry: localTelemetry,
|
||||
logger: logger,
|
||||
Telemetry: telemetry,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MetricWrapper) getKey(key int) (string, string, error) {
|
||||
switch key {
|
||||
case SplitChangesCounter:
|
||||
return splitChangesCounter, counter, nil
|
||||
case SplitChangesLatency:
|
||||
return splitChangesLatency, localSplitChangesLatency, nil
|
||||
case SegmentChangesCounter:
|
||||
return segmentChangesCounter, counter, nil
|
||||
case SegmentChangesLatency:
|
||||
return segmentChangesLatency, localSegmentChangesLatency, nil
|
||||
case TestImpressionsCounter:
|
||||
return testImpressionsCounter, counter, nil
|
||||
case TestImpressionsLatency:
|
||||
return testImpressionsLatency, localTestImpressionsLatency, nil
|
||||
case PostEventsCounter:
|
||||
return postEventsCounter, counter, nil
|
||||
case PostEventsLatency:
|
||||
return postEventsLatency, localPostEventsLatency, nil
|
||||
case MySegmentsCounter:
|
||||
return mySegmentsCounter, counter, nil
|
||||
case MySegmentsLatency:
|
||||
return mySegmentsLatency, localMySegmentsLatency, nil
|
||||
default:
|
||||
return "", "", errors.New("Key does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
// StoreCounters stores counters
|
||||
func (m *MetricWrapper) StoreCounters(key int, value string) {
|
||||
common, local, err := m.getKey(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if m.LocalTelemetry != nil {
|
||||
m.LocalTelemetry.IncCounter(strings.Replace(local, "{status}", value, 1))
|
||||
}
|
||||
if value == "ok" {
|
||||
value = "200"
|
||||
}
|
||||
m.Telemetry.IncCounter(strings.Replace(common, "{status}", value, 1))
|
||||
}
|
||||
|
||||
// StoreLatencies stores counters
|
||||
func (m *MetricWrapper) StoreLatencies(key int, bucket int) {
|
||||
common, local, err := m.getKey(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if m.LocalTelemetry != nil {
|
||||
m.LocalTelemetry.IncLatency(local, bucket)
|
||||
}
|
||||
m.Telemetry.IncLatency(common, bucket)
|
||||
}
|
||||
43
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/event.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/event.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-split-commons/v3/dtos"
|
||||
|
||||
// MockEventStorage is a mocked implementation of Event Storage
|
||||
type MockEventStorage struct {
|
||||
EmptyCall func() bool
|
||||
CountCall func() int64
|
||||
PopNCall func(n int64) ([]dtos.EventDTO, error)
|
||||
PopNWithMetadataCall func(n int64) ([]dtos.QueueStoredEventDTO, error)
|
||||
PushCall func(event dtos.EventDTO, size int) error
|
||||
DropCall func(size *int64) error
|
||||
}
|
||||
|
||||
// Empty mock
|
||||
func (m MockEventStorage) Empty() bool {
|
||||
return m.EmptyCall()
|
||||
}
|
||||
|
||||
// Count mock
|
||||
func (m MockEventStorage) Count() int64 {
|
||||
return m.CountCall()
|
||||
}
|
||||
|
||||
// PopN mock
|
||||
func (m MockEventStorage) PopN(n int64) ([]dtos.EventDTO, error) {
|
||||
return m.PopNCall(n)
|
||||
}
|
||||
|
||||
// PopNWithMetadata mock
|
||||
func (m MockEventStorage) PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error) {
|
||||
return m.PopNWithMetadataCall(n)
|
||||
}
|
||||
|
||||
// Push mock
|
||||
func (m MockEventStorage) Push(event dtos.EventDTO, size int) error {
|
||||
return m.PushCall(event, size)
|
||||
}
|
||||
|
||||
// Drop mock
|
||||
func (m MockEventStorage) Drop(size *int64) error {
|
||||
return m.Drop(size)
|
||||
}
|
||||
43
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/impression.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/impression.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-split-commons/v3/dtos"
|
||||
|
||||
// MockImpressionStorage is a mocked implementation of Impression Storage
|
||||
type MockImpressionStorage struct {
|
||||
EmptyCall func() bool
|
||||
CountCall func() int64
|
||||
LogImpressionsCall func(impressions []dtos.Impression) error
|
||||
PopNCall func(n int64) ([]dtos.Impression, error)
|
||||
PopNWithMetadataCall func(n int64) ([]dtos.ImpressionQueueObject, error)
|
||||
DropCall func(size *int64) error
|
||||
}
|
||||
|
||||
// Empty mock
|
||||
func (m MockImpressionStorage) Empty() bool {
|
||||
return m.EmptyCall()
|
||||
}
|
||||
|
||||
// Count mock
|
||||
func (m MockImpressionStorage) Count() int64 {
|
||||
return m.CountCall()
|
||||
}
|
||||
|
||||
// LogImpressions mock
|
||||
func (m MockImpressionStorage) LogImpressions(impressions []dtos.Impression) error {
|
||||
return m.LogImpressionsCall(impressions)
|
||||
}
|
||||
|
||||
// PopN mock
|
||||
func (m MockImpressionStorage) PopN(n int64) ([]dtos.Impression, error) {
|
||||
return m.PopNCall(n)
|
||||
}
|
||||
|
||||
// PopNWithMetadata mock
|
||||
func (m MockImpressionStorage) PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error) {
|
||||
return m.PopNWithMetadataCall(n)
|
||||
}
|
||||
|
||||
// Drop mock
|
||||
func (m MockImpressionStorage) Drop(size *int64) error {
|
||||
return m.Drop(size)
|
||||
}
|
||||
73
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/metric.go
сгенерированный
поставляемый
Обычный файл
73
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/metric.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,73 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-split-commons/v3/dtos"
|
||||
|
||||
// MockMetricStorage is a mocked implementation of Metric Storage
|
||||
type MockMetricStorage struct {
|
||||
IncCounterCall func(key string)
|
||||
IncLatencyCall func(metricName string, index int)
|
||||
PutGaugeCall func(key string, gauge float64)
|
||||
PopGaugesCall func() []dtos.GaugeDTO
|
||||
PopLatenciesCall func() []dtos.LatenciesDTO
|
||||
PopCountersCall func() []dtos.CounterDTO
|
||||
PeekCountersCall func() map[string]int64
|
||||
PeekLatenciesCall func() map[string][]int64
|
||||
PopGaugesWithMetadataCall func() (*dtos.GaugeDataBulk, error)
|
||||
PopCountersWithMetadataCall func() (*dtos.CounterDataBulk, error)
|
||||
PopLatenciesWithMetadataCall func() (*dtos.LatencyDataBulk, error)
|
||||
}
|
||||
|
||||
// IncCounter mock
|
||||
func (m MockMetricStorage) IncCounter(key string) {
|
||||
m.IncCounterCall(key)
|
||||
}
|
||||
|
||||
// IncLatency mock
|
||||
func (m MockMetricStorage) IncLatency(metricName string, index int) {
|
||||
m.IncLatencyCall(metricName, index)
|
||||
}
|
||||
|
||||
// PutGauge mock
|
||||
func (m MockMetricStorage) PutGauge(key string, gauge float64) {
|
||||
m.PutGaugeCall(key, gauge)
|
||||
}
|
||||
|
||||
// PopGauges mock
|
||||
func (m MockMetricStorage) PopGauges() []dtos.GaugeDTO {
|
||||
return m.PopGaugesCall()
|
||||
}
|
||||
|
||||
// PopLatencies mock
|
||||
func (m MockMetricStorage) PopLatencies() []dtos.LatenciesDTO {
|
||||
return m.PopLatenciesCall()
|
||||
}
|
||||
|
||||
// PopCounters mock
|
||||
func (m MockMetricStorage) PopCounters() []dtos.CounterDTO {
|
||||
return m.PopCountersCall()
|
||||
}
|
||||
|
||||
// PeekCounters mock
|
||||
func (m MockMetricStorage) PeekCounters() map[string]int64 {
|
||||
return m.PeekCountersCall()
|
||||
}
|
||||
|
||||
// PeekLatencies mock
|
||||
func (m MockMetricStorage) PeekLatencies() map[string][]int64 {
|
||||
return m.PeekLatenciesCall()
|
||||
}
|
||||
|
||||
// PopGaugesWithMetadata mock
|
||||
func (m MockMetricStorage) PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error) {
|
||||
return m.PopGaugesWithMetadataCall()
|
||||
}
|
||||
|
||||
// PopCountersWithMetadata mock
|
||||
func (m MockMetricStorage) PopCountersWithMetadata() (*dtos.CounterDataBulk, error) {
|
||||
return m.PopCountersWithMetadataCall()
|
||||
}
|
||||
|
||||
// PopLatenciesWithMetadata mock
|
||||
func (m MockMetricStorage) PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error) {
|
||||
return m.PopLatenciesWithMetadataCall()
|
||||
}
|
||||
43
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/segment.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/segment.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
|
||||
// MockSegmentStorage is a mocked implementation of Segment Storage
|
||||
type MockSegmentStorage struct {
|
||||
ChangeNumberCall func(segmentName string) (int64, error)
|
||||
KeysCall func(segmentName string) *set.ThreadUnsafeSet
|
||||
UpdateCall func(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, changeNumber int64) error
|
||||
SegmentContainsKeyCall func(segmentName string, key string) (bool, error)
|
||||
SetChangeNumberCall func(segmentName string, till int64) error
|
||||
CountRemovedKeysCall func(segmentName string) int64
|
||||
}
|
||||
|
||||
// ChangeNumber mock
|
||||
func (m MockSegmentStorage) ChangeNumber(segmentName string) (int64, error) {
|
||||
return m.ChangeNumberCall(segmentName)
|
||||
}
|
||||
|
||||
// Keys mock
|
||||
func (m MockSegmentStorage) Keys(segmentName string) *set.ThreadUnsafeSet {
|
||||
return m.KeysCall(segmentName)
|
||||
}
|
||||
|
||||
// Update mock
|
||||
func (m MockSegmentStorage) Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, changeNumber int64) error {
|
||||
return m.UpdateCall(name, toAdd, toRemove, changeNumber)
|
||||
}
|
||||
|
||||
// SegmentContainsKey mock
|
||||
func (m MockSegmentStorage) SegmentContainsKey(segmentName string, key string) (bool, error) {
|
||||
return m.SegmentContainsKeyCall(segmentName, key)
|
||||
}
|
||||
|
||||
// SetChangeNumber mock
|
||||
func (m MockSegmentStorage) SetChangeNumber(segmentName string, till int64) error {
|
||||
return m.SetChangeNumberCall(segmentName, till)
|
||||
}
|
||||
|
||||
// CountRemovedKeys mock
|
||||
func (m MockSegmentStorage) CountRemovedKeys(segmentName string) int64 {
|
||||
return m.CountRemovedKeysCall(segmentName)
|
||||
}
|
||||
76
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/split.go
сгенерированный
поставляемый
Обычный файл
76
vendor/github.com/splitio/go-split-commons/v3/storage/mocks/split.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,76 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
)
|
||||
|
||||
// MockSplitStorage is a mocked implementation of Split Storage
|
||||
type MockSplitStorage struct {
|
||||
AllCall func() []dtos.SplitDTO
|
||||
ChangeNumberCall func() (int64, error)
|
||||
FetchManyCall func(splitNames []string) map[string]*dtos.SplitDTO
|
||||
KillLocallyCall func(splitName string, defaultTreatment string, changeNumber int64)
|
||||
PutManyCall func(splits []dtos.SplitDTO, changeNumber int64)
|
||||
RemoveCall func(splitName string)
|
||||
SegmentNamesCall func() *set.ThreadUnsafeSet
|
||||
SetChangeNumberCall func(changeNumber int64) error
|
||||
SplitCall func(splitName string) *dtos.SplitDTO
|
||||
SplitNamesCall func() []string
|
||||
TrafficTypeExistsCall func(trafficType string) bool
|
||||
}
|
||||
|
||||
// All mock
|
||||
func (m MockSplitStorage) All() []dtos.SplitDTO {
|
||||
return m.AllCall()
|
||||
}
|
||||
|
||||
// ChangeNumber mock
|
||||
func (m MockSplitStorage) ChangeNumber() (int64, error) {
|
||||
return m.ChangeNumberCall()
|
||||
}
|
||||
|
||||
// FetchMany mock
|
||||
func (m MockSplitStorage) FetchMany(splitNames []string) map[string]*dtos.SplitDTO {
|
||||
return m.FetchManyCall(splitNames)
|
||||
}
|
||||
|
||||
// KillLocally mock
|
||||
func (m MockSplitStorage) KillLocally(splitName string, defaultTreatment string, changeNumber int64) {
|
||||
m.KillLocallyCall(splitName, defaultTreatment, changeNumber)
|
||||
}
|
||||
|
||||
// PutMany mock
|
||||
func (m MockSplitStorage) PutMany(splits []dtos.SplitDTO, changeNumber int64) {
|
||||
m.PutManyCall(splits, changeNumber)
|
||||
}
|
||||
|
||||
// Remove mock
|
||||
func (m MockSplitStorage) Remove(splitname string) {
|
||||
m.RemoveCall(splitname)
|
||||
}
|
||||
|
||||
// SegmentNames mock
|
||||
func (m MockSplitStorage) SegmentNames() *set.ThreadUnsafeSet {
|
||||
return m.SegmentNamesCall()
|
||||
}
|
||||
|
||||
// SetChangeNumber mock
|
||||
func (m MockSplitStorage) SetChangeNumber(changeNumber int64) error {
|
||||
return m.SetChangeNumberCall(changeNumber)
|
||||
}
|
||||
|
||||
// Split mock
|
||||
func (m MockSplitStorage) Split(splitName string) *dtos.SplitDTO {
|
||||
return m.SplitCall(splitName)
|
||||
}
|
||||
|
||||
// SplitNames mock
|
||||
func (m MockSplitStorage) SplitNames() []string {
|
||||
return m.SplitNamesCall()
|
||||
}
|
||||
|
||||
// TrafficTypeExists mock
|
||||
func (m MockSplitStorage) TrafficTypeExists(trafficType string) bool {
|
||||
return m.TrafficTypeExistsCall(trafficType)
|
||||
}
|
||||
149
vendor/github.com/splitio/go-split-commons/v3/storage/mutexmap/metrics.go
сгенерированный
поставляемый
Обычный файл
149
vendor/github.com/splitio/go-split-commons/v3/storage/mutexmap/metrics.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,149 @@
|
||||
package mutexmap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
)
|
||||
|
||||
// MMMetricsStorage contains an in-memory implementation of Metrics storage
|
||||
type MMMetricsStorage struct {
|
||||
gaugeData map[string]float64
|
||||
gaugeMutex *sync.Mutex
|
||||
counterData map[string]int64
|
||||
countersMutex *sync.RWMutex
|
||||
latenciesData map[string][]int64
|
||||
latenciesMutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMMMetricsStorage instantiates a new MMMetricsStorage
|
||||
func NewMMMetricsStorage() *MMMetricsStorage {
|
||||
return &MMMetricsStorage{
|
||||
counterData: make(map[string]int64),
|
||||
countersMutex: &sync.RWMutex{},
|
||||
gaugeData: make(map[string]float64),
|
||||
gaugeMutex: &sync.Mutex{},
|
||||
latenciesData: make(map[string][]int64),
|
||||
latenciesMutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// PutGauge stores a new gauge value for a specific key
|
||||
func (m *MMMetricsStorage) PutGauge(key string, gauge float64) {
|
||||
m.gaugeMutex.Lock()
|
||||
defer m.gaugeMutex.Unlock()
|
||||
m.gaugeData[key] = gauge
|
||||
}
|
||||
|
||||
// PopGauges returns and deletes all gauges currently stored
|
||||
func (m *MMMetricsStorage) PopGauges() []dtos.GaugeDTO {
|
||||
m.gaugeMutex.Lock()
|
||||
defer func() {
|
||||
m.gaugeData = make(map[string]float64)
|
||||
m.gaugeMutex.Unlock()
|
||||
}()
|
||||
|
||||
gauges := make([]dtos.GaugeDTO, 0)
|
||||
for key, gauge := range m.gaugeData {
|
||||
gauges = append(gauges, dtos.GaugeDTO{
|
||||
MetricName: key,
|
||||
Gauge: gauge,
|
||||
})
|
||||
}
|
||||
return gauges
|
||||
}
|
||||
|
||||
// IncCounter increments the counter for a specific key. It initializes it in 1 if it doesn't exist when this function
|
||||
// is called.
|
||||
func (m *MMMetricsStorage) IncCounter(key string) {
|
||||
m.countersMutex.Lock()
|
||||
defer m.countersMutex.Unlock()
|
||||
_, exists := m.counterData[key]
|
||||
if !exists {
|
||||
m.counterData[key] = 1
|
||||
} else {
|
||||
m.counterData[key]++
|
||||
}
|
||||
}
|
||||
|
||||
// PopCounters returns and deletes all the counters stored
|
||||
func (m *MMMetricsStorage) PopCounters() []dtos.CounterDTO {
|
||||
m.countersMutex.Lock()
|
||||
defer func() {
|
||||
m.counterData = make(map[string]int64)
|
||||
m.countersMutex.Unlock()
|
||||
}()
|
||||
|
||||
counters := make([]dtos.CounterDTO, 0)
|
||||
for key, counter := range m.counterData {
|
||||
counters = append(counters, dtos.CounterDTO{
|
||||
MetricName: key,
|
||||
Count: counter,
|
||||
})
|
||||
}
|
||||
return counters
|
||||
}
|
||||
|
||||
// PeekCounters returns Counters
|
||||
func (m *MMMetricsStorage) PeekCounters() map[string]int64 {
|
||||
m.countersMutex.RLock()
|
||||
defer m.countersMutex.RUnlock()
|
||||
return m.counterData
|
||||
}
|
||||
|
||||
// PeekLatencies returns Latencies
|
||||
func (m *MMMetricsStorage) PeekLatencies() map[string][]int64 {
|
||||
m.latenciesMutex.RLock()
|
||||
defer m.latenciesMutex.RUnlock()
|
||||
return m.latenciesData
|
||||
}
|
||||
|
||||
// IncLatency increments the latency for a specific key and bucket. If the key doesn't exist it's initialized to
|
||||
// an empty array of 23 items.
|
||||
func (m *MMMetricsStorage) IncLatency(metricName string, index int) {
|
||||
if index < 0 || index > 22 {
|
||||
return
|
||||
}
|
||||
m.latenciesMutex.Lock()
|
||||
defer m.latenciesMutex.Unlock()
|
||||
_, exists := m.latenciesData[metricName]
|
||||
if !exists {
|
||||
m.latenciesData[metricName] = make([]int64, 23)
|
||||
m.latenciesData[metricName][index] = 1
|
||||
} else {
|
||||
m.latenciesData[metricName][index]++
|
||||
}
|
||||
}
|
||||
|
||||
// PopLatencies Returns and delete all the latencies currently stored
|
||||
func (m *MMMetricsStorage) PopLatencies() []dtos.LatenciesDTO {
|
||||
m.latenciesMutex.Lock()
|
||||
defer func() {
|
||||
m.latenciesData = make(map[string][]int64)
|
||||
m.latenciesMutex.Unlock()
|
||||
}()
|
||||
|
||||
latencies := make([]dtos.LatenciesDTO, 0)
|
||||
for key, latency := range m.latenciesData {
|
||||
latencies = append(latencies, dtos.LatenciesDTO{
|
||||
Latencies: latency,
|
||||
MetricName: key,
|
||||
})
|
||||
}
|
||||
return latencies
|
||||
}
|
||||
|
||||
// PopGaugesWithMetadata mock
|
||||
func (m *MMMetricsStorage) PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error) {
|
||||
panic("Not implemented for inmemory")
|
||||
}
|
||||
|
||||
// PopLatenciesWithMetadata mock
|
||||
func (m *MMMetricsStorage) PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error) {
|
||||
panic("Not implemented for inmemory")
|
||||
}
|
||||
|
||||
// PopCountersWithMetadata mock
|
||||
func (m *MMMetricsStorage) PopCountersWithMetadata() (*dtos.CounterDataBulk, error) {
|
||||
panic("Not implemented for inmemory")
|
||||
}
|
||||
88
vendor/github.com/splitio/go-split-commons/v3/storage/mutexmap/segments.go
сгенерированный
поставляемый
Обычный файл
88
vendor/github.com/splitio/go-split-commons/v3/storage/mutexmap/segments.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,88 @@
|
||||
package mutexmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
)
|
||||
|
||||
// MMSegmentStorage contains is an in-memory implementation of segment storage
|
||||
type MMSegmentStorage struct {
|
||||
data map[string]*set.ThreadUnsafeSet
|
||||
till map[string]int64
|
||||
mutex *sync.RWMutex
|
||||
tillMutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMMSegmentStorage instantiates a new MMSegmentStorage
|
||||
func NewMMSegmentStorage() *MMSegmentStorage {
|
||||
return &MMSegmentStorage{
|
||||
data: make(map[string]*set.ThreadUnsafeSet),
|
||||
till: make(map[string]int64),
|
||||
mutex: &sync.RWMutex{},
|
||||
tillMutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeNumber returns the latest timestamp the segment was fetched
|
||||
func (m *MMSegmentStorage) ChangeNumber(segmentName string) (int64, error) {
|
||||
m.tillMutex.RLock()
|
||||
defer m.tillMutex.RUnlock()
|
||||
return m.till[segmentName], nil
|
||||
}
|
||||
|
||||
// Keys retrieves a segment from the in-memory storage
|
||||
// NOTE: A pointer TO A COPY is returned, in order to avoid race conditions between
|
||||
// evaluations and sdk <-> backend sync
|
||||
func (m *MMSegmentStorage) Keys(segmentName string) *set.ThreadUnsafeSet {
|
||||
// @TODO replace to IsInSegment
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
item, exists := m.data[segmentName]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
s := item.Copy().(*set.ThreadUnsafeSet)
|
||||
return s
|
||||
}
|
||||
|
||||
// SegmentContainsKey returns true if the segment contains a specific key
|
||||
func (m *MMSegmentStorage) SegmentContainsKey(segmentName string, key string) (bool, error) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
item, exists := m.data[segmentName]
|
||||
if !exists {
|
||||
return false, fmt.Errorf("segment %s not found in storage", segmentName)
|
||||
}
|
||||
return item.Has(key), nil
|
||||
}
|
||||
|
||||
// SetChangeNumber sets the till value belong to segmentName
|
||||
func (m *MMSegmentStorage) SetChangeNumber(name string, till int64) error {
|
||||
m.tillMutex.Lock()
|
||||
defer m.tillMutex.Unlock()
|
||||
m.till[name] = till
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update adds a new segment to the in-memory storage
|
||||
func (m *MMSegmentStorage) Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, till int64) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
_, ok := m.data[name]
|
||||
if !ok {
|
||||
m.data[name] = set.NewSet()
|
||||
}
|
||||
if !toRemove.IsEmpty() {
|
||||
m.data[name].Remove(toRemove.List()...)
|
||||
}
|
||||
if !toAdd.IsEmpty() {
|
||||
m.data[name].Add(toAdd.List()...)
|
||||
}
|
||||
m.SetChangeNumber(name, till)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountRemovedKeys method
|
||||
func (m *MMSegmentStorage) CountRemovedKeys(segmentName string) int64 { return 0 }
|
||||
194
vendor/github.com/splitio/go-split-commons/v3/storage/mutexmap/splits.go
сгенерированный
поставляемый
Обычный файл
194
vendor/github.com/splitio/go-split-commons/v3/storage/mutexmap/splits.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,194 @@
|
||||
package mutexmap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
)
|
||||
|
||||
// MMSplitStorage struct contains is an in-memory implementation of split storage
|
||||
type MMSplitStorage struct {
|
||||
data map[string]dtos.SplitDTO
|
||||
trafficTypes map[string]int64
|
||||
till int64
|
||||
mutex *sync.RWMutex
|
||||
ttMutex *sync.RWMutex
|
||||
tillMutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMMSplitStorage instantiates a new MMSplitStorage
|
||||
func NewMMSplitStorage() *MMSplitStorage {
|
||||
return &MMSplitStorage{
|
||||
data: make(map[string]dtos.SplitDTO),
|
||||
trafficTypes: make(map[string]int64),
|
||||
till: 0,
|
||||
mutex: &sync.RWMutex{},
|
||||
ttMutex: &sync.RWMutex{},
|
||||
tillMutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// All returns a list with a copy of each split.
|
||||
// NOTE: This method will block any further operations regarding splits. Use with caution
|
||||
func (m *MMSplitStorage) All() []dtos.SplitDTO {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
splitList := make([]dtos.SplitDTO, 0)
|
||||
for _, split := range m.data {
|
||||
splitList = append(splitList, split)
|
||||
}
|
||||
return splitList
|
||||
}
|
||||
|
||||
// ChangeNumber returns the last timestamp the split was fetched
|
||||
func (m *MMSplitStorage) ChangeNumber() (int64, error) {
|
||||
m.tillMutex.RLock()
|
||||
defer m.tillMutex.RUnlock()
|
||||
return m.till, nil
|
||||
}
|
||||
|
||||
func (m *MMSplitStorage) _get(splitName string) *dtos.SplitDTO {
|
||||
item, exists := m.data[splitName]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return &item
|
||||
}
|
||||
|
||||
// FetchMany fetches features in redis and returns an array of split dtos
|
||||
func (m *MMSplitStorage) FetchMany(splitNames []string) map[string]*dtos.SplitDTO {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
splits := make(map[string]*dtos.SplitDTO)
|
||||
for _, splitName := range splitNames {
|
||||
splits[splitName] = m._get(splitName)
|
||||
}
|
||||
return splits
|
||||
}
|
||||
|
||||
// KillLocally kills the split locally
|
||||
func (m *MMSplitStorage) KillLocally(splitName string, defaultTreatment string, changeNumber int64) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
split := m._get(splitName)
|
||||
till, err := m.ChangeNumber()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if split != nil && till < changeNumber {
|
||||
split.DefaultTreatment = defaultTreatment
|
||||
split.Killed = true
|
||||
split.ChangeNumber = changeNumber
|
||||
m.data[split.Name] = *split
|
||||
}
|
||||
}
|
||||
|
||||
// increaseTrafficTypeCount increases value for a traffic type
|
||||
func (m *MMSplitStorage) increaseTrafficTypeCount(trafficType string) {
|
||||
m.ttMutex.Lock()
|
||||
defer m.ttMutex.Unlock()
|
||||
_, exists := m.trafficTypes[trafficType]
|
||||
if !exists {
|
||||
m.trafficTypes[trafficType] = 1
|
||||
} else {
|
||||
m.trafficTypes[trafficType]++
|
||||
}
|
||||
}
|
||||
|
||||
// decreaseTrafficTypeCount decreases value for a traffic type
|
||||
func (m *MMSplitStorage) decreaseTrafficTypeCount(trafficType string) {
|
||||
m.ttMutex.Lock()
|
||||
defer m.ttMutex.Unlock()
|
||||
value, exists := m.trafficTypes[trafficType]
|
||||
if exists {
|
||||
if value > 0 {
|
||||
m.trafficTypes[trafficType]--
|
||||
} else {
|
||||
delete(m.trafficTypes, trafficType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PutMany bulk inserts splits into the in-memory storage
|
||||
func (m *MMSplitStorage) PutMany(splits []dtos.SplitDTO, till int64) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
for _, split := range splits {
|
||||
existing, thisIsAnUpdate := m.data[split.Name]
|
||||
if thisIsAnUpdate {
|
||||
// If it's an update, we decrement the traffic type count of the existing split,
|
||||
// and then add the updated one (as part of the normal flow), in case it's different.
|
||||
m.decreaseTrafficTypeCount(existing.TrafficTypeName)
|
||||
}
|
||||
m.data[split.Name] = split
|
||||
m.increaseTrafficTypeCount(split.TrafficTypeName)
|
||||
}
|
||||
m.SetChangeNumber(till)
|
||||
}
|
||||
|
||||
// Remove deletes a split from the in-memory storage
|
||||
func (m *MMSplitStorage) Remove(splitName string) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
split, exists := m.data[splitName]
|
||||
if exists {
|
||||
delete(m.data, splitName)
|
||||
m.decreaseTrafficTypeCount(split.TrafficTypeName)
|
||||
}
|
||||
}
|
||||
|
||||
// SegmentNames returns a slice with the names of all segments referenced in splits
|
||||
func (m *MMSplitStorage) SegmentNames() *set.ThreadUnsafeSet {
|
||||
segments := set.NewSet()
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
for _, split := range m.data {
|
||||
for _, condition := range split.Conditions {
|
||||
for _, matcher := range condition.MatcherGroup.Matchers {
|
||||
if matcher.UserDefinedSegment != nil {
|
||||
segments.Add(matcher.UserDefinedSegment.SegmentName)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return segments
|
||||
}
|
||||
|
||||
// SetChangeNumber sets the till value belong to split
|
||||
func (m *MMSplitStorage) SetChangeNumber(till int64) error {
|
||||
m.tillMutex.Lock()
|
||||
defer m.tillMutex.Unlock()
|
||||
m.till = till
|
||||
return nil
|
||||
}
|
||||
|
||||
// Split retrieves a split from the MMSplitStorage
|
||||
// NOTE: A pointer TO A COPY is returned, in order to avoid race conditions between
|
||||
// evaluations and sdk <-> backend sync
|
||||
func (m *MMSplitStorage) Split(splitName string) *dtos.SplitDTO {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
return m._get(splitName)
|
||||
}
|
||||
|
||||
// SplitNames returns a slice with the names of all the current splits
|
||||
func (m *MMSplitStorage) SplitNames() []string {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
splitNames := make([]string, 0)
|
||||
for key := range m.data {
|
||||
splitNames = append(splitNames, key)
|
||||
}
|
||||
return splitNames
|
||||
}
|
||||
|
||||
// TrafficTypeExists returns true or false depending on existence and counter
|
||||
// of trafficType
|
||||
func (m *MMSplitStorage) TrafficTypeExists(trafficType string) bool {
|
||||
m.ttMutex.RLock()
|
||||
defer m.ttMutex.RUnlock()
|
||||
value, exists := m.trafficTypes[trafficType]
|
||||
return exists && value > 0
|
||||
}
|
||||
6
vendor/github.com/splitio/go-split-commons/v3/storage/mutexqueue/constants.go
сгенерированный
поставляемый
Обычный файл
6
vendor/github.com/splitio/go-split-commons/v3/storage/mutexqueue/constants.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,6 @@
|
||||
package mutexqueue
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrorMaxSizeReached queue max size error
|
||||
var ErrorMaxSizeReached = errors.New("Queue max size has been reached")
|
||||
136
vendor/github.com/splitio/go-split-commons/v3/storage/mutexqueue/events.go
сгенерированный
поставляемый
Обычный файл
136
vendor/github.com/splitio/go-split-commons/v3/storage/mutexqueue/events.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,136 @@
|
||||
package mutexqueue
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// MaxAccumulatedBytes is the maximum size to accumulate in events before flush (in bytes)
|
||||
const MaxAccumulatedBytes = 5 * 1024 * 1024
|
||||
|
||||
// NewMQEventsStorage returns an instance of MQEventsStorage
|
||||
func NewMQEventsStorage(queueSize int, isFull chan string, logger logging.LoggerInterface) *MQEventsStorage {
|
||||
return &MQEventsStorage{
|
||||
queue: list.New(),
|
||||
size: queueSize,
|
||||
mutexQueue: &sync.Mutex{},
|
||||
fullChan: isFull,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type eventWrapper struct {
|
||||
event dtos.EventDTO
|
||||
size int
|
||||
}
|
||||
|
||||
// MQEventsStorage in memory events storage
|
||||
type MQEventsStorage struct {
|
||||
queue *list.List
|
||||
size int
|
||||
accumulatedBytes int
|
||||
mutexQueue *sync.Mutex
|
||||
fullChan chan string //only write channel
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
func (s *MQEventsStorage) sendSignalIsFull() {
|
||||
// Nom blocking select
|
||||
select {
|
||||
case s.fullChan <- "EVENTS_FULL":
|
||||
// Send "queue is full" signal
|
||||
break
|
||||
default:
|
||||
s.logger.Debug("Some error occurred on sending signal for events")
|
||||
}
|
||||
}
|
||||
|
||||
// Push an event into slice
|
||||
func (s *MQEventsStorage) Push(event dtos.EventDTO, size int) error {
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
|
||||
if s.queue.Len()+1 > s.size {
|
||||
s.sendSignalIsFull()
|
||||
return ErrorMaxSizeReached
|
||||
}
|
||||
|
||||
// Add element
|
||||
s.queue.PushBack(eventWrapper{event: event, size: size})
|
||||
s.accumulatedBytes += size
|
||||
if s.queue.Len() == s.size || s.accumulatedBytes >= MaxAccumulatedBytes {
|
||||
s.sendSignalIsFull()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopN pop N elements from queue
|
||||
func (s *MQEventsStorage) PopN(n int64) ([]dtos.EventDTO, error) {
|
||||
var toReturn []dtos.EventDTO
|
||||
var totalItems int
|
||||
|
||||
// Mutexing queue
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
|
||||
if int64(s.queue.Len()) >= n {
|
||||
totalItems = int(n)
|
||||
} else {
|
||||
totalItems = s.queue.Len()
|
||||
}
|
||||
|
||||
toReturn = make([]dtos.EventDTO, 0)
|
||||
accumulated := 0
|
||||
errorCount := 0
|
||||
for i := 0; i < totalItems; i++ {
|
||||
bundled, ok := s.queue.Remove(s.queue.Front()).(eventWrapper)
|
||||
if !ok {
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
toReturn = append(toReturn, bundled.event)
|
||||
accumulated += bundled.size
|
||||
if accumulated >= MaxAccumulatedBytes {
|
||||
// If we reached the maximum allowed size, break the loop so that we don't sent huge POST bodies to the BE
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.accumulatedBytes -= accumulated
|
||||
if errorCount > 0 {
|
||||
return toReturn, fmt.Errorf("%d elements could not be decoded", errorCount)
|
||||
}
|
||||
|
||||
return toReturn, nil
|
||||
}
|
||||
|
||||
// PopNWithMetadata pop N elements from queue
|
||||
func (s *MQEventsStorage) PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error) {
|
||||
panic("Not implemented for inmemory")
|
||||
}
|
||||
|
||||
// Empty returns if slice len if zero
|
||||
func (s *MQEventsStorage) Empty() bool {
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
|
||||
return s.queue.Len() == 0
|
||||
}
|
||||
|
||||
// Count returns the number of events into slice
|
||||
func (s *MQEventsStorage) Count() int64 {
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
|
||||
return int64(s.queue.Len())
|
||||
}
|
||||
|
||||
// Drop drops
|
||||
func (s *MQEventsStorage) Drop(size *int64) error {
|
||||
panic("Not implemented for inmemory")
|
||||
}
|
||||
108
vendor/github.com/splitio/go-split-commons/v3/storage/mutexqueue/impressions.go
сгенерированный
поставляемый
Обычный файл
108
vendor/github.com/splitio/go-split-commons/v3/storage/mutexqueue/impressions.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,108 @@
|
||||
package mutexqueue
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// NewMQImpressionsStorage returns an instance of MQEventsStorage
|
||||
func NewMQImpressionsStorage(queueSize int, isFull chan<- string, logger logging.LoggerInterface) *MQImpressionsStorage {
|
||||
return &MQImpressionsStorage{
|
||||
queue: list.New(),
|
||||
size: queueSize,
|
||||
mutexQueue: &sync.Mutex{},
|
||||
fullChan: isFull,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// MQImpressionsStorage in memory events storage
|
||||
type MQImpressionsStorage struct {
|
||||
queue *list.List
|
||||
size int
|
||||
mutexQueue *sync.Mutex
|
||||
fullChan chan<- string //only write channel
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
func (s *MQImpressionsStorage) sendSignalIsFull() {
|
||||
// Nom blocking select
|
||||
select {
|
||||
case s.fullChan <- "IMPRESSIONS_FULL":
|
||||
// Send "queue is full" signal
|
||||
break
|
||||
default:
|
||||
s.logger.Debug("Some error occurred on sending signal for impressions")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Empty returns if slice len if zero
|
||||
func (s *MQImpressionsStorage) Empty() bool {
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
return s.queue.Len() == 0
|
||||
}
|
||||
|
||||
// Count returns len
|
||||
func (s *MQImpressionsStorage) Count() int64 {
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
return int64(s.queue.Len())
|
||||
}
|
||||
|
||||
// LogImpressions inserts impressions into the queue
|
||||
func (s *MQImpressionsStorage) LogImpressions(impressions []dtos.Impression) error {
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
|
||||
for _, impression := range impressions {
|
||||
if s.queue.Len()+1 > s.size {
|
||||
s.sendSignalIsFull()
|
||||
return ErrorMaxSizeReached
|
||||
}
|
||||
// Add element
|
||||
s.queue.PushBack(impression)
|
||||
|
||||
if s.queue.Len() == s.size {
|
||||
s.sendSignalIsFull()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopN pop N elements from queue
|
||||
func (s *MQImpressionsStorage) PopN(n int64) ([]dtos.Impression, error) {
|
||||
var toReturn []dtos.Impression
|
||||
var totalItems int
|
||||
|
||||
// Mutexing queue
|
||||
s.mutexQueue.Lock()
|
||||
defer s.mutexQueue.Unlock()
|
||||
|
||||
if int64(s.queue.Len()) >= n {
|
||||
totalItems = int(n)
|
||||
} else {
|
||||
totalItems = s.queue.Len()
|
||||
}
|
||||
|
||||
toReturn = make([]dtos.Impression, totalItems)
|
||||
for i := 0; i < totalItems; i++ {
|
||||
toReturn[i] = s.queue.Remove(s.queue.Front()).(dtos.Impression)
|
||||
}
|
||||
|
||||
return toReturn, nil
|
||||
}
|
||||
|
||||
// PopNWithMetadata pop N elements from queue
|
||||
func (s *MQImpressionsStorage) PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error) {
|
||||
panic("Not implemented for inmemory")
|
||||
}
|
||||
|
||||
// Drop drops
|
||||
func (s *MQImpressionsStorage) Drop(size *int64) error {
|
||||
panic("Not implemented for inmemory")
|
||||
}
|
||||
17
vendor/github.com/splitio/go-split-commons/v3/storage/redis/constants.go
сгенерированный
поставляемый
Обычный файл
17
vendor/github.com/splitio/go-split-commons/v3/storage/redis/constants.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,17 @@
|
||||
package redis
|
||||
|
||||
const (
|
||||
redisSplit = "SPLITIO.split.{split}" // split object
|
||||
redisSplitTill = "SPLITIO.splits.till" // last split fetch
|
||||
redisSegment = "SPLITIO.segment.{segment}" // segment object
|
||||
redisSegmentTill = "SPLITIO.segment.{segment}.till" // last segment fetch
|
||||
redisImpressions = "SPLITIO/{sdkVersion}/{instanceId}/impressions.{feature}" // impressions for a feature
|
||||
redisLatency = "SPLITIO/{sdkVersion}/{instanceId}/latency.{metric}.bucket.{bucket}" // latency bucket
|
||||
redisCounter = "SPLITIO/{sdkVersion}/{instanceId}/count.{metric}" // counter
|
||||
redisGauge = "SPLITIO/{sdkVersion}/{instanceId}/gauge.{metric}" // gauge
|
||||
redisEvents = "SPLITIO.events" // events LIST key
|
||||
redisImpressionsQueue = "SPLITIO.impressions" // impressions LIST key
|
||||
redisImpressionsTTL = 60 // impressions default TTL
|
||||
redisTrafficType = "SPLITIO.trafficType.{trafficType}" // traffic Type fetch
|
||||
redisHash = "SPLITIO.hash"
|
||||
)
|
||||
177
vendor/github.com/splitio/go-split-commons/v3/storage/redis/events.go
сгенерированный
поставляемый
Обычный файл
177
vendor/github.com/splitio/go-split-commons/v3/storage/redis/events.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,177 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/queuecache"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
)
|
||||
|
||||
// EventsStorage redis implementation of EventsStorage interface
|
||||
type EventsStorage struct {
|
||||
cache queuecache.InMemoryQueueCacheOverlay
|
||||
client *redis.PrefixedRedisClient
|
||||
logger logging.LoggerInterface
|
||||
metadata dtos.Metadata
|
||||
redisKey string
|
||||
refillMutex *sync.RWMutex
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// maxAccumulatedSize is the maximum number of bytes to be fetched from cache before posting to the backend
|
||||
const maxAccumulatedSize = 5 * 1024 * 1024
|
||||
|
||||
// maxEventSize is the maximum allowed event size
|
||||
const maxEventSize = 32 * 1024
|
||||
|
||||
// NewEventStorageConsumer storage for consumer
|
||||
func NewEventStorageConsumer(redisClient *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *EventsStorage {
|
||||
return &EventsStorage{
|
||||
cache: queuecache.InMemoryQueueCacheOverlay{},
|
||||
client: redisClient,
|
||||
logger: logger,
|
||||
metadata: metadata,
|
||||
redisKey: redisEvents,
|
||||
refillMutex: &sync.RWMutex{},
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewEventsStorage returns an instance of RedisEventsStorage
|
||||
func NewEventsStorage(redisClient *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *EventsStorage {
|
||||
refillMutex := &sync.RWMutex{}
|
||||
refillFunc := func(count int) ([]interface{}, error) {
|
||||
refillMutex.Lock()
|
||||
defer refillMutex.Unlock()
|
||||
lrange, err := redisClient.LRange(redisEvents, 0, int64(count-1))
|
||||
if err != nil {
|
||||
logger.Error("Fetching events", err)
|
||||
return nil, err
|
||||
}
|
||||
totalFetchedEvents := len(lrange)
|
||||
|
||||
idxFrom := count
|
||||
if totalFetchedEvents < count {
|
||||
idxFrom = totalFetchedEvents
|
||||
}
|
||||
|
||||
err = redisClient.LTrim(redisEvents, int64(idxFrom), -1)
|
||||
if err != nil {
|
||||
logger.Error("Trim events", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toReturn := make([]interface{}, len(lrange))
|
||||
for index, item := range lrange {
|
||||
toReturn[index] = item
|
||||
}
|
||||
return toReturn, nil
|
||||
}
|
||||
|
||||
return &EventsStorage{
|
||||
cache: *queuecache.New(10000, refillFunc),
|
||||
client: redisClient,
|
||||
logger: logger,
|
||||
metadata: metadata,
|
||||
redisKey: redisEvents,
|
||||
refillMutex: refillMutex,
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// Push events into Redis LIST data type with RPUSH command
|
||||
func (r *EventsStorage) Push(event dtos.EventDTO, _ int) error {
|
||||
var queueMessage = dtos.QueueStoredEventDTO{Metadata: r.metadata, Event: event}
|
||||
|
||||
eventJSON, err := json.Marshal(queueMessage)
|
||||
if err != nil {
|
||||
r.logger.Error("Something were wrong marshaling provided event to JSON", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Debug("Pushing events to:", r.redisKey, string(eventJSON))
|
||||
|
||||
_, errPush := r.client.RPush(r.redisKey, eventJSON)
|
||||
if errPush != nil {
|
||||
r.logger.Error("Something were wrong pushing event to redis", errPush)
|
||||
return errPush
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopN return N elements from 0 to N
|
||||
func (r *EventsStorage) PopN(n int64) ([]dtos.EventDTO, error) {
|
||||
panic("Not implemented for redis")
|
||||
}
|
||||
|
||||
// PopNWithMetadata pop N elements from queue
|
||||
func (r *EventsStorage) PopNWithMetadata(n int64) ([]dtos.QueueStoredEventDTO, error) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
toReturn := make([]dtos.QueueStoredEventDTO, n)
|
||||
var err error
|
||||
fetchedCount := 0
|
||||
accumulatedSize := 0
|
||||
writeIndex := 0
|
||||
for r.Count() > 0 && int64(fetchedCount) < n && accumulatedSize+maxEventSize < maxAccumulatedSize && err == nil {
|
||||
numberOfItemsToFetch := int(math.Min(
|
||||
float64((maxAccumulatedSize-accumulatedSize)/maxEventSize),
|
||||
float64(n-int64(fetchedCount)),
|
||||
))
|
||||
elems, err := r.cache.Fetch(numberOfItemsToFetch)
|
||||
if err != nil {
|
||||
r.logger.Error("Error fetching events", err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
for _, elem := range elems {
|
||||
asStr, ok := elem.(string)
|
||||
if !ok {
|
||||
r.logger.Error("Error type-asserting event as string", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
storedEventDTO := dtos.QueueStoredEventDTO{}
|
||||
err = json.Unmarshal([]byte(asStr), &storedEventDTO)
|
||||
if err != nil {
|
||||
r.logger.Error("Error decoding event JSON", err.Error())
|
||||
continue
|
||||
}
|
||||
accumulatedSize += storedEventDTO.Event.Size()
|
||||
toReturn[writeIndex] = storedEventDTO
|
||||
writeIndex++
|
||||
}
|
||||
fetchedCount += len(elems)
|
||||
}
|
||||
return toReturn[0:writeIndex], nil
|
||||
}
|
||||
|
||||
// Count returns the number of items in the redis list
|
||||
func (r *EventsStorage) Count() int64 {
|
||||
val, err := r.client.LLen(r.redisKey)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Empty returns true if redis list is zero length
|
||||
func (r *EventsStorage) Empty() bool {
|
||||
return r.Count() == 0
|
||||
}
|
||||
|
||||
// Drop drops events from queue
|
||||
func (r *EventsStorage) Drop(size *int64) error {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
if size == nil {
|
||||
_, err := r.client.Del(r.redisKey)
|
||||
return err
|
||||
}
|
||||
return r.client.LTrim(r.redisKey, *size, -1)
|
||||
}
|
||||
149
vendor/github.com/splitio/go-split-commons/v3/storage/redis/impressions.go
сгенерированный
поставляемый
Обычный файл
149
vendor/github.com/splitio/go-split-commons/v3/storage/redis/impressions.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,149 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
)
|
||||
|
||||
const impressionsTTLRefresh = time.Duration(3600) * time.Second
|
||||
|
||||
// ImpressionStorage is a redis-based implementation of split storage
|
||||
type ImpressionStorage struct {
|
||||
client *redis.PrefixedRedisClient
|
||||
mutex *sync.Mutex
|
||||
logger logging.LoggerInterface
|
||||
redisKey string
|
||||
impressionsTTL time.Duration
|
||||
metadata dtos.Metadata
|
||||
}
|
||||
|
||||
// NewImpressionStorage creates a new RedisSplitStorage and returns a reference to it
|
||||
func NewImpressionStorage(client *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *ImpressionStorage {
|
||||
return &ImpressionStorage{
|
||||
client: client,
|
||||
mutex: &sync.Mutex{},
|
||||
logger: logger,
|
||||
redisKey: redisImpressionsQueue,
|
||||
impressionsTTL: redisImpressionsTTL,
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Count returns the size of the impressions queue
|
||||
func (r *ImpressionStorage) Count() int64 {
|
||||
val, err := r.client.LLen(r.redisKey)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Drop drops impressions from queue
|
||||
func (r *ImpressionStorage) Drop(size *int64) error {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
if size == nil {
|
||||
_, err := r.client.Del(r.redisKey)
|
||||
return err
|
||||
}
|
||||
return r.client.LTrim(r.redisKey, *size, -1)
|
||||
}
|
||||
|
||||
// Empty returns true if redis list is zero length
|
||||
func (r *ImpressionStorage) Empty() bool {
|
||||
return r.Count() == 0
|
||||
}
|
||||
|
||||
// push stores impressions in redis
|
||||
func (r *ImpressionStorage) push(impressions []dtos.ImpressionQueueObject) error {
|
||||
var impressionsJSON []interface{}
|
||||
for _, impression := range impressions {
|
||||
iJSON, err := json.Marshal(impression)
|
||||
if err != nil {
|
||||
r.logger.Error("Error encoding impression in json")
|
||||
r.logger.Error(err)
|
||||
} else {
|
||||
impressionsJSON = append(impressionsJSON, iJSON)
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Debug("Pushing impressions to: ", r.redisKey, len(impressionsJSON))
|
||||
|
||||
inserted, errPush := r.client.RPush(r.redisKey, impressionsJSON...)
|
||||
if errPush != nil {
|
||||
r.logger.Error("Something were wrong pushing impressions to redis", errPush)
|
||||
return errPush
|
||||
}
|
||||
|
||||
// Checks if expiration needs to be set
|
||||
if inserted == int64(len(impressionsJSON)) {
|
||||
r.logger.Debug("Proceeding to set expiration for: ", r.redisKey)
|
||||
result := r.client.Expire(r.redisKey, time.Duration(r.impressionsTTL)*time.Minute)
|
||||
if result == false {
|
||||
r.logger.Error("Something were wrong setting expiration", errPush)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogImpressions stores impressions in redis as Queue
|
||||
func (r *ImpressionStorage) LogImpressions(impressions []dtos.Impression) error {
|
||||
var impressionsToStore []dtos.ImpressionQueueObject
|
||||
for _, i := range impressions {
|
||||
var impression = dtos.ImpressionQueueObject{Metadata: r.metadata, Impression: i}
|
||||
impressionsToStore = append(impressionsToStore, impression)
|
||||
}
|
||||
|
||||
if len(impressionsToStore) > 0 {
|
||||
return r.push(impressionsToStore)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopN return N elements from 0 to N
|
||||
func (r *ImpressionStorage) PopN(n int64) ([]dtos.Impression, error) {
|
||||
panic("Not implemented for redis")
|
||||
}
|
||||
|
||||
// PopNWithMetadata pop N elements from queue
|
||||
func (r *ImpressionStorage) PopNWithMetadata(n int64) ([]dtos.ImpressionQueueObject, error) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
toReturn := make([]dtos.ImpressionQueueObject, 0, n)
|
||||
lrange, err := r.client.LRange(r.redisKey, 0, n-1)
|
||||
if err != nil {
|
||||
r.logger.Error("Error fetching impressions")
|
||||
return toReturn, err
|
||||
}
|
||||
|
||||
fetchedCount := int64(len(lrange))
|
||||
err = r.client.LTrim(r.redisKey, fetchedCount, int64(-1))
|
||||
if err != nil {
|
||||
r.logger.Error("Error trimming impressions")
|
||||
return toReturn, err
|
||||
}
|
||||
|
||||
// This operation will simply do nothing if the key no longer exists (queue is empty)
|
||||
// It's only done in the "successful" exit path so that the TTL is not overriden if impressons weren't
|
||||
// popped correctly. This will result in impressions getting lost but will prevent the queue from taking
|
||||
// a huge amount of memory.
|
||||
r.client.Expire(r.redisKey, impressionsTTLRefresh)
|
||||
|
||||
for _, asStr := range lrange {
|
||||
storedImpressionDTO := dtos.ImpressionQueueObject{}
|
||||
err = json.Unmarshal([]byte(asStr), &storedImpressionDTO)
|
||||
if err != nil {
|
||||
r.logger.Error("Error decoding event JSON", err.Error())
|
||||
continue
|
||||
}
|
||||
toReturn = append(toReturn, storedImpressionDTO)
|
||||
}
|
||||
|
||||
return toReturn, nil
|
||||
}
|
||||
322
vendor/github.com/splitio/go-split-commons/v3/storage/redis/metrics.go
сгенерированный
поставляемый
Обычный файл
322
vendor/github.com/splitio/go-split-commons/v3/storage/redis/metrics.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,322 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
)
|
||||
|
||||
// MetricsStorage is a redis-based implementation of split storage
|
||||
type MetricsStorage struct {
|
||||
client redis.PrefixedRedisClient
|
||||
logger logging.LoggerInterface
|
||||
gaugeSingleTemplate string
|
||||
countersSingleTemplate string
|
||||
latenciesSingleTemplate string
|
||||
gaugeMultiTemplate string
|
||||
countersMultiTemplate string
|
||||
latenciesMultiTemplate string
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMetricsStorage creates a new RedisSplitStorage and returns a reference to it
|
||||
func NewMetricsStorage(redisClient *redis.PrefixedRedisClient, metadata dtos.Metadata, logger logging.LoggerInterface) *MetricsStorage {
|
||||
// @Todo Split Storages between Go-Client and Redis
|
||||
gaugeSingleTemplate := strings.Replace(redisGauge, "{sdkVersion}", metadata.SDKVersion, 1)
|
||||
gaugeSingleTemplate = strings.Replace(gaugeSingleTemplate, "{instanceId}", metadata.MachineName, 1)
|
||||
countersSingleTemplate := strings.Replace(redisCounter, "{sdkVersion}", metadata.SDKVersion, 1)
|
||||
countersSingleTemplate = strings.Replace(countersSingleTemplate, "{instanceId}", metadata.MachineName, 1)
|
||||
latenciesSingleTemplate := strings.Replace(redisLatency, "{sdkVersion}", metadata.SDKVersion, 1)
|
||||
latenciesSingleTemplate = strings.Replace(latenciesSingleTemplate, "{instanceId}", metadata.MachineName, 1)
|
||||
|
||||
gaugeMultiTemplate := strings.Replace(redisGauge, "{sdkVersion}", "*", 1)
|
||||
gaugeMultiTemplate = strings.Replace(gaugeMultiTemplate, "{instanceId}", "*", 1)
|
||||
gaugeMultiTemplate = strings.Replace(gaugeMultiTemplate, "{metric}", "*", 1)
|
||||
countersMultiTemplate := strings.Replace(redisCounter, "{sdkVersion}", "*", 1)
|
||||
countersMultiTemplate = strings.Replace(countersMultiTemplate, "{instanceId}", "*", 1)
|
||||
countersMultiTemplate = strings.Replace(countersMultiTemplate, "{metric}", "*", 1)
|
||||
latenciesMultiTemplate := strings.Replace(redisLatency, "{sdkVersion}", "*", 1)
|
||||
latenciesMultiTemplate = strings.Replace(latenciesMultiTemplate, "{instanceId}", "*", 1)
|
||||
latenciesMultiTemplate = strings.Replace(latenciesMultiTemplate, "{metric}", "*", 1)
|
||||
latenciesMultiTemplate = strings.Replace(latenciesMultiTemplate, "{bucket}", "*", 1)
|
||||
|
||||
return &MetricsStorage{
|
||||
client: *redisClient,
|
||||
logger: logger,
|
||||
gaugeSingleTemplate: gaugeSingleTemplate,
|
||||
countersSingleTemplate: countersSingleTemplate,
|
||||
latenciesSingleTemplate: latenciesSingleTemplate,
|
||||
gaugeMultiTemplate: gaugeMultiTemplate,
|
||||
countersMultiTemplate: countersMultiTemplate,
|
||||
latenciesMultiTemplate: latenciesMultiTemplate,
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// IncCounter incraeses the count for a specific metric
|
||||
func (r *MetricsStorage) IncCounter(metric string) {
|
||||
keyToIncr := strings.Replace(r.countersSingleTemplate, "{metric}", metric, 1)
|
||||
_, err := r.client.Incr(keyToIncr)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error incrementing counterfor metric \"%s\" in redis: %s", metric, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// IncLatency incraeses the latency of a bucket for a specific metric
|
||||
func (r *MetricsStorage) IncLatency(metric string, index int) {
|
||||
keyToIncr := strings.Replace(r.latenciesSingleTemplate, "{metric}", metric, 1)
|
||||
keyToIncr = strings.Replace(keyToIncr, "{bucket}", strconv.FormatInt(int64(index), 10), 1)
|
||||
_, err := r.client.Incr(keyToIncr)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf(
|
||||
"Error incrementing latency bucket %d for metric \"%s\" in redis: %s", index, metric, err.Error(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// PutGauge stores a gauge in redis
|
||||
func (r *MetricsStorage) PutGauge(key string, gauge float64) {
|
||||
keyToStore := strings.Replace(r.gaugeSingleTemplate, "{metric}", key, 1)
|
||||
err := r.client.Set(keyToStore, gauge, 0)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error storing gauge \"%s\" in redis: %s\n", key, err))
|
||||
}
|
||||
}
|
||||
|
||||
// PopCounters some
|
||||
func (r *MetricsStorage) PopCounters() []dtos.CounterDTO {
|
||||
panic("Not implemented for redis")
|
||||
}
|
||||
|
||||
// PopGauges some
|
||||
func (r *MetricsStorage) PopGauges() []dtos.GaugeDTO {
|
||||
panic("Not implemented for redis")
|
||||
}
|
||||
|
||||
// PopLatencies some
|
||||
func (r *MetricsStorage) PopLatencies() []dtos.LatenciesDTO {
|
||||
panic("Not implemented for redis")
|
||||
}
|
||||
|
||||
func (r *MetricsStorage) popByPattern(pattern string, useTransaction bool) (map[string]interface{}, error) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
keys, err := r.client.Keys(pattern)
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
values, err := r.client.MGet(keys)
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
_, err = r.client.Del(keys...)
|
||||
if err != nil {
|
||||
// if we failed to delete the keys, log an error and continue working.
|
||||
r.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
toReturn := make(map[string]interface{})
|
||||
for index := range keys {
|
||||
if index >= len(keys) || index >= len(values) {
|
||||
break
|
||||
}
|
||||
toReturn[keys[index]] = values[index]
|
||||
}
|
||||
return toReturn, nil
|
||||
|
||||
}
|
||||
|
||||
func parseIntRedisValue(s interface{}) (int64, error) {
|
||||
asStr, ok := s.(string)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("%+v is not a string", s)
|
||||
}
|
||||
|
||||
asInt64, err := strconv.ParseInt(asStr, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return asInt64, nil
|
||||
}
|
||||
|
||||
func parseFloatRedisValue(s interface{}) (float64, error) {
|
||||
asStr, ok := s.(string)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("%+v is not a string", s)
|
||||
}
|
||||
|
||||
asFloat64, err := strconv.ParseFloat(asStr, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return asFloat64, nil
|
||||
}
|
||||
|
||||
func (r *MetricsStorage) parseLatencyKey(key string) (string, string, string, int, error) {
|
||||
re := regexp.MustCompile(`(\w+.)?SPLITIO\/([^\/]+)\/([^\/]+)\/latency.([^\/]+).bucket.([0-9]*)`)
|
||||
match := re.FindStringSubmatch(key)
|
||||
|
||||
if len(match) < 6 {
|
||||
return "", "", "", 0, fmt.Errorf("Error parsing key %s", key)
|
||||
}
|
||||
|
||||
sdkNameAndVersion := match[2]
|
||||
if sdkNameAndVersion == "" {
|
||||
return "", "", "", 0, fmt.Errorf("Invalid sdk name/version")
|
||||
}
|
||||
|
||||
machineIP := match[3]
|
||||
if machineIP == "" {
|
||||
return "", "", "", 0, fmt.Errorf("Invalid machine IP")
|
||||
}
|
||||
|
||||
metricName := match[4]
|
||||
if metricName == "" {
|
||||
return "", "", "", 0, fmt.Errorf("Invalid feature name")
|
||||
}
|
||||
|
||||
bucketNumber, err := strconv.Atoi(match[5])
|
||||
if err != nil {
|
||||
return "", "", "", 0, fmt.Errorf("Error parsing bucket number: %s", err.Error())
|
||||
}
|
||||
r.logger.Verbose("Impression parsed key", match)
|
||||
|
||||
return sdkNameAndVersion, machineIP, metricName, bucketNumber, nil
|
||||
}
|
||||
|
||||
func (r *MetricsStorage) parseMetricKey(metricType string, key string) (string, string, string, error) {
|
||||
var re = regexp.MustCompile(strings.Replace(
|
||||
`(\w+.)?SPLITIO\/([^\/]+)\/([^\/]+)\/{metricType}.([\s\S]*)`,
|
||||
"{metricType}",
|
||||
metricType,
|
||||
1,
|
||||
))
|
||||
match := re.FindStringSubmatch(key)
|
||||
|
||||
if len(match) < 5 {
|
||||
return "", "", "", fmt.Errorf("Error parsing key %s", key)
|
||||
}
|
||||
|
||||
sdkNameAndVersion := match[2]
|
||||
if sdkNameAndVersion == "" {
|
||||
return "", "", "", fmt.Errorf("Invalid sdk name/version")
|
||||
}
|
||||
|
||||
machineIP := match[3]
|
||||
if machineIP == "" {
|
||||
return "", "", "", fmt.Errorf("Invalid machine IP")
|
||||
}
|
||||
|
||||
metricName := match[4]
|
||||
if metricName == "" {
|
||||
return "", "", "", fmt.Errorf("Invalid feature name")
|
||||
}
|
||||
|
||||
r.logger.Verbose("Impression parsed key", match)
|
||||
|
||||
return sdkNameAndVersion, machineIP, metricName, nil
|
||||
}
|
||||
|
||||
// PopGaugesWithMetadata returns gauges values saved in Redis by SDKs
|
||||
func (r *MetricsStorage) PopGaugesWithMetadata() (*dtos.GaugeDataBulk, error) {
|
||||
data, err := r.popByPattern(r.gaugeMultiTemplate, false)
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gaugesToReturn := dtos.NewGaugeDataBulk()
|
||||
for key, value := range data {
|
||||
sdkNameAndVersion, machineIP, metricName, err := r.parseMetricKey("gauge", key)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Unable to parse key %s. Skipping", key))
|
||||
continue
|
||||
}
|
||||
asFloat, err := parseFloatRedisValue(value)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Unable to parse value %+v. Skipping", value))
|
||||
continue
|
||||
}
|
||||
gaugesToReturn.PutGauge(sdkNameAndVersion, machineIP, metricName, asFloat)
|
||||
}
|
||||
|
||||
return gaugesToReturn, nil
|
||||
}
|
||||
|
||||
// PopCountersWithMetadata returns counter values saved in Redis by SDKs
|
||||
func (r *MetricsStorage) PopCountersWithMetadata() (*dtos.CounterDataBulk, error) {
|
||||
data, err := r.popByPattern(r.countersMultiTemplate, false)
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
countersToReturn := dtos.NewCounterDataBulk()
|
||||
for key, value := range data {
|
||||
sdkNameAndVersion, machineIP, metricName, err := r.parseMetricKey("count", key)
|
||||
if err != nil {
|
||||
r.logger.Error("Unable to parse key %s. Skipping", key)
|
||||
continue
|
||||
}
|
||||
asInt, err := parseIntRedisValue(value)
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
countersToReturn.PutCounter(sdkNameAndVersion, machineIP, metricName, asInt)
|
||||
}
|
||||
|
||||
return countersToReturn, nil
|
||||
}
|
||||
|
||||
// PopLatenciesWithMetadata returns latency values saved in Redis by SDKs
|
||||
func (r *MetricsStorage) PopLatenciesWithMetadata() (*dtos.LatencyDataBulk, error) {
|
||||
data, err := r.popByPattern(r.latenciesMultiTemplate, false)
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latenciesToReturn := dtos.NewLatencyDataBulk()
|
||||
for key, value := range data {
|
||||
value, err := parseIntRedisValue(value)
|
||||
if err != nil {
|
||||
r.logger.Warning(fmt.Sprintf("Unable to parse value of key %s. Skipping", key))
|
||||
continue
|
||||
}
|
||||
sdkNameAndVersion, machineIP, metricName, bucketNumber, err := r.parseLatencyKey(key)
|
||||
if err != nil {
|
||||
r.logger.Warning(fmt.Sprintf("Unable to parse key %s. Skipping", key))
|
||||
continue
|
||||
}
|
||||
latenciesToReturn.PutLatency(sdkNameAndVersion, machineIP, metricName, bucketNumber, value)
|
||||
}
|
||||
r.logger.Verbose(latenciesToReturn)
|
||||
return latenciesToReturn, nil
|
||||
}
|
||||
|
||||
// PeekCounters returns Counters
|
||||
func (r *MetricsStorage) PeekCounters() map[string]int64 {
|
||||
return make(map[string]int64, 0)
|
||||
}
|
||||
|
||||
// PeekLatencies returns Latencies
|
||||
func (r *MetricsStorage) PeekLatencies() map[string][]int64 {
|
||||
return make(map[string][]int64, 0)
|
||||
}
|
||||
56
vendor/github.com/splitio/go-split-commons/v3/storage/redis/miscstorage.go
сгенерированный
поставляемый
Обычный файл
56
vendor/github.com/splitio/go-split-commons/v3/storage/redis/miscstorage.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,56 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
)
|
||||
|
||||
// ErrorHashNotPresent constant
|
||||
const ErrorHashNotPresent = "hash-not-present"
|
||||
|
||||
const clearAllSCriptTemplate = `
|
||||
local toDelete = redis.call('KEYS', '{KEY_NAMESPACE}*')
|
||||
local count = 0
|
||||
for _, key in ipairs(toDelete) do
|
||||
redis.call('DEL', key)
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
`
|
||||
|
||||
// MiscStorage provides methods to handle the synchronizer's initialization procedure
|
||||
type MiscStorage struct {
|
||||
client *redis.PrefixedRedisClient
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// GetApikeyHash gets hashed apikey from redis
|
||||
func (m *MiscStorage) GetApikeyHash() (string, error) {
|
||||
res, err := m.client.Get(redisHash)
|
||||
if err != nil && err.Error() == "redis: nil" {
|
||||
return "", errors.New(ErrorHashNotPresent)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// SetApikeyHash sets hashed apikey in redis
|
||||
func (m *MiscStorage) SetApikeyHash(newApikeyHash string) error {
|
||||
return m.client.Set(redisHash, newApikeyHash, 0)
|
||||
}
|
||||
|
||||
// ClearAll cleans previous used data
|
||||
func (m *MiscStorage) ClearAll() error {
|
||||
luaCMD := strings.Replace(clearAllSCriptTemplate, "{KEY_NAMESPACE}", m.client.Prefix, 1)
|
||||
return m.client.Eval(luaCMD, []string{}, nil)
|
||||
}
|
||||
|
||||
// NewMiscStorage creates a new MiscStorageAdapter and returns a reference to it
|
||||
func NewMiscStorage(client *redis.PrefixedRedisClient, logger logging.LoggerInterface) *MiscStorage {
|
||||
return &MiscStorage{
|
||||
client: client,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
74
vendor/github.com/splitio/go-split-commons/v3/storage/redis/redis.go
сгенерированный
поставляемый
Обычный файл
74
vendor/github.com/splitio/go-split-commons/v3/storage/redis/redis.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,74 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
"github.com/splitio/go-toolkit/v4/redis/helpers"
|
||||
)
|
||||
|
||||
// NewRedisClient returns a new Prefixed Redis Client
|
||||
func NewRedisClient(config *conf.RedisConfig, logger logging.LoggerInterface) (*redis.PrefixedRedisClient, error) {
|
||||
prefix := config.Prefix
|
||||
|
||||
if len(config.SentinelAddresses) > 0 && len(config.ClusterNodes) > 0 {
|
||||
return nil, errors.New("Incompatible configuration of redis, Sentinel and Cluster cannot be enabled at the same time")
|
||||
}
|
||||
|
||||
universalOptions := &redis.UniversalOptions{
|
||||
Password: config.Password,
|
||||
DB: config.Database,
|
||||
TLSConfig: config.TLSConfig,
|
||||
MaxRetries: config.MaxRetries,
|
||||
PoolSize: config.PoolSize,
|
||||
DialTimeout: time.Duration(config.DialTimeout) * time.Second,
|
||||
ReadTimeout: time.Duration(config.ReadTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(config.WriteTimeout) * time.Second,
|
||||
}
|
||||
|
||||
if len(config.SentinelAddresses) > 0 {
|
||||
logger.Info("To start as Sentinel Mode")
|
||||
if config.SentinelMaster == "" {
|
||||
return nil, errors.New("Missing redis sentinel master name")
|
||||
}
|
||||
|
||||
universalOptions.MasterName = config.SentinelMaster
|
||||
universalOptions.Addrs = config.SentinelAddresses
|
||||
} else {
|
||||
if len(config.ClusterNodes) > 0 {
|
||||
logger.Info("To start as Cluster Mode")
|
||||
var keyHashTag = "{SPLITIO}"
|
||||
|
||||
if config.ClusterKeyHashTag != "" {
|
||||
keyHashTag = config.ClusterKeyHashTag
|
||||
if len(keyHashTag) < 3 ||
|
||||
string(keyHashTag[0]) != "{" ||
|
||||
string(keyHashTag[len(keyHashTag)-1]) != "}" ||
|
||||
strings.Count(keyHashTag, "{") != 1 ||
|
||||
strings.Count(keyHashTag, "}") != 1 {
|
||||
return nil, errors.New("keyHashTag is not valid")
|
||||
}
|
||||
}
|
||||
|
||||
prefix = keyHashTag + prefix
|
||||
universalOptions.Addrs = config.ClusterNodes
|
||||
} else {
|
||||
logger.Info("To start as Single Mode")
|
||||
universalOptions.Addrs = []string{fmt.Sprintf("%s:%d", config.Host, config.Port)}
|
||||
}
|
||||
}
|
||||
|
||||
rClient, err := redis.NewClient(universalOptions)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
helpers.EnsureConnected(rClient)
|
||||
|
||||
return redis.NewPrefixedRedisClient(rClient, prefix)
|
||||
}
|
||||
100
vendor/github.com/splitio/go-split-commons/v3/storage/redis/segments.go
сгенерированный
поставляемый
Обычный файл
100
vendor/github.com/splitio/go-split-commons/v3/storage/redis/segments.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,100 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
)
|
||||
|
||||
// SegmentStorage is a redis implementation of a storage for segments
|
||||
type SegmentStorage struct {
|
||||
client redis.PrefixedRedisClient
|
||||
logger logging.LoggerInterface
|
||||
mutext *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewSegmentStorage creates a new RedisSegmentStorage and returns a reference to it
|
||||
func NewSegmentStorage(redisClient *redis.PrefixedRedisClient, logger logging.LoggerInterface) *SegmentStorage {
|
||||
return &SegmentStorage{
|
||||
client: *redisClient,
|
||||
logger: logger,
|
||||
mutext: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeNumber returns the changeNumber for a particular segment
|
||||
func (r *SegmentStorage) ChangeNumber(segmentName string) (int64, error) {
|
||||
segmentKey := strings.Replace(redisSegmentTill, "{segment}", segmentName, 1)
|
||||
tillStr, err := r.client.Get(segmentKey)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
asInt, err := strconv.ParseInt(tillStr, 10, 64)
|
||||
if err != nil {
|
||||
r.logger.Error("Error retrieving till. Returning -1: ", err.Error())
|
||||
return -1, err
|
||||
}
|
||||
return asInt, nil
|
||||
}
|
||||
|
||||
// Keys returns segments keys for segment if it's present
|
||||
func (r *SegmentStorage) Keys(segmentName string) *set.ThreadUnsafeSet {
|
||||
keyToFetch := strings.Replace(redisSegment, "{segment}", segmentName, 1)
|
||||
segmentKeys, err := r.client.SMembers(keyToFetch)
|
||||
if len(segmentKeys) <= 0 {
|
||||
r.logger.Debug(fmt.Sprintf("Nonexsitent segment requested: %s", segmentName))
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error retrieving members from set %s", segmentName))
|
||||
return nil
|
||||
}
|
||||
segment := set.NewSet()
|
||||
for _, member := range segmentKeys {
|
||||
segment.Add(member)
|
||||
}
|
||||
return segment
|
||||
}
|
||||
|
||||
// SetChangeNumber sets the till value belong to segmentName
|
||||
func (r *SegmentStorage) SetChangeNumber(segmentName string, changeNumber int64) error {
|
||||
segmentKey := strings.Replace(redisSegmentTill, "{segment}", segmentName, 1)
|
||||
return r.client.Set(segmentKey, changeNumber, 0)
|
||||
}
|
||||
|
||||
// Update adds a new segment
|
||||
func (r *SegmentStorage) Update(name string, toAdd *set.ThreadUnsafeSet, toRemove *set.ThreadUnsafeSet, till int64) error {
|
||||
r.mutext.Lock()
|
||||
defer r.mutext.Unlock()
|
||||
segmentKey := strings.Replace(redisSegment, "{segment}", name, 1)
|
||||
if !toRemove.IsEmpty() {
|
||||
_, err := r.client.SRem(segmentKey, toRemove.List()...)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error removing keys in redis: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
if !toAdd.IsEmpty() {
|
||||
_, err := r.client.SAdd(segmentKey, toAdd.List()...)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error removing keys in redis: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
r.SetChangeNumber(name, till)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SegmentContainsKey returns true if the segment contains a specific key
|
||||
func (r *SegmentStorage) SegmentContainsKey(segmentName string, key string) (bool, error) {
|
||||
segmentKey := strings.Replace(redisSegment, "{segment}", segmentName, 1)
|
||||
exists := r.client.SIsMember(segmentKey, key)
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// CountRemovedKeys method
|
||||
func (r *SegmentStorage) CountRemovedKeys(segmentName string) int64 { return 0 }
|
||||
261
vendor/github.com/splitio/go-split-commons/v3/storage/redis/splits.go
сгенерированный
поставляемый
Обычный файл
261
vendor/github.com/splitio/go-split-commons/v3/storage/redis/splits.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,261 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/redis"
|
||||
)
|
||||
|
||||
// SplitStorage is a redis-based implementation of split storage
|
||||
type SplitStorage struct {
|
||||
client *redis.PrefixedRedisClient
|
||||
logger logging.LoggerInterface
|
||||
mutext *sync.RWMutex
|
||||
}
|
||||
|
||||
// NewSplitStorage creates a new RedisSplitStorage and returns a reference to it
|
||||
func NewSplitStorage(redisClient *redis.PrefixedRedisClient, logger logging.LoggerInterface) *SplitStorage {
|
||||
return &SplitStorage{
|
||||
client: redisClient,
|
||||
logger: logger,
|
||||
mutext: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// All returns a slice of splits dtos.
|
||||
func (r *SplitStorage) All() []dtos.SplitDTO {
|
||||
splits := make([]dtos.SplitDTO, 0)
|
||||
keyPattern := strings.Replace(redisSplit, "{split}", "*", 1)
|
||||
keys, err := r.client.Keys(keyPattern)
|
||||
if err != nil {
|
||||
r.logger.Error("Error fetching split keys. Returning empty split list")
|
||||
return splits
|
||||
}
|
||||
|
||||
rawSplits, err := r.client.MGet(keys)
|
||||
if err != nil {
|
||||
r.logger.Error("Could not get splits")
|
||||
return splits
|
||||
}
|
||||
for idx, raw := range rawSplits {
|
||||
var split dtos.SplitDTO
|
||||
rawSplit, ok := rawSplits[idx].(string)
|
||||
if ok {
|
||||
err = json.Unmarshal([]byte(rawSplit), &split)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error parsing json for split %s", raw))
|
||||
continue
|
||||
}
|
||||
}
|
||||
splits = append(splits, split)
|
||||
}
|
||||
|
||||
return splits
|
||||
}
|
||||
|
||||
// ChangeNumber returns the latest split changeNumber
|
||||
func (r *SplitStorage) ChangeNumber() (int64, error) {
|
||||
val, err := r.client.Get(redisSplitTill)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
asInt, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
r.logger.Error("Could not parse Till value from redis")
|
||||
return -1, err
|
||||
}
|
||||
return asInt, nil
|
||||
}
|
||||
|
||||
// FetchMany retrieves features from redis storage
|
||||
func (r *SplitStorage) FetchMany(features []string) map[string]*dtos.SplitDTO {
|
||||
keysToFetch := make([]string, 0)
|
||||
for _, feature := range features {
|
||||
keysToFetch = append(keysToFetch, strings.Replace(redisSplit, "{split}", feature, 1))
|
||||
}
|
||||
rawSplits, err := r.client.MGet(keysToFetch)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Could not fetch features from redis: %s", err.Error()))
|
||||
return nil
|
||||
}
|
||||
|
||||
splits := make(map[string]*dtos.SplitDTO)
|
||||
for idx, feature := range features {
|
||||
var split *dtos.SplitDTO
|
||||
rawSplit, ok := rawSplits[idx].(string)
|
||||
if ok {
|
||||
err = json.Unmarshal([]byte(rawSplit), &split)
|
||||
if err != nil {
|
||||
r.logger.Error("Could not parse feature \"%s\" fetched from redis", feature)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
splits[feature] = split
|
||||
}
|
||||
|
||||
return splits
|
||||
}
|
||||
|
||||
// KillLocally mock
|
||||
func (r *SplitStorage) KillLocally(splitName string, defaultTreatment string, changeNumber int64) {
|
||||
// @TODO Implement for Sync
|
||||
}
|
||||
|
||||
// incr stores/increments trafficType in Redis
|
||||
func (r *SplitStorage) incr(trafficType string) error {
|
||||
key := strings.Replace(redisTrafficType, "{trafficType}", trafficType, 1)
|
||||
|
||||
_, err := r.client.Incr(key)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error storing trafficType %s in redis", trafficType))
|
||||
r.logger.Error(err)
|
||||
return errors.New("Error incrementing trafficType")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decr decrements trafficType count in Redis
|
||||
func (r *SplitStorage) decr(trafficType string) error {
|
||||
key := strings.Replace(redisTrafficType, "{trafficType}", trafficType, 1)
|
||||
|
||||
val, _ := r.client.Decr(key)
|
||||
if val <= 0 {
|
||||
_, err := r.client.Del(key)
|
||||
if err != nil {
|
||||
r.logger.Verbose(fmt.Sprintf("Error removing trafficType %s in redis", trafficType))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutMany bulk stores splits in redis
|
||||
func (r *SplitStorage) PutMany(splits []dtos.SplitDTO, changeNumber int64) {
|
||||
r.mutext.Lock()
|
||||
defer r.mutext.Unlock()
|
||||
for _, split := range splits {
|
||||
keyToStore := strings.Replace(redisSplit, "{split}", split.Name, 1)
|
||||
raw, err := json.Marshal(split)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Could not dump feature \"%s\" to json", split.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
existing := r.Split(split.Name)
|
||||
if existing != nil {
|
||||
// If it's an update, we decrement the traffic type count of the existing split,
|
||||
// and then add the updated one (as part of the normal flow), in case it's different.
|
||||
r.decr(existing.TrafficTypeName)
|
||||
}
|
||||
|
||||
r.incr(split.TrafficTypeName)
|
||||
|
||||
err = r.client.Set(keyToStore, raw, 0)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Could not store split \"%s\" in redis: %s", split.Name, err.Error()))
|
||||
}
|
||||
}
|
||||
err := r.client.Set(redisSplitTill, changeNumber, 0)
|
||||
if err != nil {
|
||||
r.logger.Error("Could not update split changenumber")
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes split item from redis
|
||||
func (r *SplitStorage) Remove(splitName string) {
|
||||
r.mutext.Lock()
|
||||
defer r.mutext.Unlock()
|
||||
keyToDelete := strings.Replace(redisSplit, "{split}", splitName, 1)
|
||||
existing := r.Split(splitName)
|
||||
if existing == nil {
|
||||
r.logger.Warning("Tried to delete split " + splitName + " which doesn't exist. ignoring")
|
||||
return
|
||||
}
|
||||
r.decr(existing.TrafficTypeName)
|
||||
_, err := r.client.Del(keyToDelete)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Error deleting split \"%s\".", splitName))
|
||||
}
|
||||
}
|
||||
|
||||
// SegmentNames returns a slice of strings with all the segment names
|
||||
func (r *SplitStorage) SegmentNames() *set.ThreadUnsafeSet {
|
||||
segmentNames := set.NewSet()
|
||||
splits := r.All()
|
||||
|
||||
for _, split := range splits {
|
||||
for _, condition := range split.Conditions {
|
||||
for _, matcher := range condition.MatcherGroup.Matchers {
|
||||
if matcher.UserDefinedSegment != nil {
|
||||
segmentNames.Add(matcher.UserDefinedSegment.SegmentName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return segmentNames
|
||||
}
|
||||
|
||||
// SetChangeNumber sets the till value belong to segmentName
|
||||
func (r *SplitStorage) SetChangeNumber(changeNumber int64) error {
|
||||
return r.client.Set(redisSplitTill, changeNumber, 0)
|
||||
}
|
||||
|
||||
// Split fetches a feature in redis and returns a pointer to a split dto
|
||||
func (r *SplitStorage) Split(feature string) *dtos.SplitDTO {
|
||||
keyToFetch := strings.Replace(redisSplit, "{split}", feature, 1)
|
||||
val, err := r.client.Get(keyToFetch)
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Could not fetch feature %s from redis: %s", feature, err.Error()))
|
||||
return nil
|
||||
}
|
||||
|
||||
var split dtos.SplitDTO
|
||||
err = json.Unmarshal([]byte(val), &split)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Could not parse feature %s fetched from redis", feature))
|
||||
return nil
|
||||
}
|
||||
|
||||
return &split
|
||||
}
|
||||
|
||||
// SplitNames returns a slice of strings with all the split names
|
||||
func (r *SplitStorage) SplitNames() []string {
|
||||
splitNames := make([]string, 0)
|
||||
keyPattern := strings.Replace(redisSplit, "{split}", "*", 1)
|
||||
keys, err := r.client.Keys(keyPattern)
|
||||
if err == nil {
|
||||
toRemove := strings.Replace(redisSplit, "{split}", "", 1) // Create a string with all the prefix to remove
|
||||
for _, key := range keys {
|
||||
splitNames = append(splitNames, strings.Replace(key, toRemove, "", 1)) // Extract split name from key
|
||||
}
|
||||
}
|
||||
return splitNames
|
||||
}
|
||||
|
||||
// TrafficTypeExists returns true or false depending on existence and counter
|
||||
// of trafficType
|
||||
func (r *SplitStorage) TrafficTypeExists(trafficType string) bool {
|
||||
keyToFetch := strings.Replace(redisTrafficType, "{trafficType}", trafficType, 1)
|
||||
res, err := r.client.Get(keyToFetch)
|
||||
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Could not fetch trafficType \"%s\" from redis: %s", trafficType, err.Error()))
|
||||
return false
|
||||
}
|
||||
|
||||
val, err := strconv.ParseInt(res, 10, 64)
|
||||
if err != nil {
|
||||
r.logger.Error("TrafficType could not be converted")
|
||||
return false
|
||||
}
|
||||
return val > 0
|
||||
}
|
||||
13
vendor/github.com/splitio/go-split-commons/v3/synchronizer/interface.go
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/splitio/go-split-commons/v3/synchronizer/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,13 @@
|
||||
package synchronizer
|
||||
|
||||
// Synchronizer interface for syncing data to and from splits servers
|
||||
type Synchronizer interface {
|
||||
SyncAll(requestNoCache bool) error
|
||||
SynchronizeSplits(till *int64, requestNoCache bool) error
|
||||
LocalKill(splitName string, defaultTreatment string, changeNumber int64)
|
||||
SynchronizeSegment(segmentName string, till *int64, requestNoCache bool) error
|
||||
StartPeriodicFetching()
|
||||
StopPeriodicFetching()
|
||||
StartPeriodicDataRecording()
|
||||
StopPeriodicDataRecording()
|
||||
}
|
||||
86
vendor/github.com/splitio/go-split-commons/v3/synchronizer/local.go
сгенерированный
поставляемый
Обычный файл
86
vendor/github.com/splitio/go-split-commons/v3/synchronizer/local.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,86 @@
|
||||
package synchronizer
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/storage"
|
||||
storageMock "github.com/splitio/go-split-commons/v3/storage/mocks"
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/split"
|
||||
"github.com/splitio/go-split-commons/v3/tasks"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// Local implements Local Synchronizer
|
||||
type Local struct {
|
||||
splitTasks SplitTasks
|
||||
workers Workers
|
||||
logger logging.LoggerInterface
|
||||
inMememoryFullQueue chan string
|
||||
}
|
||||
|
||||
// NewLocal creates new Local
|
||||
func NewLocal(
|
||||
period int,
|
||||
splitAPI *service.SplitAPI,
|
||||
splitStorage storage.SplitStorage,
|
||||
logger logging.LoggerInterface,
|
||||
) Synchronizer {
|
||||
metricStorageMock := storageMock.MockMetricStorage{
|
||||
IncCounterCall: func(key string) {},
|
||||
IncLatencyCall: func(metricName string, index int) {},
|
||||
PopCountersCall: func() []dtos.CounterDTO { return make([]dtos.CounterDTO, 0, 0) },
|
||||
PopGaugesCall: func() []dtos.GaugeDTO { return make([]dtos.GaugeDTO, 0, 0) },
|
||||
PopLatenciesCall: func() []dtos.LatenciesDTO { return make([]dtos.LatenciesDTO, 0, 0) },
|
||||
PutGaugeCall: func(key string, gauge float64) {},
|
||||
}
|
||||
metricsWrapper := storage.NewMetricWrapper(metricStorageMock, nil, logger)
|
||||
workers := Workers{
|
||||
SplitFetcher: split.NewSplitFetcher(splitStorage, splitAPI.SplitFetcher, metricsWrapper, logger),
|
||||
}
|
||||
return &Local{
|
||||
splitTasks: SplitTasks{
|
||||
SplitSyncTask: tasks.NewFetchSplitsTask(workers.SplitFetcher, period, logger),
|
||||
},
|
||||
workers: workers,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SyncAll syncs splits and segments
|
||||
func (s *Local) SyncAll(requestNoCache bool) error {
|
||||
_, err := s.workers.SplitFetcher.SynchronizeSplits(nil, requestNoCache)
|
||||
return err
|
||||
}
|
||||
|
||||
// StartPeriodicFetching starts periodic fetchers tasks
|
||||
func (s *Local) StartPeriodicFetching() {
|
||||
s.splitTasks.SplitSyncTask.Start()
|
||||
}
|
||||
|
||||
// StopPeriodicFetching stops periodic fetchers tasks
|
||||
func (s *Local) StopPeriodicFetching() {
|
||||
s.splitTasks.SplitSyncTask.Stop(false)
|
||||
}
|
||||
|
||||
// StartPeriodicDataRecording starts periodic recorders tasks
|
||||
func (s *Local) StartPeriodicDataRecording() {
|
||||
}
|
||||
|
||||
// StopPeriodicDataRecording stops periodic recorders tasks
|
||||
func (s *Local) StopPeriodicDataRecording() {
|
||||
}
|
||||
|
||||
// SynchronizeSplits syncs splits
|
||||
func (s *Local) SynchronizeSplits(till *int64, requestNoCache bool) error {
|
||||
_, err := s.workers.SplitFetcher.SynchronizeSplits(nil, requestNoCache)
|
||||
return err
|
||||
}
|
||||
|
||||
// SynchronizeSegment syncs segment
|
||||
func (s *Local) SynchronizeSegment(name string, till *int64, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalKill does nothing
|
||||
func (s *Local) LocalKill(splitName string, defaultTreatment string, changeNumber int64) {
|
||||
}
|
||||
207
vendor/github.com/splitio/go-split-commons/v3/synchronizer/manager.go
сгенерированный
поставляемый
Обычный файл
207
vendor/github.com/splitio/go-split-commons/v3/synchronizer/manager.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,207 @@
|
||||
package synchronizer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/push"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/storage"
|
||||
"github.com/splitio/go-toolkit/v4/backoff"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/struct/traits/lifecycle"
|
||||
)
|
||||
|
||||
const (
|
||||
// Ready represents ready
|
||||
Ready = iota
|
||||
// StreamingReady ready
|
||||
StreamingReady
|
||||
// Error represents some error in SSE streaming
|
||||
Error
|
||||
)
|
||||
|
||||
// Operation mode constants
|
||||
const (
|
||||
Streaming = iota
|
||||
Polling
|
||||
)
|
||||
|
||||
// Manager interface
|
||||
type Manager interface {
|
||||
Start()
|
||||
Stop()
|
||||
IsRunning() bool
|
||||
}
|
||||
|
||||
// ManagerImpl struct
|
||||
type ManagerImpl struct {
|
||||
synchronizer Synchronizer
|
||||
logger logging.LoggerInterface
|
||||
config conf.AdvancedConfig
|
||||
pushManager push.Manager
|
||||
managerStatus chan int
|
||||
streamingStatus chan int64
|
||||
operationMode int32
|
||||
lifecycle lifecycle.Manager
|
||||
backoff backoff.Interface
|
||||
}
|
||||
|
||||
// NewSynchronizerManager creates new sync manager
|
||||
func NewSynchronizerManager(
|
||||
synchronizer Synchronizer,
|
||||
logger logging.LoggerInterface,
|
||||
config conf.AdvancedConfig,
|
||||
authClient service.AuthClient,
|
||||
splitStorage storage.SplitStorage,
|
||||
managerStatus chan int,
|
||||
) (*ManagerImpl, error) {
|
||||
if managerStatus == nil || cap(managerStatus) < 1 {
|
||||
return nil, errors.New("Status channel cannot be nil nor having capacity")
|
||||
}
|
||||
|
||||
manager := &ManagerImpl{
|
||||
backoff: backoff.New(),
|
||||
synchronizer: synchronizer,
|
||||
logger: logger,
|
||||
config: config,
|
||||
managerStatus: managerStatus,
|
||||
}
|
||||
manager.lifecycle.Setup()
|
||||
if config.StreamingEnabled {
|
||||
streamingStatus := make(chan int64, 1000)
|
||||
pushManager, err := push.NewManager(logger, synchronizer, &config, streamingStatus, authClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manager.pushManager = pushManager
|
||||
manager.streamingStatus = streamingStatus
|
||||
}
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
// IsRunning returns true if is in Streaming or Polling
|
||||
func (s *ManagerImpl) IsRunning() bool {
|
||||
return s.lifecycle.IsRunning()
|
||||
}
|
||||
|
||||
// Start starts synchronization through Split
|
||||
func (s *ManagerImpl) Start() {
|
||||
if !s.lifecycle.BeginInitialization() {
|
||||
s.logger.Info("Manager is already running, skipping start")
|
||||
return
|
||||
}
|
||||
|
||||
// It's safe to drain the channel here, since it's guaranteed that the manager status is "starting"
|
||||
// push manager is still stopped
|
||||
for len(s.managerStatus) > 0 {
|
||||
<-s.managerStatus
|
||||
}
|
||||
err := s.synchronizer.SyncAll(false)
|
||||
if err != nil {
|
||||
defer s.lifecycle.ShutdownComplete()
|
||||
s.managerStatus <- Error
|
||||
return
|
||||
}
|
||||
|
||||
if !s.lifecycle.InitializationComplete() {
|
||||
defer s.lifecycle.ShutdownComplete()
|
||||
return
|
||||
}
|
||||
s.logger.Debug("SyncAll Ready")
|
||||
s.managerStatus <- Ready
|
||||
s.synchronizer.StartPeriodicDataRecording()
|
||||
|
||||
if !s.config.StreamingEnabled {
|
||||
s.logger.Info("SDK initialized in polling mode")
|
||||
s.startPolling()
|
||||
go func() { // create a goroutine that stops everything (the same way the streaming status watcher would)
|
||||
<-s.lifecycle.ShutdownRequested()
|
||||
s.stop()
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
// Start streaming
|
||||
s.logger.Info("SDK Initialized in streaming mode")
|
||||
s.pushManager.Start()
|
||||
go s.pushStatusWatcher()
|
||||
}
|
||||
|
||||
func (s *ManagerImpl) stop() {
|
||||
if s.pushManager != nil {
|
||||
s.pushManager.Stop()
|
||||
}
|
||||
s.synchronizer.StopPeriodicFetching()
|
||||
s.synchronizer.StopPeriodicDataRecording()
|
||||
s.lifecycle.ShutdownComplete()
|
||||
}
|
||||
|
||||
// Stop stop synchronizaation through Split
|
||||
func (s *ManagerImpl) Stop() {
|
||||
if !s.lifecycle.BeginShutdown() {
|
||||
s.logger.Info("sync manager not yet running, skipping shutdown.")
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Info("Stopping all synchronization tasks")
|
||||
s.lifecycle.AwaitShutdownComplete()
|
||||
}
|
||||
|
||||
func (s *ManagerImpl) pushStatusWatcher() {
|
||||
defer s.stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.lifecycle.ShutdownRequested():
|
||||
return
|
||||
case status := <-s.streamingStatus:
|
||||
switch status {
|
||||
case push.StatusUp:
|
||||
s.stopPolling()
|
||||
s.logger.Info("streaming up and running")
|
||||
s.enableStreaming()
|
||||
s.synchronizer.SyncAll(true)
|
||||
case push.StatusDown:
|
||||
s.logger.Info("streaming down, switchin to polling")
|
||||
s.synchronizer.SyncAll(false)
|
||||
s.pauseStreaming()
|
||||
s.startPolling()
|
||||
case push.StatusRetryableError:
|
||||
howLong := s.backoff.Next()
|
||||
s.logger.Error("retryable error in streaming subsystem. Switching to polling and retrying in ", howLong, " seconds")
|
||||
s.pushManager.Stop()
|
||||
s.synchronizer.SyncAll(false)
|
||||
s.startPolling()
|
||||
time.Sleep(howLong)
|
||||
s.pushManager.Start()
|
||||
case push.StatusNonRetryableError:
|
||||
s.logger.Error("non retryable error in streaming subsystem. Switching to polling until next SDK initialization")
|
||||
s.pushManager.Stop()
|
||||
s.synchronizer.SyncAll(false)
|
||||
s.startPolling()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ManagerImpl) startPolling() {
|
||||
atomic.StoreInt32(&s.operationMode, Polling)
|
||||
s.synchronizer.StartPeriodicFetching()
|
||||
}
|
||||
|
||||
func (s *ManagerImpl) stopPolling() {
|
||||
s.synchronizer.StopPeriodicFetching()
|
||||
}
|
||||
|
||||
func (s *ManagerImpl) pauseStreaming() {
|
||||
s.pushManager.StartWorkers()
|
||||
}
|
||||
|
||||
func (s *ManagerImpl) enableStreaming() {
|
||||
s.pushManager.StartWorkers()
|
||||
atomic.StoreInt32(&s.operationMode, Streaming)
|
||||
s.backoff.Reset()
|
||||
}
|
||||
179
vendor/github.com/splitio/go-split-commons/v3/synchronizer/synchronizer.go
сгенерированный
поставляемый
Обычный файл
179
vendor/github.com/splitio/go-split-commons/v3/synchronizer/synchronizer.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,179 @@
|
||||
package synchronizer
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/event"
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/impression"
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/impressionscount"
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/metric"
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/segment"
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/split"
|
||||
"github.com/splitio/go-split-commons/v3/tasks"
|
||||
"github.com/splitio/go-toolkit/v4/asynctask"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// SplitTasks struct for tasks
|
||||
type SplitTasks struct {
|
||||
SplitSyncTask *asynctask.AsyncTask
|
||||
SegmentSyncTask *asynctask.AsyncTask
|
||||
TelemetrySyncTask *asynctask.AsyncTask
|
||||
ImpressionSyncTask tasks.Task
|
||||
EventSyncTask tasks.Task
|
||||
ImpressionsCountSyncTask *asynctask.AsyncTask
|
||||
}
|
||||
|
||||
// Workers struct for workers
|
||||
type Workers struct {
|
||||
SplitFetcher split.Updater
|
||||
SegmentFetcher segment.Updater
|
||||
TelemetryRecorder metric.MetricRecorder
|
||||
ImpressionRecorder impression.ImpressionRecorder
|
||||
EventRecorder event.EventRecorder
|
||||
ImpressionsCountRecorder impressionscount.ImpressionsCountRecorder
|
||||
}
|
||||
|
||||
// SynchronizerImpl implements Synchronizer
|
||||
type SynchronizerImpl struct {
|
||||
splitTasks SplitTasks
|
||||
workers Workers
|
||||
logger logging.LoggerInterface
|
||||
inMememoryFullQueue chan string
|
||||
impressionBulkSize int64
|
||||
eventBulkSize int64
|
||||
}
|
||||
|
||||
// NewSynchronizer creates new SynchronizerImpl
|
||||
func NewSynchronizer(
|
||||
confAdvanced conf.AdvancedConfig,
|
||||
splitTasks SplitTasks,
|
||||
workers Workers,
|
||||
logger logging.LoggerInterface,
|
||||
inMememoryFullQueue chan string,
|
||||
) Synchronizer {
|
||||
return &SynchronizerImpl{
|
||||
impressionBulkSize: confAdvanced.ImpressionsBulkSize,
|
||||
eventBulkSize: confAdvanced.EventsBulkSize,
|
||||
splitTasks: splitTasks,
|
||||
workers: workers,
|
||||
logger: logger,
|
||||
inMememoryFullQueue: inMememoryFullQueue,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SynchronizerImpl) dataFlusher() {
|
||||
for true {
|
||||
msg := <-s.inMememoryFullQueue
|
||||
switch msg {
|
||||
case "EVENTS_FULL":
|
||||
s.logger.Debug("FLUSHING storage queue")
|
||||
err := s.workers.EventRecorder.SynchronizeEvents(s.eventBulkSize)
|
||||
if err != nil {
|
||||
s.logger.Error("Error flushing storage queue", err)
|
||||
}
|
||||
break
|
||||
case "IMPRESSIONS_FULL":
|
||||
s.logger.Debug("FLUSHING storage queue")
|
||||
err := s.workers.ImpressionRecorder.SynchronizeImpressions(s.impressionBulkSize)
|
||||
if err != nil {
|
||||
s.logger.Error("Error flushing storage queue", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SyncAll syncs splits and segments
|
||||
func (s *SynchronizerImpl) SyncAll(requestNoCache bool) error {
|
||||
_, err := s.workers.SplitFetcher.SynchronizeSplits(nil, requestNoCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.workers.SegmentFetcher.SynchronizeSegments(requestNoCache)
|
||||
}
|
||||
|
||||
// StartPeriodicFetching starts periodic fetchers tasks
|
||||
func (s *SynchronizerImpl) StartPeriodicFetching() {
|
||||
if s.splitTasks.SplitSyncTask != nil {
|
||||
s.splitTasks.SplitSyncTask.Start()
|
||||
}
|
||||
if s.splitTasks.SegmentSyncTask != nil {
|
||||
s.splitTasks.SegmentSyncTask.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// StopPeriodicFetching stops periodic fetchers tasks
|
||||
func (s *SynchronizerImpl) StopPeriodicFetching() {
|
||||
if s.splitTasks.SplitSyncTask != nil {
|
||||
s.splitTasks.SplitSyncTask.Stop(false)
|
||||
}
|
||||
if s.splitTasks.SegmentSyncTask != nil {
|
||||
s.splitTasks.SegmentSyncTask.Stop(true)
|
||||
}
|
||||
}
|
||||
|
||||
// StartPeriodicDataRecording starts periodic recorders tasks
|
||||
func (s *SynchronizerImpl) StartPeriodicDataRecording() {
|
||||
if s.inMememoryFullQueue != nil {
|
||||
go s.dataFlusher()
|
||||
}
|
||||
|
||||
if s.splitTasks.ImpressionSyncTask != nil {
|
||||
s.splitTasks.ImpressionSyncTask.Start()
|
||||
}
|
||||
if s.splitTasks.TelemetrySyncTask != nil {
|
||||
s.splitTasks.TelemetrySyncTask.Start()
|
||||
}
|
||||
if s.splitTasks.EventSyncTask != nil {
|
||||
s.splitTasks.EventSyncTask.Start()
|
||||
}
|
||||
if s.splitTasks.ImpressionsCountSyncTask != nil {
|
||||
s.splitTasks.ImpressionsCountSyncTask.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// StopPeriodicDataRecording stops periodic recorders tasks
|
||||
func (s *SynchronizerImpl) StopPeriodicDataRecording() {
|
||||
if s.splitTasks.ImpressionSyncTask != nil {
|
||||
s.splitTasks.ImpressionSyncTask.Stop(true)
|
||||
}
|
||||
if s.splitTasks.TelemetrySyncTask != nil {
|
||||
s.splitTasks.TelemetrySyncTask.Stop(false)
|
||||
}
|
||||
if s.splitTasks.EventSyncTask != nil {
|
||||
s.splitTasks.EventSyncTask.Stop(true)
|
||||
}
|
||||
if s.splitTasks.ImpressionsCountSyncTask != nil {
|
||||
s.splitTasks.ImpressionsCountSyncTask.Stop(true)
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeSplits syncs splits
|
||||
func (s *SynchronizerImpl) SynchronizeSplits(till *int64, requstNoCache bool) error {
|
||||
referencedSegments, err := s.workers.SplitFetcher.SynchronizeSplits(till, requstNoCache)
|
||||
for _, segment := range s.filterCachedSegments(referencedSegments) {
|
||||
go s.SynchronizeSegment(segment, nil, true) // send segment to workerpool (queue is bypassed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SynchronizerImpl) filterCachedSegments(segmentsReferenced []string) []string {
|
||||
toRet := make([]string, 0, len(segmentsReferenced))
|
||||
for _, name := range segmentsReferenced {
|
||||
if !s.workers.SegmentFetcher.IsSegmentCached(name) {
|
||||
toRet = append(toRet, name)
|
||||
}
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
// LocalKill locally kills a split
|
||||
func (s *SynchronizerImpl) LocalKill(splitName string, defaultTreatment string, changeNumber int64) {
|
||||
s.workers.SplitFetcher.LocalKill(splitName, defaultTreatment, changeNumber)
|
||||
}
|
||||
|
||||
// SynchronizeSegment syncs segment
|
||||
func (s *SynchronizerImpl) SynchronizeSegment(name string, till *int64, requstNoCache bool) error {
|
||||
return s.workers.SegmentFetcher.SynchronizeSegment(name, till, requstNoCache)
|
||||
}
|
||||
|
||||
var _ Synchronizer = &SynchronizerImpl{}
|
||||
7
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/event/interface.go
сгенерированный
поставляемый
Обычный файл
7
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/event/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,7 @@
|
||||
package event
|
||||
|
||||
// EventRecorder interface
|
||||
type EventRecorder interface {
|
||||
SynchronizeEvents(bulkSize int64) error
|
||||
FlushEvents(bulkSize int64) error
|
||||
}
|
||||
77
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/event/single.go
сгенерированный
поставляемый
Обычный файл
77
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/event/single.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,77 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/storage"
|
||||
"github.com/splitio/go-split-commons/v3/util"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// RecorderSingle struct for event sync
|
||||
type RecorderSingle struct {
|
||||
eventStorage storage.EventStorageConsumer
|
||||
eventRecorder service.EventsRecorder
|
||||
metricsWrapper *storage.MetricWrapper
|
||||
logger logging.LoggerInterface
|
||||
metadata dtos.Metadata
|
||||
}
|
||||
|
||||
// NewEventRecorderSingle creates new event synchronizer for posting events
|
||||
func NewEventRecorderSingle(
|
||||
eventStorage storage.EventStorageConsumer,
|
||||
eventRecorder service.EventsRecorder,
|
||||
metricsWrapper *storage.MetricWrapper,
|
||||
logger logging.LoggerInterface,
|
||||
metadata dtos.Metadata,
|
||||
) EventRecorder {
|
||||
return &RecorderSingle{
|
||||
eventStorage: eventStorage,
|
||||
eventRecorder: eventRecorder,
|
||||
metricsWrapper: metricsWrapper,
|
||||
logger: logger,
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeEvents syncs events
|
||||
func (e *RecorderSingle) SynchronizeEvents(bulkSize int64) error {
|
||||
queuedEvents, err := e.eventStorage.PopN(bulkSize)
|
||||
if err != nil {
|
||||
e.logger.Error("Error reading events queue", err)
|
||||
return errors.New("Error reading events queue")
|
||||
}
|
||||
|
||||
if len(queuedEvents) == 0 {
|
||||
e.logger.Debug("No events fetched from queue. Nothing to send")
|
||||
return nil
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
err = e.eventRecorder.Record(queuedEvents, e.metadata)
|
||||
if err != nil {
|
||||
if httpError, ok := err.(*dtos.HTTPError); ok {
|
||||
e.metricsWrapper.StoreCounters(storage.PostEventsCounter, strconv.Itoa(httpError.Code))
|
||||
}
|
||||
return err
|
||||
}
|
||||
bucket := util.Bucket(time.Now().Sub(before).Nanoseconds())
|
||||
e.metricsWrapper.StoreLatencies(storage.PostEventsLatency, bucket)
|
||||
e.metricsWrapper.StoreCounters(storage.PostEventsCounter, "ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushEvents flushes events
|
||||
func (e *RecorderSingle) FlushEvents(bulkSize int64) error {
|
||||
for !e.eventStorage.Empty() {
|
||||
err := e.SynchronizeEvents(bulkSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impression/interface.go
сгенерированный
поставляемый
Обычный файл
7
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impression/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,7 @@
|
||||
package impression
|
||||
|
||||
// ImpressionRecorder interface
|
||||
type ImpressionRecorder interface {
|
||||
SynchronizeImpressions(bulkSize int64) error
|
||||
FlushImpressions(bulkSize int64) error
|
||||
}
|
||||
118
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impression/single.go
сгенерированный
поставляемый
Обычный файл
118
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impression/single.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,118 @@
|
||||
package impression
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/storage"
|
||||
"github.com/splitio/go-split-commons/v3/util"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
maxImpressionCacheSize = 500000
|
||||
splitSDKImpressionsMode = "SplitSDKImpressionsMode"
|
||||
)
|
||||
|
||||
// RecorderSingle struct for impression sync
|
||||
type RecorderSingle struct {
|
||||
impressionStorage storage.ImpressionStorageConsumer
|
||||
impressionRecorder service.ImpressionsRecorder
|
||||
metricsWrapper *storage.MetricWrapper
|
||||
logger logging.LoggerInterface
|
||||
metadata dtos.Metadata
|
||||
mode string
|
||||
}
|
||||
|
||||
// NewRecorderSingle creates new impression synchronizer for posting impressions
|
||||
func NewRecorderSingle(
|
||||
impressionStorage storage.ImpressionStorageConsumer,
|
||||
impressionRecorder service.ImpressionsRecorder,
|
||||
metricsWrapper *storage.MetricWrapper,
|
||||
logger logging.LoggerInterface,
|
||||
metadata dtos.Metadata,
|
||||
managerConfig conf.ManagerConfig,
|
||||
) ImpressionRecorder {
|
||||
mode := conf.ImpressionsModeOptimized
|
||||
if !util.ShouldBeOptimized(managerConfig) {
|
||||
mode = conf.ImpressionsModeDebug
|
||||
}
|
||||
return &RecorderSingle{
|
||||
impressionStorage: impressionStorage,
|
||||
impressionRecorder: impressionRecorder,
|
||||
metricsWrapper: metricsWrapper,
|
||||
logger: logger,
|
||||
metadata: metadata,
|
||||
mode: mode,
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeImpressions syncs impressions
|
||||
func (i *RecorderSingle) SynchronizeImpressions(bulkSize int64) error {
|
||||
queuedImpressions, err := i.impressionStorage.PopN(bulkSize)
|
||||
if err != nil {
|
||||
i.logger.Error("Error reading impressions queue", err)
|
||||
return errors.New("Error reading impressions queue")
|
||||
}
|
||||
|
||||
if len(queuedImpressions) == 0 {
|
||||
i.logger.Debug("No impressions fetched from queue. Nothing to send")
|
||||
return nil
|
||||
}
|
||||
|
||||
impressionsToPost := make(map[string][]dtos.ImpressionDTO)
|
||||
for _, impression := range queuedImpressions {
|
||||
keyImpression := dtos.ImpressionDTO{
|
||||
KeyName: impression.KeyName,
|
||||
Treatment: impression.Treatment,
|
||||
Time: impression.Time,
|
||||
ChangeNumber: impression.ChangeNumber,
|
||||
Label: impression.Label,
|
||||
BucketingKey: impression.BucketingKey,
|
||||
Pt: impression.Pt,
|
||||
}
|
||||
v, ok := impressionsToPost[impression.FeatureName]
|
||||
if ok {
|
||||
v = append(v, keyImpression)
|
||||
} else {
|
||||
v = []dtos.ImpressionDTO{keyImpression}
|
||||
}
|
||||
impressionsToPost[impression.FeatureName] = v
|
||||
}
|
||||
|
||||
bulkImpressions := make([]dtos.ImpressionsDTO, 0)
|
||||
for testName, testImpressions := range impressionsToPost {
|
||||
bulkImpressions = append(bulkImpressions, dtos.ImpressionsDTO{
|
||||
TestName: testName,
|
||||
KeyImpressions: testImpressions,
|
||||
})
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
err = i.impressionRecorder.Record(bulkImpressions, i.metadata, map[string]string{splitSDKImpressionsMode: i.mode})
|
||||
if err != nil {
|
||||
if httpError, ok := err.(*dtos.HTTPError); ok {
|
||||
i.metricsWrapper.StoreCounters(storage.TestImpressionsCounter, strconv.Itoa(httpError.Code))
|
||||
}
|
||||
return err
|
||||
}
|
||||
bucket := util.Bucket(time.Now().Sub(before).Nanoseconds())
|
||||
i.metricsWrapper.StoreLatencies(storage.TestImpressionsLatency, bucket)
|
||||
i.metricsWrapper.StoreCounters(storage.TestImpressionsCounter, "ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlushImpressions flushes impressions
|
||||
func (i *RecorderSingle) FlushImpressions(bulkSize int64) error {
|
||||
for !i.impressionStorage.Empty() {
|
||||
err := i.SynchronizeImpressions(bulkSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
6
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impressionscount/interface.go
сгенерированный
поставляемый
Обычный файл
6
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impressionscount/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,6 @@
|
||||
package impressionscount
|
||||
|
||||
// ImpressionsCountRecorder interface
|
||||
type ImpressionsCountRecorder interface {
|
||||
SynchronizeImpressionsCount() error
|
||||
}
|
||||
51
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impressionscount/single.go
сгенерированный
поставляемый
Обычный файл
51
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/impressionscount/single.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,51 @@
|
||||
package impressionscount
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/provisional"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// RecorderSingle struct for impressionsCount sync
|
||||
type RecorderSingle struct {
|
||||
impressionsCounter *provisional.ImpressionsCounter
|
||||
impressionRecorder service.ImpressionsRecorder
|
||||
metadata dtos.Metadata
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// NewRecorderSingle creates new impressionsCount synchronizer for posting impressionsCount
|
||||
func NewRecorderSingle(
|
||||
impressionsCounter *provisional.ImpressionsCounter,
|
||||
impressionRecorder service.ImpressionsRecorder,
|
||||
metadata dtos.Metadata,
|
||||
logger logging.LoggerInterface,
|
||||
) ImpressionsCountRecorder {
|
||||
return &RecorderSingle{
|
||||
impressionsCounter: impressionsCounter,
|
||||
impressionRecorder: impressionRecorder,
|
||||
metadata: metadata,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeImpressionsCount syncs imp counts
|
||||
func (m *RecorderSingle) SynchronizeImpressionsCount() error {
|
||||
impressionsCount := m.impressionsCounter.PopAll()
|
||||
|
||||
impressionsInTimeFrame := make([]dtos.ImpressionsInTimeFrameDTO, 0)
|
||||
for key, count := range impressionsCount {
|
||||
impressionInTimeFrame := dtos.ImpressionsInTimeFrameDTO{
|
||||
FeatureName: key.FeatureName,
|
||||
RawCount: count,
|
||||
TimeFrame: key.TimeFrame,
|
||||
}
|
||||
impressionsInTimeFrame = append(impressionsInTimeFrame, impressionInTimeFrame)
|
||||
}
|
||||
|
||||
pf := dtos.ImpressionsCountDTO{
|
||||
PerFeature: impressionsInTimeFrame,
|
||||
}
|
||||
return m.impressionRecorder.RecordImpressionsCount(pf, m.metadata)
|
||||
}
|
||||
6
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/metric/interface.go
сгенерированный
поставляемый
Обычный файл
6
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/metric/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,6 @@
|
||||
package metric
|
||||
|
||||
// MetricRecorder interface
|
||||
type MetricRecorder interface {
|
||||
SynchronizeTelemetry() error
|
||||
}
|
||||
74
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/metric/single.go
сгенерированный
поставляемый
Обычный файл
74
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/metric/single.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,74 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/storage"
|
||||
)
|
||||
|
||||
// RecorderSingle struct for metric sync
|
||||
type RecorderSingle struct {
|
||||
metricStorage storage.MetricsStorageConsumer
|
||||
metricRecorder service.MetricsRecorder
|
||||
metadata dtos.Metadata
|
||||
}
|
||||
|
||||
// NewRecorderSingle creates new metric synchronizer for posting metrics
|
||||
func NewRecorderSingle(
|
||||
metricStorage storage.MetricsStorageConsumer,
|
||||
metricRecorder service.MetricsRecorder,
|
||||
metadata dtos.Metadata,
|
||||
) MetricRecorder {
|
||||
return &RecorderSingle{
|
||||
metricStorage: metricStorage,
|
||||
metricRecorder: metricRecorder,
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RecorderSingle) synchronizeLatencies() error {
|
||||
latencies := m.metricStorage.PopLatencies()
|
||||
if len(latencies) > 0 {
|
||||
err := m.metricRecorder.RecordLatencies(latencies, m.metadata)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RecorderSingle) synchronizeGauges() error {
|
||||
var errs []error
|
||||
for _, gauge := range m.metricStorage.PopGauges() {
|
||||
err := m.metricRecorder.RecordGauge(gauge, m.metadata)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errors.New("Some gauges could not be posted")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RecorderSingle) synchronizeCounters() error {
|
||||
counters := m.metricStorage.PopCounters()
|
||||
if len(counters) > 0 {
|
||||
err := m.metricRecorder.RecordCounters(counters, m.metadata)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SynchronizeTelemetry syncs telemetry
|
||||
func (m *RecorderSingle) SynchronizeTelemetry() error {
|
||||
err := m.synchronizeGauges()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.synchronizeLatencies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.synchronizeCounters()
|
||||
}
|
||||
9
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/segment/interface.go
сгенерированный
поставляемый
Обычный файл
9
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/segment/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,9 @@
|
||||
package segment
|
||||
|
||||
// Updater interface
|
||||
type Updater interface {
|
||||
SynchronizeSegment(name string, till *int64, requestNoCache bool) error
|
||||
SynchronizeSegments(requestNoCache bool) error
|
||||
SegmentNames() []interface{}
|
||||
IsSegmentCached(segmentName string) bool
|
||||
}
|
||||
146
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/segment/segment.go
сгенерированный
поставляемый
Обычный файл
146
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/segment/segment.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,146 @@
|
||||
package segment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/storage"
|
||||
"github.com/splitio/go-split-commons/v3/util"
|
||||
"github.com/splitio/go-toolkit/v4/datastructures/set"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// UpdaterImpl struct for segment sync
|
||||
type UpdaterImpl struct {
|
||||
splitStorage storage.SplitStorageConsumer
|
||||
segmentStorage storage.SegmentStorage
|
||||
segmentFetcher service.SegmentFetcher
|
||||
metricsWrapper *storage.MetricWrapper
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// NewSegmentFetcher creates new segment synchronizer for processing segment updates
|
||||
func NewSegmentFetcher(
|
||||
splitStorage storage.SplitStorage,
|
||||
segmentStorage storage.SegmentStorage,
|
||||
segmentFetcher service.SegmentFetcher,
|
||||
metricsWrapper *storage.MetricWrapper,
|
||||
logger logging.LoggerInterface,
|
||||
) Updater {
|
||||
return &UpdaterImpl{
|
||||
splitStorage: splitStorage,
|
||||
segmentStorage: segmentStorage,
|
||||
segmentFetcher: segmentFetcher,
|
||||
metricsWrapper: metricsWrapper,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UpdaterImpl) processUpdate(segmentChanges *dtos.SegmentChangesDTO) {
|
||||
name := segmentChanges.Name
|
||||
oldSegment := s.segmentStorage.Keys(name)
|
||||
if oldSegment == nil {
|
||||
keys := set.NewSet()
|
||||
for _, key := range segmentChanges.Added {
|
||||
keys.Add(key)
|
||||
}
|
||||
s.logger.Debug(fmt.Sprintf("Segment [%s] doesn't exist now, it will add (%d) keys", name, keys.Size()))
|
||||
s.segmentStorage.Update(name, keys, set.NewSet(), segmentChanges.Till)
|
||||
} else {
|
||||
toAdd := set.NewSet()
|
||||
toRemove := set.NewSet()
|
||||
// Segment exists, must add new members and remove old ones
|
||||
for _, key := range segmentChanges.Added {
|
||||
toAdd.Add(key)
|
||||
}
|
||||
for _, key := range segmentChanges.Removed {
|
||||
toRemove.Add(key)
|
||||
}
|
||||
if toAdd.Size() > 0 || toRemove.Size() > 0 {
|
||||
s.logger.Debug(fmt.Sprintf("Segment [%s] exists, it will be updated. %d keys added, %d keys removed", name, toAdd.Size(), toRemove.Size()))
|
||||
s.segmentStorage.Update(name, toAdd, toRemove, segmentChanges.Till)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeSegment syncs segment
|
||||
func (s *UpdaterImpl) SynchronizeSegment(name string, till *int64, requestNoCache bool) error {
|
||||
for {
|
||||
s.logger.Debug(fmt.Sprintf("Synchronizing segment %s", name))
|
||||
changeNumber, _ := s.segmentStorage.ChangeNumber(name)
|
||||
if changeNumber == 0 {
|
||||
changeNumber = -1
|
||||
}
|
||||
if till != nil && *till < changeNumber {
|
||||
return nil
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
segmentChanges, err := s.segmentFetcher.Fetch(name, changeNumber, requestNoCache)
|
||||
if err != nil {
|
||||
if httpError, ok := err.(*dtos.HTTPError); ok {
|
||||
s.metricsWrapper.StoreCounters(storage.SegmentChangesCounter, strconv.Itoa(httpError.Code))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
s.processUpdate(segmentChanges)
|
||||
bucket := util.Bucket(time.Now().Sub(before).Nanoseconds())
|
||||
s.metricsWrapper.StoreLatencies(storage.SegmentChangesLatency, bucket)
|
||||
s.metricsWrapper.StoreCounters(storage.SegmentChangesCounter, "ok")
|
||||
if segmentChanges.Till == segmentChanges.Since || (till != nil && segmentChanges.Till >= *till) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeSegments syncs segments at once
|
||||
func (s *UpdaterImpl) SynchronizeSegments(requestNoCache bool) error {
|
||||
// @TODO: add delays
|
||||
segmentNames := s.splitStorage.SegmentNames().List()
|
||||
s.logger.Debug("Segment Sync", segmentNames)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(segmentNames))
|
||||
failedSegments := set.NewThreadSafeSet()
|
||||
for _, name := range segmentNames {
|
||||
conv, ok := name.(string)
|
||||
if !ok {
|
||||
s.logger.Warning("Skipping non-string segment present in storage at initialization-time!")
|
||||
continue
|
||||
}
|
||||
go func(segmentName string) {
|
||||
defer wg.Done() // Make sure the "finished" signal is always sent
|
||||
ready := false
|
||||
var err error
|
||||
for !ready {
|
||||
err = s.SynchronizeSegment(segmentName, nil, requestNoCache)
|
||||
if err != nil {
|
||||
failedSegments.Add(segmentName)
|
||||
}
|
||||
return
|
||||
}
|
||||
}(conv)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if failedSegments.Size() > 0 {
|
||||
return fmt.Errorf("The following segments failed to be fetched %v", failedSegments.List())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SegmentNames returns all segments
|
||||
func (s *UpdaterImpl) SegmentNames() []interface{} {
|
||||
return s.splitStorage.SegmentNames().List()
|
||||
}
|
||||
|
||||
// IsSegmentCached returns true if a segment exists
|
||||
func (s *UpdaterImpl) IsSegmentCached(segmentName string) bool {
|
||||
cn, _ := s.segmentStorage.ChangeNumber(segmentName)
|
||||
return cn != -1
|
||||
}
|
||||
7
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/split/interface.go
сгенерированный
поставляемый
Обычный файл
7
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/split/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,7 @@
|
||||
package split
|
||||
|
||||
// Updater interface
|
||||
type Updater interface {
|
||||
SynchronizeSplits(till *int64, requestNoCache bool) ([]string, error)
|
||||
LocalKill(splitName string, defaultTreatment string, changeNumber int64)
|
||||
}
|
||||
116
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/split/split.go
сгенерированный
поставляемый
Обычный файл
116
vendor/github.com/splitio/go-split-commons/v3/synchronizer/worker/split/split.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,116 @@
|
||||
package split
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/dtos"
|
||||
"github.com/splitio/go-split-commons/v3/service"
|
||||
"github.com/splitio/go-split-commons/v3/storage"
|
||||
"github.com/splitio/go-split-commons/v3/util"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
matcherTypeInSegment = "IN_SEGMENT"
|
||||
)
|
||||
|
||||
// UpdaterImpl struct for split sync
|
||||
type UpdaterImpl struct {
|
||||
splitStorage storage.SplitStorage
|
||||
splitFetcher service.SplitFetcher
|
||||
metricsWrapper *storage.MetricWrapper
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// NewSplitFetcher creates new split synchronizer for processing split updates
|
||||
func NewSplitFetcher(
|
||||
splitStorage storage.SplitStorage,
|
||||
splitFetcher service.SplitFetcher,
|
||||
metricsWrapper *storage.MetricWrapper,
|
||||
logger logging.LoggerInterface,
|
||||
) *UpdaterImpl {
|
||||
return &UpdaterImpl{
|
||||
splitStorage: splitStorage,
|
||||
splitFetcher: splitFetcher,
|
||||
metricsWrapper: metricsWrapper,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UpdaterImpl) processUpdate(splits *dtos.SplitChangesDTO) {
|
||||
inactiveSplits := make([]dtos.SplitDTO, 0)
|
||||
activeSplits := make([]dtos.SplitDTO, 0)
|
||||
for _, split := range splits.Splits {
|
||||
if split.Status == "ACTIVE" {
|
||||
activeSplits = append(activeSplits, split)
|
||||
} else {
|
||||
inactiveSplits = append(inactiveSplits, split)
|
||||
}
|
||||
}
|
||||
|
||||
// Add/Update active splits
|
||||
s.splitStorage.PutMany(activeSplits, splits.Till)
|
||||
|
||||
// Remove inactive splits
|
||||
for _, split := range inactiveSplits {
|
||||
s.splitStorage.Remove(split.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeSplits syncs splits
|
||||
func (s *UpdaterImpl) SynchronizeSplits(till *int64, requestNoCache bool) ([]string, error) {
|
||||
// @TODO: add delays
|
||||
|
||||
segments := make([]string, 0)
|
||||
for {
|
||||
changeNumber, _ := s.splitStorage.ChangeNumber()
|
||||
if changeNumber == 0 {
|
||||
changeNumber = -1
|
||||
}
|
||||
if till != nil && *till < changeNumber {
|
||||
return segments, nil
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
splits, err := s.splitFetcher.Fetch(changeNumber, requestNoCache)
|
||||
if err != nil {
|
||||
if httpError, ok := err.(*dtos.HTTPError); ok {
|
||||
s.metricsWrapper.StoreCounters(storage.SplitChangesCounter, strconv.Itoa(httpError.Code))
|
||||
}
|
||||
return segments, err
|
||||
}
|
||||
s.processUpdate(splits)
|
||||
segments = append(segments, extractSegments(splits)...)
|
||||
bucket := util.Bucket(time.Now().Sub(before).Nanoseconds())
|
||||
s.metricsWrapper.StoreCounters(storage.SplitChangesCounter, "ok")
|
||||
s.metricsWrapper.StoreLatencies(storage.SplitChangesLatency, bucket)
|
||||
if splits.Till == splits.Since || (till != nil && splits.Till >= *till) {
|
||||
return segments, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extractSegments(splits *dtos.SplitChangesDTO) []string {
|
||||
names := make(map[string]struct{})
|
||||
for _, split := range splits.Splits {
|
||||
for _, cond := range split.Conditions {
|
||||
for _, matcher := range cond.MatcherGroup.Matchers {
|
||||
if matcher.MatcherType == matcherTypeInSegment && matcher.UserDefinedSegment != nil {
|
||||
names[matcher.UserDefinedSegment.SegmentName] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toRet := make([]string, 0, len(names))
|
||||
for name := range names {
|
||||
toRet = append(toRet, name)
|
||||
}
|
||||
return toRet
|
||||
}
|
||||
|
||||
// LocalKill marks a spit as killed in local storage
|
||||
func (s *UpdaterImpl) LocalKill(splitName string, defaultTreatment string, changeNumber int64) {
|
||||
s.splitStorage.KillLocally(splitName, defaultTreatment, changeNumber)
|
||||
}
|
||||
50
vendor/github.com/splitio/go-split-commons/v3/tasks/eventsync.go
сгенерированный
поставляемый
Обычный файл
50
vendor/github.com/splitio/go-split-commons/v3/tasks/eventsync.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,50 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/event"
|
||||
"github.com/splitio/go-toolkit/v4/asynctask"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// NewRecordEventsTask creates a new events recording task
|
||||
func NewRecordEventsTask(
|
||||
synchronizer event.EventRecorder,
|
||||
bulkSize int64,
|
||||
period int,
|
||||
logger logging.LoggerInterface,
|
||||
) Task {
|
||||
record := func(logger logging.LoggerInterface) error {
|
||||
return synchronizer.SynchronizeEvents(bulkSize)
|
||||
}
|
||||
|
||||
onStop := func(logger logging.LoggerInterface) {
|
||||
// All this function does is flush events which will clear the storage
|
||||
synchronizer.FlushEvents(bulkSize)
|
||||
}
|
||||
|
||||
return asynctask.NewAsyncTask("SubmitEvents", record, period, nil, onStop, logger)
|
||||
}
|
||||
|
||||
// NewRecordEventsTasks creates a new splits fetching and storing task
|
||||
func NewRecordEventsTasks(
|
||||
recorder event.EventRecorder,
|
||||
bulkSize int64,
|
||||
period int,
|
||||
logger logging.LoggerInterface,
|
||||
totalTasks int) Task {
|
||||
record := func(logger logging.LoggerInterface) error {
|
||||
return recorder.SynchronizeEvents(bulkSize)
|
||||
}
|
||||
|
||||
tasks := make([]Task, 0, totalTasks)
|
||||
for i := 0; i < totalTasks; i++ {
|
||||
logger.Info(fmt.Sprintf("Creating SubmitEvents_%d", i))
|
||||
tasks = append(tasks, asynctask.NewAsyncTask(fmt.Sprintf("SubmitEvents_%d", i), record, period, nil, nil, logger))
|
||||
}
|
||||
return MultipleTask{
|
||||
logger: logger,
|
||||
tasks: tasks,
|
||||
}
|
||||
}
|
||||
27
vendor/github.com/splitio/go-split-commons/v3/tasks/impressionscountsync.go
сгенерированный
поставляемый
Обычный файл
27
vendor/github.com/splitio/go-split-commons/v3/tasks/impressionscountsync.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,27 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/impressionscount"
|
||||
"github.com/splitio/go-toolkit/v4/asynctask"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
period = 1800 // 30 min
|
||||
)
|
||||
|
||||
// NewRecordImpressionsCountTask creates a new impressionsCount recording task
|
||||
func NewRecordImpressionsCountTask(
|
||||
recorder impressionscount.ImpressionsCountRecorder,
|
||||
logger logging.LoggerInterface,
|
||||
) *asynctask.AsyncTask {
|
||||
record := func(logger logging.LoggerInterface) error {
|
||||
return recorder.SynchronizeImpressionsCount()
|
||||
}
|
||||
|
||||
onStop := func(logger logging.LoggerInterface) {
|
||||
recorder.SynchronizeImpressionsCount()
|
||||
}
|
||||
|
||||
return asynctask.NewAsyncTask("SubmitImpressionsCount", record, period, nil, onStop, logger)
|
||||
}
|
||||
50
vendor/github.com/splitio/go-split-commons/v3/tasks/impressionsync.go
сгенерированный
поставляемый
Обычный файл
50
vendor/github.com/splitio/go-split-commons/v3/tasks/impressionsync.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,50 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/impression"
|
||||
"github.com/splitio/go-toolkit/v4/asynctask"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// NewRecordImpressionsTask creates a new splits fetching and storing task
|
||||
func NewRecordImpressionsTask(
|
||||
recorder impression.ImpressionRecorder,
|
||||
period int,
|
||||
logger logging.LoggerInterface,
|
||||
bulkSize int64,
|
||||
) Task {
|
||||
record := func(logger logging.LoggerInterface) error {
|
||||
return recorder.SynchronizeImpressions(bulkSize)
|
||||
}
|
||||
|
||||
onStop := func(logger logging.LoggerInterface) {
|
||||
// All this function does is flush impressions which will clear the storage
|
||||
recorder.FlushImpressions(bulkSize)
|
||||
}
|
||||
|
||||
return asynctask.NewAsyncTask("SubmitImpressions", record, period, nil, onStop, logger)
|
||||
}
|
||||
|
||||
// NewRecordImpressionsTasks creates a new splits fetching and storing task
|
||||
func NewRecordImpressionsTasks(
|
||||
recorder impression.ImpressionRecorder,
|
||||
period int,
|
||||
logger logging.LoggerInterface,
|
||||
bulkSize int64,
|
||||
totalTasks int) Task {
|
||||
record := func(logger logging.LoggerInterface) error {
|
||||
return recorder.SynchronizeImpressions(bulkSize)
|
||||
}
|
||||
|
||||
tasks := make([]Task, 0, totalTasks)
|
||||
for i := 0; i < totalTasks; i++ {
|
||||
logger.Info(fmt.Sprintf("Creating SubmitImpressions_%d", i))
|
||||
tasks = append(tasks, asynctask.NewAsyncTask(fmt.Sprintf("SubmitImpressions_%d", i), record, period, nil, nil, logger))
|
||||
}
|
||||
return MultipleTask{
|
||||
logger: logger,
|
||||
tasks: tasks,
|
||||
}
|
||||
}
|
||||
53
vendor/github.com/splitio/go-split-commons/v3/tasks/interface.go
сгенерированный
поставляемый
Обычный файл
53
vendor/github.com/splitio/go-split-commons/v3/tasks/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,53 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// Task interface
|
||||
type Task interface {
|
||||
Start()
|
||||
Stop(blocking bool) error
|
||||
IsRunning() bool
|
||||
}
|
||||
|
||||
// MultipleTask struct
|
||||
type MultipleTask struct {
|
||||
tasks []Task
|
||||
logger logging.LoggerInterface
|
||||
}
|
||||
|
||||
// IsRunning method
|
||||
func (m MultipleTask) IsRunning() bool {
|
||||
for _, t := range m.tasks {
|
||||
if t.IsRunning() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Start method
|
||||
func (m MultipleTask) Start() {
|
||||
for _, t := range m.tasks {
|
||||
t.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop method
|
||||
func (m MultipleTask) Stop(blocking bool) error {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(m.tasks))
|
||||
for _, t := range m.tasks {
|
||||
go func(t Task) {
|
||||
t.Stop(blocking)
|
||||
wg.Done()
|
||||
}(t)
|
||||
}
|
||||
if blocking {
|
||||
wg.Wait()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
23
vendor/github.com/splitio/go-split-commons/v3/tasks/metricsync.go
сгенерированный
поставляемый
Обычный файл
23
vendor/github.com/splitio/go-split-commons/v3/tasks/metricsync.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,23 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/metric"
|
||||
"github.com/splitio/go-toolkit/v4/asynctask"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// NewRecordTelemetryTask creates a new telemtry recording task
|
||||
func NewRecordTelemetryTask(
|
||||
recorder metric.MetricRecorder,
|
||||
period int,
|
||||
logger logging.LoggerInterface,
|
||||
) *asynctask.AsyncTask {
|
||||
record := func(logger logging.LoggerInterface) error {
|
||||
return recorder.SynchronizeTelemetry()
|
||||
}
|
||||
|
||||
onStop := func(l logging.LoggerInterface) {
|
||||
record(logger)
|
||||
}
|
||||
return asynctask.NewAsyncTask("SubmitTelemetry", record, period, nil, onStop, logger)
|
||||
}
|
||||
81
vendor/github.com/splitio/go-split-commons/v3/tasks/segmentsync.go
сгенерированный
поставляемый
Обычный файл
81
vendor/github.com/splitio/go-split-commons/v3/tasks/segmentsync.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,81 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/segment"
|
||||
"github.com/splitio/go-toolkit/v4/asynctask"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
"github.com/splitio/go-toolkit/v4/workerpool"
|
||||
)
|
||||
|
||||
func updateSegments(
|
||||
fetcher segment.Updater,
|
||||
admin *workerpool.WorkerAdmin,
|
||||
logger logging.LoggerInterface,
|
||||
) error {
|
||||
segmentList := fetcher.SegmentNames()
|
||||
for _, name := range segmentList {
|
||||
ok := admin.QueueMessage(name)
|
||||
if !ok {
|
||||
logger.Error(
|
||||
fmt.Sprintf("Segment %s could not be added because the job queue is full.\n", name),
|
||||
fmt.Sprintf(
|
||||
"You currently have %d segments and the queue size is %d.\n",
|
||||
len(segmentList),
|
||||
admin.QueueSize(),
|
||||
),
|
||||
"Please consider updating the segment queue size accordingly in the configuration options",
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFetchSegmentsTask creates a new segment fetching and storing task
|
||||
func NewFetchSegmentsTask(
|
||||
fetcher segment.Updater,
|
||||
period int,
|
||||
workerCount int,
|
||||
queueSize int,
|
||||
logger logging.LoggerInterface,
|
||||
) *asynctask.AsyncTask {
|
||||
|
||||
admin := atomic.Value{}
|
||||
|
||||
// After all segments are in sync, add workers to the pool that will keep them up to date
|
||||
// periodically
|
||||
onInit := func(logger logging.LoggerInterface) error {
|
||||
admin.Store(workerpool.NewWorkerAdmin(queueSize, logger))
|
||||
for i := 0; i < workerCount; i++ {
|
||||
worker := NewSegmentWorker(
|
||||
fmt.Sprintf("SegmentWorker_%d", i),
|
||||
0,
|
||||
func(n string, t *int64) error { return fetcher.SynchronizeSegment(n, t, false) },
|
||||
)
|
||||
admin.Load().(*workerpool.WorkerAdmin).AddWorker(worker)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
update := func(logger logging.LoggerInterface) error {
|
||||
wa, ok := admin.Load().(*workerpool.WorkerAdmin)
|
||||
if !ok || wa == nil {
|
||||
return errors.New("unable to type-assert worker manager")
|
||||
}
|
||||
return updateSegments(fetcher, wa, logger)
|
||||
}
|
||||
|
||||
cleanup := func(logger logging.LoggerInterface) {
|
||||
wa, ok := admin.Load().(*workerpool.WorkerAdmin)
|
||||
if !ok || wa == nil {
|
||||
logger.Error("unable to type-assert worker manager")
|
||||
return
|
||||
}
|
||||
wa.StopAll(true)
|
||||
}
|
||||
|
||||
return asynctask.NewAsyncTask("UpdateSegments", update, period, onInit, cleanup, logger)
|
||||
}
|
||||
21
vendor/github.com/splitio/go-split-commons/v3/tasks/splitsync.go
сгенерированный
поставляемый
Обычный файл
21
vendor/github.com/splitio/go-split-commons/v3/tasks/splitsync.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,21 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v3/synchronizer/worker/split"
|
||||
"github.com/splitio/go-toolkit/v4/asynctask"
|
||||
"github.com/splitio/go-toolkit/v4/logging"
|
||||
)
|
||||
|
||||
// NewFetchSplitsTask creates a new splits fetching and storing task
|
||||
func NewFetchSplitsTask(
|
||||
fetcher split.Updater,
|
||||
period int,
|
||||
logger logging.LoggerInterface,
|
||||
) *asynctask.AsyncTask {
|
||||
update := func(logger logging.LoggerInterface) error {
|
||||
_, err := fetcher.SynchronizeSplits(nil, false)
|
||||
return err
|
||||
}
|
||||
|
||||
return asynctask.NewAsyncTask("UpdateSplits", update, period, nil, nil, logger)
|
||||
}
|
||||
47
vendor/github.com/splitio/go-split-commons/v3/tasks/worker.go
сгенерированный
поставляемый
Обычный файл
47
vendor/github.com/splitio/go-split-commons/v3/tasks/worker.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,47 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// SegmentWorker struct contains resources and functions for fetching segments and storing them
|
||||
type SegmentWorker struct {
|
||||
name string
|
||||
failureTime int64
|
||||
toExecute func(name string, till *int64) error
|
||||
}
|
||||
|
||||
// NewSegmentWorker some
|
||||
func NewSegmentWorker(name string, failureTime int64, toExecute func(name string, till *int64) error) *SegmentWorker {
|
||||
return &SegmentWorker{
|
||||
name: name,
|
||||
failureTime: failureTime,
|
||||
toExecute: toExecute,
|
||||
}
|
||||
}
|
||||
|
||||
// Name Returns the name of the worker
|
||||
func (w *SegmentWorker) Name() string {
|
||||
return w.name
|
||||
}
|
||||
|
||||
// FailureTime Returns how much time should be waited after an error, before the worker resumes execution
|
||||
func (w *SegmentWorker) FailureTime() int64 {
|
||||
return w.failureTime
|
||||
}
|
||||
|
||||
// DoWork performs the actual work and returns an error if something goes wrong
|
||||
func (w *SegmentWorker) DoWork(msg interface{}) error {
|
||||
segmentName, ok := msg.(string)
|
||||
if !ok {
|
||||
return errors.New("segment name popped from queue is not a string")
|
||||
}
|
||||
|
||||
return w.toExecute(segmentName, nil)
|
||||
}
|
||||
|
||||
// OnError callback does nothing
|
||||
func (w *SegmentWorker) OnError(e error) {}
|
||||
|
||||
// Cleanup callback does nothing
|
||||
func (w *SegmentWorker) Cleanup() error { return nil }
|
||||
43
vendor/github.com/splitio/go-split-commons/v3/util/metrics.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v3/util/metrics.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package util
|
||||
|
||||
var latencyBuckets = [23]float64{
|
||||
1.00,
|
||||
1.50,
|
||||
2.25,
|
||||
3.38,
|
||||
5.06,
|
||||
7.59,
|
||||
11.39,
|
||||
17.09,
|
||||
25.63,
|
||||
38.44,
|
||||
57.67,
|
||||
86.50,
|
||||
129.75,
|
||||
194.62,
|
||||
291.93,
|
||||
437.89,
|
||||
656.84,
|
||||
985.26,
|
||||
1477.89,
|
||||
2216.84,
|
||||
3325.26,
|
||||
4987.89,
|
||||
7481.83,
|
||||
}
|
||||
|
||||
// Bucket returns the bucket where the received latency falls
|
||||
func Bucket(latency int64) int {
|
||||
floatLatency := float64(latency) / 1000 // Convert to millisencods
|
||||
|
||||
index := 0
|
||||
for index < len(latencyBuckets) && floatLatency > latencyBuckets[index] {
|
||||
index++
|
||||
}
|
||||
|
||||
if index == len(latencyBuckets) {
|
||||
return index - 1
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
30
vendor/github.com/splitio/go-split-commons/v3/util/mode.go
сгенерированный
поставляемый
Обычный файл
30
vendor/github.com/splitio/go-split-commons/v3/util/mode.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,30 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-split-commons/v3/conf"
|
||||
)
|
||||
|
||||
// ShouldAddPreviousTime returns if previous time should be set up or not depending on operationMode
|
||||
func ShouldAddPreviousTime(managerConfig conf.ManagerConfig) bool {
|
||||
switch strings.ToLower(managerConfig.OperationMode) {
|
||||
case conf.ProducerSync:
|
||||
fallthrough
|
||||
case conf.Standalone:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldBeOptimized returns if should dedupe impressions or not depending on configs
|
||||
func ShouldBeOptimized(managerConfig conf.ManagerConfig) bool {
|
||||
if !ShouldAddPreviousTime(managerConfig) {
|
||||
return false
|
||||
}
|
||||
if strings.ToLower(managerConfig.ImpressionsMode) == conf.ImpressionsModeOptimized {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
11
vendor/github.com/splitio/go-split-commons/v3/util/time.go
сгенерированный
поставляемый
Обычный файл
11
vendor/github.com/splitio/go-split-commons/v3/util/time.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,11 @@
|
||||
package util
|
||||
|
||||
import "time"
|
||||
|
||||
const dedupWindowSizeMs = 3600 * 1000
|
||||
|
||||
// TruncateTimeFrame truncates de time frame received with the time window
|
||||
func TruncateTimeFrame(timestampInNs int64) int64 {
|
||||
timestampInMs := timestampInNs / int64(time.Millisecond)
|
||||
return timestampInMs - (timestampInMs % dedupWindowSizeMs)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user