summaryrefslogtreecommitdiff
path: root/internal/statsengine/filerank_test.go
blob: bec5eae4ab50eef6d628c701ac7305a90770c540 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package statsengine

import (
	"fmt"
	"reflect"
	"testing"

	"ior/internal/event"
	"ior/internal/file"
	"ior/internal/types"
)

func TestFileRankerHeapEviction(t *testing.T) {
	r := newFileRankerWithConfig(2)

	r.Add(newFilePair("/a", 10, 1, types.READ_CLASSIFIED))
	r.Add(newFilePair("/b", 10, 1, types.READ_CLASSIFIED))
	r.Add(newFilePair("/b", 10, 1, types.READ_CLASSIFIED))
	r.Add(newFilePair("/c", 10, 1, types.READ_CLASSIFIED))
	r.Add(newFilePair("/c", 10, 1, types.READ_CLASSIFIED))
	r.Add(newFilePair("/c", 10, 1, types.READ_CLASSIFIED))

	got := paths(r.Snapshot())
	want := []string{"/c", "/b"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("unexpected top-n paths: got %v want %v", got, want)
	}
}

func TestFileRankerRankingCorrectness(t *testing.T) {
	r := newFileRankerWithConfig(3)

	r.Add(newFilePair("/read", 20, 11, types.READ_CLASSIFIED))
	r.Add(newFilePair("/read", 10, 9, types.READ_CLASSIFIED))
	r.Add(newFilePair("/write", 25, 7, types.WRITE_CLASSIFIED))
	r.Add(newFilePair("/write", 15, 3, types.WRITE_CLASSIFIED))
	r.Add(newFilePair("/xfer", 40, 5, types.TRANSFER_CLASSIFIED))
	r.Add(newFilePair("/xfer", 60, 6, types.TRANSFER_CLASSIFIED))

	snap := r.Snapshot()
	if len(snap) != 3 {
		t.Fatalf("expected 3 rows, got %d", len(snap))
	}

	gotOrder := paths(snap)
	wantOrder := []string{"/read", "/write", "/xfer"}
	if !reflect.DeepEqual(gotOrder, wantOrder) {
		t.Fatalf("unexpected ranking order: got %v want %v", gotOrder, wantOrder)
	}

	byPath := make(map[string]FileSnapshot, len(snap))
	for _, row := range snap {
		byPath[row.Path] = row
	}

	readRow := byPath["/read"]
	if readRow.Accesses != 2 || readRow.BytesRead != 20 || readRow.BytesWritten != 0 || readRow.MaxLatencyNs != 20 {
		t.Fatalf("unexpected /read stats: %+v", readRow)
	}
	if readRow.AvgLatencyNs != 15 {
		t.Fatalf("unexpected /read avg latency: %v", readRow.AvgLatencyNs)
	}

	writeRow := byPath["/write"]
	if writeRow.BytesRead != 0 || writeRow.BytesWritten != 10 {
		t.Fatalf("unexpected /write bytes split: %+v", writeRow)
	}

	xferRow := byPath["/xfer"]
	if xferRow.BytesRead != 11 || xferRow.BytesWritten != 11 {
		t.Fatalf("unexpected /xfer bytes split: %+v", xferRow)
	}
	if xferRow.AvgLatencyNs != 50 {
		t.Fatalf("unexpected /xfer avg latency: %v", xferRow.AvgLatencyNs)
	}
}

func TestFileRankerIgnoresInvalidFileData(t *testing.T) {
	r := newFileRankerWithConfig(3)

	r.Add(nil)
	r.Add(&event.Pair{})
	r.Add(&event.Pair{File: file.NewFd(1, "", -1), Duration: 10, Bytes: 1, ExitEv: &types.RetEvent{RetType: types.READ_CLASSIFIED}})

	if got := r.Snapshot(); len(got) != 0 {
		t.Fatalf("expected empty snapshot for invalid rows, got %+v", got)
	}
}

func TestFileRankerCompactsHighCardinality(t *testing.T) {
	r := newFileRankerWithLimits(3, 5)

	// Make one file clearly hot so it must survive compaction.
	for i := 0; i < 10; i++ {
		r.Add(newFilePair("/hot", 10, 1, types.READ_CLASSIFIED))
	}
	for i := 0; i < 200; i++ {
		r.Add(newFilePair(fmt.Sprintf("/tmp/file-%d", i), 1, 1, types.READ_CLASSIFIED))
	}

	if len(r.byPath) > r.maxSeen {
		t.Fatalf("cardinality guard failed: byPath=%d maxSeen=%d", len(r.byPath), r.maxSeen)
	}

	snap := r.Snapshot()
	if len(snap) == 0 || snap[0].Path != "/hot" {
		t.Fatalf("expected hot file to remain top-ranked after compaction, got %+v", snap)
	}
}

func newFilePair(path string, duration uint64, bytes uint64, retType uint32) *event.Pair {
	return &event.Pair{
		File:     file.NewFd(3, path, -1),
		Duration: duration,
		Bytes:    bytes,
		ExitEv:   &types.RetEvent{RetType: retType},
	}
}

func paths(rows []FileSnapshot) []string {
	out := make([]string, 0, len(rows))
	for _, row := range rows {
		out = append(out, row.Path)
	}
	return out
}