MM-28859 Add feature flag managment system using split.io and remove viper. (#15954)
* Add feature flag managment system using split.io and remove viper. * Fixing tests. * Attempt to fix postgres tests. * Fix watch filepath for advanced logging. * Review fixes. * Some error wrapping. * Remove unessisary store interface. * Desanitize SplitKey * Simplify. * Review feedback. * Rename split mlog adatper to split logger. * fsInner * Style. * Restore oldcfg test. * Downgrading non-actionable feature flag errors to warnings. Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
8bb772638c
Коммит
1aadd36644
118
vendor/github.com/splitio/go-split-commons/v2/storage/interfaces.go
сгенерированный
поставляемый
Обычный файл
118
vendor/github.com/splitio/go-split-commons/v2/storage/interfaces.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,118 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/metricWrapper.go
сгенерированный
поставляемый
Обычный файл
125
vendor/github.com/splitio/go-split-commons/v2/storage/metricWrapper.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,125 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/mocks/event.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/event.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-split-commons/v2/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/v2/storage/mocks/impression.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/impression.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-split-commons/v2/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/v2/storage/mocks/metric.go
сгенерированный
поставляемый
Обычный файл
73
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/metric.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,73 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-split-commons/v2/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/v2/storage/mocks/segment.go
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/segment.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package mocks
|
||||
|
||||
import "github.com/splitio/go-toolkit/v3/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/v2/storage/mocks/split.go
сгенерированный
поставляемый
Обычный файл
76
vendor/github.com/splitio/go-split-commons/v2/storage/mocks/split.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,76 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/mutexmap/metrics.go
сгенерированный
поставляемый
Обычный файл
149
vendor/github.com/splitio/go-split-commons/v2/storage/mutexmap/metrics.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,149 @@
|
||||
package mutexmap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/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/v2/storage/mutexmap/segments.go
сгенерированный
поставляемый
Обычный файл
88
vendor/github.com/splitio/go-split-commons/v2/storage/mutexmap/segments.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,88 @@
|
||||
package mutexmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/mutexmap/splits.go
сгенерированный
поставляемый
Обычный файл
194
vendor/github.com/splitio/go-split-commons/v2/storage/mutexmap/splits.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,194 @@
|
||||
package mutexmap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/mutexqueue/constants.go
сгенерированный
поставляемый
Обычный файл
6
vendor/github.com/splitio/go-split-commons/v2/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/v2/storage/mutexqueue/events.go
сгенерированный
поставляемый
Обычный файл
136
vendor/github.com/splitio/go-split-commons/v2/storage/mutexqueue/events.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,136 @@
|
||||
package mutexqueue
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/mutexqueue/impressions.go
сгенерированный
поставляемый
Обычный файл
108
vendor/github.com/splitio/go-split-commons/v2/storage/mutexqueue/impressions.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,108 @@
|
||||
package mutexqueue
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/redis/constants.go
сгенерированный
поставляемый
Обычный файл
17
vendor/github.com/splitio/go-split-commons/v2/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/v2/storage/redis/events.go
сгенерированный
поставляемый
Обычный файл
177
vendor/github.com/splitio/go-split-commons/v2/storage/redis/events.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,177 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v3/queuecache"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/redis/impressions.go
сгенерированный
поставляемый
Обычный файл
149
vendor/github.com/splitio/go-split-commons/v2/storage/redis/impressions.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,149 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/redis/metrics.go
сгенерированный
поставляемый
Обычный файл
322
vendor/github.com/splitio/go-split-commons/v2/storage/redis/metrics.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,322 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/redis/miscstorage.go
сгенерированный
поставляемый
Обычный файл
56
vendor/github.com/splitio/go-split-commons/v2/storage/redis/miscstorage.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,56 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/redis/redis.go
сгенерированный
поставляемый
Обычный файл
74
vendor/github.com/splitio/go-split-commons/v2/storage/redis/redis.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,74 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/conf"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v3/redis"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/redis/segments.go
сгенерированный
поставляемый
Обычный файл
100
vendor/github.com/splitio/go-split-commons/v2/storage/redis/segments.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,100 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-toolkit/v3/datastructures/set"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v3/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/v2/storage/redis/splits.go
сгенерированный
поставляемый
Обычный файл
261
vendor/github.com/splitio/go-split-commons/v2/storage/redis/splits.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,261 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/splitio/go-split-commons/v2/dtos"
|
||||
"github.com/splitio/go-toolkit/v3/datastructures/set"
|
||||
"github.com/splitio/go-toolkit/v3/logging"
|
||||
"github.com/splitio/go-toolkit/v3/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
|
||||
}
|
||||
Ссылка в новой задаче
Block a user