|
| 1 | +//go:build linux |
| 2 | + |
| 3 | +package memory |
| 4 | + |
| 5 | +import ( |
| 6 | + "context" |
| 7 | + "strings" |
| 8 | + "testing" |
| 9 | + "unsafe" |
| 10 | + |
| 11 | + "github.com/stretchr/testify/assert" |
| 12 | + "github.com/stretchr/testify/require" |
| 13 | + "golang.org/x/sys/unix" |
| 14 | +) |
| 15 | + |
| 16 | +func TestParseAnonRWRegions(t *testing.T) { |
| 17 | + t.Parallel() |
| 18 | + |
| 19 | + // A representative /proc/<pid>/maps excerpt: two anonymous rw regions (Go |
| 20 | + // heap arenas), plus a file-backed mapping, a [stack] pseudo-mapping, a |
| 21 | + // read-only anon mapping, and a malformed line — all of which must be |
| 22 | + // excluded. |
| 23 | + maps := strings.Join([]string{ |
| 24 | + "014a00000000-014a00200000 rw-p 00000000 00:00 0 ", |
| 25 | + "7f0000000000-7f0000001000 rw-p 00000000 08:01 1234 /usr/lib/x.so", |
| 26 | + "7ffd00000000-7ffd00021000 rw-p 00000000 00:00 0 [stack]", |
| 27 | + "560000000000-560000001000 r--p 00000000 00:00 0 ", |
| 28 | + "this is a malformed line", |
| 29 | + "014b00000000-014b00400000 rw-p 00000000 00:00 0 ", |
| 30 | + }, "\n") |
| 31 | + |
| 32 | + regions, err := parseAnonRWRegions(strings.NewReader(maps)) |
| 33 | + require.NoError(t, err) |
| 34 | + require.Len(t, regions, 2) |
| 35 | + |
| 36 | + assert.Equal(t, uintptr(0x014a00000000), regions[0].start) |
| 37 | + assert.Equal(t, uintptr(0x014a00200000), regions[0].end) |
| 38 | + assert.Equal(t, uintptr(0x014b00000000), regions[1].start) |
| 39 | + assert.Equal(t, uintptr(0x014b00400000), regions[1].end) |
| 40 | +} |
| 41 | + |
| 42 | +func TestParseAnonRWRegionsEmpty(t *testing.T) { |
| 43 | + t.Parallel() |
| 44 | + |
| 45 | + regions, err := parseAnonRWRegions(strings.NewReader("")) |
| 46 | + require.NoError(t, err) |
| 47 | + assert.Empty(t, regions) |
| 48 | +} |
| 49 | + |
| 50 | +// TestCollapseRange maps a sparsely-populated anonymous region (one touched |
| 51 | +// page per 2 MiB window) and verifies collapseRange consolidates every window. |
| 52 | +func TestCollapseRange(t *testing.T) { |
| 53 | + t.Parallel() |
| 54 | + |
| 55 | + const windows = 8 |
| 56 | + const size = windows * hugePageSize |
| 57 | + |
| 58 | + // Over-allocate by one hugepage so we can 2 MiB-align the start. |
| 59 | + raw, err := unix.Mmap(-1, 0, size+hugePageSize, |
| 60 | + unix.PROT_READ|unix.PROT_WRITE, unix.MAP_PRIVATE|unix.MAP_ANONYMOUS) |
| 61 | + require.NoError(t, err) |
| 62 | + defer func() { _ = unix.Munmap(raw) }() |
| 63 | + |
| 64 | + base := uintptr(unsafe.Pointer(&raw[0])) |
| 65 | + aligned := (base + hugePageSize - 1) &^ uintptr(hugePageSize-1) |
| 66 | + off := int(aligned - base) |
| 67 | + |
| 68 | + // Sparsely touch one byte per 2 MiB window so each window has live pages to |
| 69 | + // migrate but is far from fully present (the scattered-heap shape). |
| 70 | + for i := range windows { |
| 71 | + raw[off+i*hugePageSize] = 1 |
| 72 | + } |
| 73 | + |
| 74 | + s := collapseRange(context.Background(), aligned, aligned+uintptr(size)) |
| 75 | + assert.Equal(t, windows, s.Chunks) |
| 76 | + if s.Collapsed == 0 { |
| 77 | + t.Skip("MADV_COLLAPSE unsupported on this kernel; only window count verified") |
| 78 | + } |
| 79 | + assert.Equal(t, windows, s.Collapsed) |
| 80 | + assert.Zero(t, s.Skipped) |
| 81 | +} |
| 82 | + |
| 83 | +// TestCollapseSelf is a smoke test that collapsing the test process's own heap |
| 84 | +// scans at least one anonymous region and returns no error. |
| 85 | +func TestCollapseSelf(t *testing.T) { |
| 86 | + t.Parallel() |
| 87 | + |
| 88 | + s, err := CollapseSelf(context.Background()) |
| 89 | + require.NoError(t, err) |
| 90 | + assert.Positive(t, s.Regions) |
| 91 | + assert.Equal(t, s.Chunks, s.Collapsed+s.Skipped) |
| 92 | +} |
0 commit comments