-
Notifications
You must be signed in to change notification settings - Fork 0
feat(api): Add Server-Sent Events #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
2ccd6bd
3030bef
c4dbce4
87d9b44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package api | ||
|
|
||
| import "sync/atomic" | ||
|
|
||
| // ConnectionTracker tracks concurrent streaming connections against a limit | ||
| type ConnectionTracker struct { | ||
| count atomic.Int32 | ||
| max int32 | ||
| } | ||
|
|
||
| // NewConnectionTracker creates a new connection tracker with the given limit | ||
| func NewConnectionTracker(limit int) *ConnectionTracker { | ||
| return &ConnectionTracker{max: int32(limit)} //nolint:gosec // config-validated positive int, no overflow risk | ||
| } | ||
|
|
||
| // Acquire attempts to claim a connection slot, returns false if at limit | ||
| func (t *ConnectionTracker) Acquire() bool { | ||
| for { | ||
| current := t.count.Load() | ||
| if current >= t.max { | ||
| return false | ||
| } | ||
|
|
||
| if t.count.CompareAndSwap(current, current+1) { | ||
| return true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Release frees a connection slot | ||
| func (t *ConnectionTracker) Release() { | ||
| for { | ||
| current := t.count.Load() | ||
| if current <= 0 { | ||
| return | ||
| } | ||
|
|
||
| if t.count.CompareAndSwap(current, current-1) { | ||
| return | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Count returns the current number of active connections | ||
| func (t *ConnectionTracker) Count() int { | ||
| return int(t.count.Load()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "sync" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func Test_ConnectionTracker_Acquire(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| max int | ||
| acquire int | ||
| want bool | ||
| }{ | ||
| { | ||
| name: "within limit", | ||
| max: 3, | ||
| acquire: 1, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "at limit", | ||
| max: 2, | ||
| acquire: 3, | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "single slot", | ||
| max: 1, | ||
| acquire: 1, | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "single slot exceeded", | ||
| max: 1, | ||
| acquire: 2, | ||
| want: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| tracker := NewConnectionTracker(tt.max) | ||
|
|
||
| var ok bool | ||
| for range tt.acquire { | ||
| ok = tracker.Acquire() | ||
| } | ||
|
|
||
| assert.Equal(t, tt.want, ok) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func Test_ConnectionTracker_Release(t *testing.T) { | ||
| tracker := NewConnectionTracker(1) | ||
|
|
||
| require.True(t, tracker.Acquire()) | ||
| require.False(t, tracker.Acquire()) | ||
|
|
||
| tracker.Release() | ||
|
|
||
| assert.True(t, tracker.Acquire()) | ||
| } | ||
|
|
||
| func Test_ConnectionTracker_DoubleRelease(t *testing.T) { | ||
| tracker := NewConnectionTracker(1) | ||
|
|
||
| require.True(t, tracker.Acquire()) | ||
| tracker.Release() | ||
| tracker.Release() | ||
|
|
||
| assert.Equal(t, 0, tracker.Count()) | ||
|
|
||
| require.True(t, tracker.Acquire()) | ||
| assert.False(t, tracker.Acquire()) | ||
| } | ||
|
|
||
| func Test_ConnectionTracker_ReleaseWithoutAcquire(t *testing.T) { | ||
| tracker := NewConnectionTracker(1) | ||
|
|
||
| tracker.Release() | ||
|
|
||
| assert.Equal(t, 0, tracker.Count()) | ||
| } | ||
|
|
||
| func Test_ConnectionTracker_Count(t *testing.T) { | ||
| tracker := NewConnectionTracker(5) | ||
|
|
||
| assert.Equal(t, 0, tracker.Count()) | ||
|
|
||
| tracker.Acquire() | ||
| tracker.Acquire() | ||
| assert.Equal(t, 2, tracker.Count()) | ||
|
|
||
| tracker.Release() | ||
| assert.Equal(t, 1, tracker.Count()) | ||
| } | ||
|
|
||
| func Test_ConnectionTracker_Concurrent(t *testing.T) { | ||
| tracker := NewConnectionTracker(10) | ||
|
|
||
| var wg sync.WaitGroup | ||
|
|
||
| for range 20 { | ||
| wg.Go(func() { | ||
| if tracker.Acquire() { | ||
| defer tracker.Release() | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| wg.Wait() | ||
|
|
||
| assert.Equal(t, 0, tracker.Count()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -534,12 +534,33 @@ func (s *store) sampleStats(ctx context.Context) { | |
| } | ||
|
|
||
| s.mu.Lock() | ||
| defer s.mu.Unlock() | ||
|
|
||
| for id, st := range stats { | ||
| if svc, exists := s.services[id]; exists && svc.pid == pids[id] { | ||
| svc.cpu = st.CPU | ||
| svc.memory = st.RawMEM | ||
| samples := make([]bus.ServiceMetrics, 0, len(stats)) | ||
|
|
||
| for id, stat := range stats { | ||
| service, exists := s.services[id] | ||
| if !exists || service.pid != pids[id] { | ||
| continue | ||
| } | ||
|
|
||
| service.cpu = stat.CPU | ||
| service.memory = stat.RawMEM | ||
|
|
||
| if service.status.IsRunning() { | ||
| samples = append(samples, bus.ServiceMetrics{ | ||
| Service: bus.Service{ID: service.id, Name: service.name}, | ||
| CPU: stat.CPU, | ||
| Memory: stat.RawMEM, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| s.mu.Unlock() | ||
|
|
||
| if len(samples) > 0 { | ||
| s.bus.Publish(bus.Message{ | ||
| Type: bus.EventServiceMetrics, | ||
| Data: bus.ServiceMetricsBatch{Samples: samples}, | ||
| }) | ||
| } | ||
|
Comment on lines
+560
to
+565
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.