diff options
Diffstat (limited to 'internal/syscall_aggregate_consumer_test.go')
| -rw-r--r-- | internal/syscall_aggregate_consumer_test.go | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/internal/syscall_aggregate_consumer_test.go b/internal/syscall_aggregate_consumer_test.go index 362dfba..f8ef217 100644 --- a/internal/syscall_aggregate_consumer_test.go +++ b/internal/syscall_aggregate_consumer_test.go @@ -3,9 +3,12 @@ package internal import ( "bytes" "encoding/binary" + "fmt" "testing" + "unsafe" "ior/internal/flags" + "ior/internal/statsengine" "ior/internal/types" ) @@ -69,3 +72,280 @@ func TestDecodeRawSyscallAggregateRejectsBadSize(t *testing.T) { t.Fatal("expected error for short value") } } + +func TestDecodeRawSyscallAggregatePerCPUSumsActiveCPUs(t *testing.T) { + raw := encodeRawAggregates(t, + rawSyscallAggregate{ + Count: 2, + Errors: 1, + TotalDuration: 30, + MinDuration: 10, + MaxDuration: 20, + Histogram: [8]uint64{1, 0, 1}, + }, + rawSyscallAggregate{}, + rawSyscallAggregate{ + Count: 3, + Errors: 0, + TotalDuration: 90, + MinDuration: 5, + MaxDuration: 50, + Histogram: [8]uint64{0, 2, 1}, + }, + ) + + got, err := decodeRawSyscallAggregatePerCPU(raw) + if err != nil { + t.Fatalf("decodeRawSyscallAggregatePerCPU error: %v", err) + } + + want := rawSyscallAggregate{ + Count: 5, + Errors: 1, + TotalDuration: 120, + MinDuration: 5, + MaxDuration: 50, + Histogram: [8]uint64{1, 2, 2}, + } + if got != want { + t.Fatalf("per-cpu aggregate = %+v, want %+v", got, want) + } +} + +func TestDecodeRawSyscallAggregatePerCPURejectsBadSize(t *testing.T) { + raw := encodeRawAggregates(t, rawSyscallAggregate{Count: 1}) + raw = append(raw, 0) + + if _, err := decodeRawSyscallAggregatePerCPU(raw); err == nil { + t.Fatal("expected error for non-stride-aligned value") + } +} + +func TestDecodeRawSyscallAggregatePerCPURejectsEmptyValue(t *testing.T) { + if _, err := decodeRawSyscallAggregatePerCPU(nil); err == nil { + t.Fatal("expected error for empty per-cpu value") + } +} + +func TestSyscallAggregateConsumerDrainEmitsDeltas(t *testing.T) { + const traceID = uint32(types.SYS_ENTER_FUTEX) + fakeMap := newFakeSyscallAggregateMap(traceID, encodeRawAggregates(t, + rawSyscallAggregate{ + Count: 2, + Errors: 1, + TotalDuration: 30, + MinDuration: 10, + MaxDuration: 20, + Histogram: [8]uint64{1, 0, 1}, + }, + rawSyscallAggregate{ + Count: 3, + TotalDuration: 90, + MinDuration: 5, + MaxDuration: 50, + Histogram: [8]uint64{0, 2, 1}, + }, + )) + consumer := &syscallAggregateConsumer{ + aggregateMap: fakeMap, + last: make(map[types.TraceId]rawSyscallAggregate), + } + + rows, err := consumer.Drain() + if err != nil { + t.Fatalf("first Drain error: %v", err) + } + assertAggregateRows(t, rows, statsengine.SyscallAggregate{ + TraceID: types.TraceId(traceID), + Count: 5, + Errors: 1, + TotalLatencyNs: 120, + MinLatencyNs: 5, + MaxLatencyNs: 50, + LatencyHistogramNs: [8]uint64{ + 1, 2, 2, + }, + }) + + fakeMap.values[traceID] = encodeRawAggregates(t, + rawSyscallAggregate{ + Count: 4, + Errors: 2, + TotalDuration: 80, + MinDuration: 4, + MaxDuration: 40, + Histogram: [8]uint64{2, 1, 1}, + }, + rawSyscallAggregate{ + Count: 3, + TotalDuration: 110, + MinDuration: 5, + MaxDuration: 70, + Histogram: [8]uint64{0, 2, 1, 1}, + }, + ) + rows, err = consumer.Drain() + if err != nil { + t.Fatalf("second Drain error: %v", err) + } + assertAggregateRows(t, rows, statsengine.SyscallAggregate{ + TraceID: types.TraceId(traceID), + Count: 2, + Errors: 1, + TotalLatencyNs: 70, + MinLatencyNs: 4, + MaxLatencyNs: 70, + LatencyHistogramNs: [8]uint64{ + 1, 1, 0, 1, + }, + }) + + fakeMap.values[traceID] = encodeRawAggregates(t, + rawSyscallAggregate{ + Count: 5, + Errors: 2, + TotalDuration: 100, + MinDuration: 4, + MaxDuration: 40, + Histogram: [8]uint64{3, 1, 1}, + }, + rawSyscallAggregate{ + Count: 3, + TotalDuration: 110, + MinDuration: 5, + MaxDuration: 70, + Histogram: [8]uint64{0, 2, 1, 1}, + }, + ) + rows, err = consumer.Drain() + if err != nil { + t.Fatalf("third Drain error: %v", err) + } + assertAggregateRows(t, rows, statsengine.SyscallAggregate{ + TraceID: types.TraceId(traceID), + Count: 1, + Errors: 0, + TotalLatencyNs: 20, + MinLatencyNs: 0, + MaxLatencyNs: 999, + LatencyHistogramNs: [8]uint64{ + 1, + }, + }) + + rows, err = consumer.Drain() + if err != nil { + t.Fatalf("fourth Drain error: %v", err) + } + if len(rows) != 0 { + t.Fatalf("fourth Drain rows = %+v, want none for zero delta", rows) + } +} + +func TestRawSyscallAggregateDiffReturnsOnlyNewCounts(t *testing.T) { + prev := rawSyscallAggregate{ + Count: 5, + Errors: 1, + TotalDuration: 100, + MinDuration: 10, + MaxDuration: 40, + Histogram: [8]uint64{1, 2, 2}, + } + current := rawSyscallAggregate{ + Count: 9, + Errors: 3, + TotalDuration: 190, + MinDuration: 5, + MaxDuration: 80, + Histogram: [8]uint64{2, 5, 2, 1}, + } + + got := current.diff(prev) + want := rawSyscallAggregate{ + Count: 4, + Errors: 2, + TotalDuration: 90, + MinDuration: 5, + MaxDuration: 80, + Histogram: [8]uint64{1, 3, 0, 1}, + } + if got != want { + t.Fatalf("aggregate diff = %+v, want %+v", got, want) + } +} + +func encodeRawAggregates(t *testing.T, values ...rawSyscallAggregate) []byte { + t.Helper() + + var buf bytes.Buffer + for _, value := range values { + if err := binary.Write(&buf, binary.LittleEndian, value); err != nil { + t.Fatalf("binary write: %v", err) + } + } + return buf.Bytes() +} + +func assertAggregateRows(t *testing.T, got []statsengine.SyscallAggregate, want statsengine.SyscallAggregate) { + t.Helper() + + if len(got) != 1 { + t.Fatalf("Drain rows = %+v, want one row", got) + } + if got[0] != want { + t.Fatalf("Drain row = %+v, want %+v", got[0], want) + } +} + +type fakeSyscallAggregateMap struct { + keys [][]byte + values map[uint32][]byte +} + +func newFakeSyscallAggregateMap(traceID uint32, value []byte) *fakeSyscallAggregateMap { + key := make([]byte, 4) + binary.LittleEndian.PutUint32(key, traceID) + return &fakeSyscallAggregateMap{ + keys: [][]byte{key}, + values: map[uint32][]byte{ + traceID: value, + }, + } +} + +func (m *fakeSyscallAggregateMap) Iterator() syscallAggregateIterator { + return &fakeSyscallAggregateIterator{keys: m.keys} +} + +func (m *fakeSyscallAggregateMap) GetValue(keyPtr unsafe.Pointer) ([]byte, error) { + key := *(*uint32)(keyPtr) + value, ok := m.values[key] + if !ok { + return nil, fmt.Errorf("missing value for key %d", key) + } + return append([]byte(nil), value...), nil +} + +type fakeSyscallAggregateIterator struct { + keys [][]byte + next int +} + +func (i *fakeSyscallAggregateIterator) Next() bool { + if i.next >= len(i.keys) { + return false + } + i.next++ + return i.next <= len(i.keys) +} + +func (i *fakeSyscallAggregateIterator) Key() []byte { + if i.next == 0 || i.next > len(i.keys) { + return nil + } + return i.keys[i.next-1] +} + +func (i *fakeSyscallAggregateIterator) Err() error { + return nil +} |
