Public source validation / validate (push) Failing after 3m8s
186 lines
4.7 KiB
Go
186 lines
4.7 KiB
Go
package live_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/live"
|
|
"github.com/itworx/pulse/internal/queryplan"
|
|
)
|
|
|
|
type blockingSampler struct {
|
|
mu sync.Mutex
|
|
calls int
|
|
started chan struct{}
|
|
release chan struct{}
|
|
}
|
|
|
|
func (s *blockingSampler) Sample(_ context.Context, _ queryplan.Request) ([]live.Sample, error) {
|
|
s.mu.Lock()
|
|
s.calls++
|
|
if s.calls == 1 {
|
|
close(s.started)
|
|
}
|
|
s.mu.Unlock()
|
|
<-s.release
|
|
value := 1.0
|
|
return []live.Sample{{Series: "shared", Timestamp: time.Now().UTC(), Value: &value, Freshness: "fresh"}}, nil
|
|
}
|
|
|
|
func (s *blockingSampler) Calls() int {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.calls
|
|
}
|
|
|
|
func sharedRequest() queryplan.Request {
|
|
now := time.Now().UTC().Truncate(time.Second)
|
|
return queryplan.Request{
|
|
Metric: "container.cpu.utilization",
|
|
Scope: map[string]string{"container": "media_server"},
|
|
Range: queryplan.Range{From: now.Add(-time.Minute), To: now, StepSeconds: 15},
|
|
Aggregation: "avg",
|
|
MaxSeries: 1,
|
|
MaxPoints: 60,
|
|
}
|
|
}
|
|
|
|
func TestRegistryDeduplicatesOverlappingSamplesAndReleasesReferences(t *testing.T) {
|
|
source := &blockingSampler{started: make(chan struct{}), release: make(chan struct{})}
|
|
registry := live.NewRegistry(source, live.RegistryOptions{MaxEntries: 4, MaxConcurrent: 1})
|
|
request := sharedRequest()
|
|
firstLease, err := registry.Acquire(request, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
secondLease, err := registry.Acquire(request, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
entries, references := registry.Active()
|
|
if entries != 1 || references != 2 {
|
|
t.Fatalf("active=%d references=%d", entries, references)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
first := make(chan error, 1)
|
|
go func() {
|
|
_, err := registry.Sample(ctx, request)
|
|
first <- err
|
|
}()
|
|
<-source.started
|
|
second := make(chan error, 1)
|
|
go func() {
|
|
_, err := registry.Sample(ctx, request)
|
|
second <- err
|
|
}()
|
|
select {
|
|
case err := <-second:
|
|
t.Fatalf("second sample completed before shared source: %v", err)
|
|
case <-time.After(25 * time.Millisecond):
|
|
}
|
|
close(source.release)
|
|
if err := <-first; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := <-second; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls := source.Calls(); calls != 1 {
|
|
t.Fatalf("upstream calls=%d, want 1", calls)
|
|
}
|
|
|
|
firstLease.Release()
|
|
entries, references = registry.Active()
|
|
if entries != 1 || references != 1 {
|
|
t.Fatalf("after first release active=%d references=%d", entries, references)
|
|
}
|
|
secondLease.Release()
|
|
entries, references = registry.Active()
|
|
if entries != 0 || references != 0 {
|
|
t.Fatalf("after final release active=%d references=%d", entries, references)
|
|
}
|
|
}
|
|
|
|
func TestRegistryWithoutSamplerFailsVisiblyInsteadOfReturningEmptySamples(t *testing.T) {
|
|
registry := live.NewRegistry(nil, live.RegistryOptions{})
|
|
request := sharedRequest()
|
|
lease, err := registry.Acquire(request, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer lease.Release()
|
|
samples, err := registry.Sample(context.Background(), request)
|
|
if !errors.Is(err, live.ErrSamplerUnavailable) {
|
|
t.Fatalf("nil sampler reported err=%v", err)
|
|
}
|
|
if samples != nil {
|
|
t.Fatalf("nil sampler returned samples=%+v", samples)
|
|
}
|
|
}
|
|
|
|
type trackingSampler struct {
|
|
mu sync.Mutex
|
|
active int
|
|
maxActive int
|
|
started chan struct{}
|
|
release chan struct{}
|
|
}
|
|
|
|
func (s *trackingSampler) Sample(_ context.Context, _ queryplan.Request) ([]live.Sample, error) {
|
|
s.mu.Lock()
|
|
s.active++
|
|
if s.active > s.maxActive {
|
|
s.maxActive = s.active
|
|
}
|
|
s.mu.Unlock()
|
|
s.started <- struct{}{}
|
|
<-s.release
|
|
s.mu.Lock()
|
|
s.active--
|
|
s.mu.Unlock()
|
|
return []live.Sample{}, nil
|
|
}
|
|
|
|
func (s *trackingSampler) MaxActive() int {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.maxActive
|
|
}
|
|
|
|
func TestRegistryBoundsEntriesAndUpstreamConcurrency(t *testing.T) {
|
|
source := &trackingSampler{started: make(chan struct{}, 2), release: make(chan struct{})}
|
|
registry := live.NewRegistry(source, live.RegistryOptions{MaxEntries: 1, MaxConcurrent: 1})
|
|
firstRequest := sharedRequest()
|
|
firstLease, err := registry.Acquire(firstRequest, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer firstLease.Release()
|
|
secondRequest := firstRequest
|
|
secondRequest.Metric = "host.cpu.utilization"
|
|
if _, err := registry.Acquire(secondRequest, time.Second); err == nil {
|
|
t.Fatal("expected registry entry limit")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
first := make(chan error, 1)
|
|
go func() {
|
|
_, err := registry.Sample(ctx, firstRequest)
|
|
first <- err
|
|
}()
|
|
<-source.started
|
|
close(source.release)
|
|
if err := <-first; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if source.MaxActive() != 1 {
|
|
t.Fatalf("max upstream concurrency=%d", source.MaxActive())
|
|
}
|
|
}
|