-
Notifications
You must be signed in to change notification settings - Fork 251
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Live debugging service with prometheus.relabel (#797)
* create xray service to manage the debug streams * add xray support to the prometheus.relabel component * add xray endpoint to the api * rename handler to manager * defer cleanup func * rework to add multi-streams support * more tests * additional multi delete test * add todo comment for buffer size * add error checks * improve naming and readability, split interfaces and add a check to avoid expensive string computation * move register and isregistered to a new debugRegistry interface
- Loading branch information
Showing
9 changed files
with
386 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package livedebugging | ||
|
||
import "sync" | ||
|
||
type ComponentName string | ||
type ComponentID string | ||
type CallbackID string | ||
|
||
// DebugCallbackManager is used to manage live debugging callbacks. | ||
type DebugCallbackManager interface { | ||
DebugRegistry | ||
// AddCallback sets a callback for a given componentID. | ||
// The callback is used to send debugging data to live debugging consumers. | ||
AddCallback(callbackID CallbackID, componentID ComponentID, callback func(string)) | ||
// DeleteCallback deletes a callback for a given componentID. | ||
DeleteCallback(callbackID CallbackID, componentID ComponentID) | ||
} | ||
|
||
// DebugDataPublisher is used by components to push information to live debugging consumers. | ||
type DebugDataPublisher interface { | ||
DebugRegistry | ||
// Publish sends debugging data for a given componentID. | ||
Publish(componentID ComponentID, data string) | ||
// IsActive returns true when at least one consumer is listening for debugging data for the given componentID. | ||
IsActive(componentID ComponentID) bool | ||
} | ||
|
||
// DebugRegistry is used to keep track of the components that supports the live debugging functionality. | ||
type DebugRegistry interface { | ||
// Register a component by name. | ||
Register(componentName ComponentName) | ||
// IsRegistered returns true if a component has live debugging support. | ||
IsRegistered(componentName ComponentName) bool | ||
} | ||
|
||
type liveDebugging struct { | ||
loadMut sync.RWMutex | ||
callbacks map[ComponentID]map[CallbackID]func(string) | ||
registeredComponents map[ComponentName]struct{} | ||
} | ||
|
||
var _ DebugCallbackManager = &liveDebugging{} | ||
var _ DebugDataPublisher = &liveDebugging{} | ||
|
||
// NewLiveDebugging creates a new instance of liveDebugging. | ||
func NewLiveDebugging() *liveDebugging { | ||
return &liveDebugging{ | ||
callbacks: make(map[ComponentID]map[CallbackID]func(string)), | ||
registeredComponents: make(map[ComponentName]struct{}), | ||
} | ||
} | ||
|
||
func (s *liveDebugging) Publish(componentID ComponentID, data string) { | ||
s.loadMut.RLock() | ||
defer s.loadMut.RUnlock() | ||
for _, callback := range s.callbacks[componentID] { | ||
callback(data) | ||
} | ||
} | ||
|
||
func (s *liveDebugging) IsActive(componentID ComponentID) bool { | ||
_, exist := s.callbacks[componentID] | ||
return exist | ||
} | ||
|
||
func (s *liveDebugging) AddCallback(callbackID CallbackID, componentID ComponentID, callback func(string)) { | ||
s.loadMut.Lock() | ||
defer s.loadMut.Unlock() | ||
if _, ok := s.callbacks[componentID]; !ok { | ||
s.callbacks[componentID] = make(map[CallbackID]func(string)) | ||
} | ||
s.callbacks[componentID][callbackID] = callback | ||
} | ||
|
||
func (s *liveDebugging) DeleteCallback(callbackID CallbackID, componentID ComponentID) { | ||
s.loadMut.Lock() | ||
defer s.loadMut.Unlock() | ||
delete(s.callbacks[componentID], callbackID) | ||
} | ||
|
||
func (s *liveDebugging) Register(componentName ComponentName) { | ||
s.registeredComponents[componentName] = struct{}{} | ||
} | ||
|
||
func (s *liveDebugging) IsRegistered(componentName ComponentName) bool { | ||
_, exist := s.registeredComponents[componentName] | ||
return exist | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package livedebugging | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestRegister(t *testing.T) { | ||
livedebugging := NewLiveDebugging() | ||
require.False(t, livedebugging.IsRegistered("type1")) | ||
livedebugging.Register("type1") | ||
require.True(t, livedebugging.IsRegistered("type1")) | ||
// registering a component name that has already been registered does not do anything | ||
require.NotPanics(t, func() { livedebugging.Register("type1") }) | ||
} | ||
|
||
func TestStream(t *testing.T) { | ||
livedebugging := NewLiveDebugging() | ||
componentID := ComponentID("component1") | ||
CallbackID := CallbackID("callback1") | ||
|
||
var receivedData string | ||
callback := func(data string) { | ||
receivedData = data | ||
} | ||
require.False(t, livedebugging.IsActive(componentID)) | ||
livedebugging.AddCallback(CallbackID, componentID, callback) | ||
require.True(t, livedebugging.IsActive(componentID)) | ||
require.Len(t, livedebugging.callbacks[componentID], 1) | ||
|
||
livedebugging.Publish(componentID, "test data") | ||
require.Equal(t, "test data", receivedData) | ||
} | ||
|
||
func TestStreamEmpty(t *testing.T) { | ||
livedebugging := NewLiveDebugging() | ||
componentID := ComponentID("component1") | ||
require.NotPanics(t, func() { livedebugging.Publish(componentID, "test data") }) | ||
} | ||
|
||
func TestMultipleStreams(t *testing.T) { | ||
livedebugging := NewLiveDebugging() | ||
componentID := ComponentID("component1") | ||
callbackID1 := CallbackID("callback1") | ||
callbackID2 := CallbackID("callback2") | ||
|
||
var receivedData1 string | ||
callback1 := func(data string) { | ||
receivedData1 = data | ||
} | ||
|
||
var receivedData2 string | ||
callback2 := func(data string) { | ||
receivedData2 = data | ||
} | ||
|
||
livedebugging.AddCallback(callbackID1, componentID, callback1) | ||
livedebugging.AddCallback(callbackID2, componentID, callback2) | ||
require.Len(t, livedebugging.callbacks[componentID], 2) | ||
|
||
livedebugging.Publish(componentID, "test data") | ||
require.Equal(t, "test data", receivedData1) | ||
require.Equal(t, "test data", receivedData2) | ||
} | ||
|
||
func TestDeleteCallback(t *testing.T) { | ||
livedebugging := NewLiveDebugging() | ||
componentID := ComponentID("component1") | ||
callbackID1 := CallbackID("callback1") | ||
callbackID2 := CallbackID("callback2") | ||
|
||
callback1 := func(data string) {} | ||
callback2 := func(data string) {} | ||
|
||
livedebugging.AddCallback(callbackID1, componentID, callback1) | ||
livedebugging.AddCallback(callbackID2, componentID, callback2) | ||
require.Len(t, livedebugging.callbacks[componentID], 2) | ||
|
||
// Deleting callbacks that don't exist should not panic | ||
require.NotPanics(t, func() { livedebugging.DeleteCallback(callbackID1, "fakeComponentID") }) | ||
require.NotPanics(t, func() { livedebugging.DeleteCallback("fakeCallbackID", componentID) }) | ||
|
||
livedebugging.AddCallback(callbackID1, componentID, callback1) | ||
livedebugging.AddCallback(callbackID2, componentID, callback2) | ||
|
||
livedebugging.DeleteCallback(callbackID1, componentID) | ||
require.Len(t, livedebugging.callbacks[componentID], 1) | ||
|
||
livedebugging.DeleteCallback(callbackID2, componentID) | ||
require.Empty(t, livedebugging.callbacks[componentID]) | ||
} |
Oops, something went wrong.