github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/objstorage/objstorageprovider/objiotracing/obj_io_tracing_test.go (about)

     1  // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package objiotracing_test
     6  
     7  import (
     8  	"io"
     9  	"strings"
    10  	"testing"
    11  	"unsafe"
    12  
    13  	"github.com/cockroachdb/pebble"
    14  	"github.com/cockroachdb/pebble/internal/base"
    15  	"github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing"
    16  	"github.com/cockroachdb/pebble/vfs"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  type Event = objiotracing.Event
    21  
    22  const eventSize = int(unsafe.Sizeof(Event{}))
    23  
    24  func TestTracing(t *testing.T) {
    25  	if !objiotracing.Enabled {
    26  		t.Skipf("test can only be run under pebble_obj_io_tracing build tag")
    27  	}
    28  	fs := vfs.NewMem()
    29  	d, err := pebble.Open("", &pebble.Options{FS: fs})
    30  	require.NoError(t, err)
    31  
    32  	require.NoError(t, d.Set([]byte("a"), []byte("aaa"), nil))
    33  	require.NoError(t, d.Set([]byte("b"), []byte("bbb"), nil))
    34  	require.NoError(t, d.Flush())
    35  	require.NoError(t, d.Set([]byte("c"), []byte("ccc"), nil))
    36  	require.NoError(t, d.Flush())
    37  	require.NoError(t, d.Compact([]byte("a"), []byte("z"), false /* parallelize */))
    38  	require.NoError(t, d.Set([]byte("b"), []byte("bbb2"), nil))
    39  	require.NoError(t, d.Set([]byte("c"), []byte("ccc2"), nil))
    40  	require.NoError(t, d.Set([]byte("d"), []byte("ddd"), nil))
    41  	require.NoError(t, d.Flush())
    42  	require.NoError(t, d.Compact([]byte("a"), []byte("z"), false /* parallelize */))
    43  	require.NoError(t, d.Close())
    44  
    45  	collectEvents := func() []Event {
    46  		t.Helper()
    47  
    48  		list, err := fs.List("")
    49  		require.NoError(t, err)
    50  
    51  		var events []Event
    52  		for _, f := range list {
    53  			if strings.HasPrefix(f, "IOTRACES-") {
    54  				file, err := fs.Open(f)
    55  				require.NoError(t, err)
    56  				data, err := io.ReadAll(file)
    57  				file.Close()
    58  				require.NoError(t, err)
    59  				// Remove the file so we don't read these events again later.
    60  				fs.Remove(f)
    61  				if len(data) == 0 {
    62  					continue
    63  				}
    64  				require.Equal(t, len(data)%eventSize, 0)
    65  				p := unsafe.Pointer(&data[0])
    66  				asEvents := unsafe.Slice((*Event)(p), len(data)/eventSize)
    67  				events = append(events, asEvents...)
    68  			}
    69  		}
    70  		if testing.Verbose() {
    71  			t.Logf("collected events:")
    72  			for _, e := range events {
    73  				t.Logf("  %#v", e)
    74  			}
    75  		}
    76  		return events
    77  	}
    78  	events := collectEvents()
    79  	num := func(check func(e Event) bool) int {
    80  		res := 0
    81  		for _, e := range events {
    82  			if check(e) {
    83  				res += 1
    84  			}
    85  		}
    86  		return res
    87  	}
    88  	// Check that we saw at least a few reads and writes.
    89  	// TODO(radu): check more fields when they are populated.
    90  	require.Greater(t, num(func(e Event) bool { return e.Op == objiotracing.ReadOp }), 5)
    91  	require.Greater(t, num(func(e Event) bool { return e.Op == objiotracing.WriteOp }), 5)
    92  
    93  	// We should see writes at L0 and L7.
    94  	require.Greater(t, num(func(e Event) bool { return e.Op == objiotracing.WriteOp && e.LevelPlusOne == 1 }), 0)
    95  	require.Greater(t, num(func(e Event) bool { return e.Op == objiotracing.WriteOp && e.LevelPlusOne == 7 }), 0)
    96  
    97  	// Check that we saw writes for flushing and for compaction.
    98  	require.Greater(t, num(func(e Event) bool { return e.Reason == objiotracing.ForFlush }), 0)
    99  	require.Greater(t, num(func(e Event) bool { return e.Reason == objiotracing.ForCompaction }), 0)
   100  
   101  	// Check that offset is set on reads & writes as expected.
   102  	require.Greater(t, num(func(e Event) bool { return e.Op == objiotracing.ReadOp && e.Offset > 0 }), 0)
   103  	require.Greater(t, num(func(e Event) bool { return e.Op == objiotracing.WriteOp && e.Offset > 0 }), 0)
   104  
   105  	// Check that the FileNums are set and that we see at least two different files.
   106  	fileNums := make(map[base.FileNum]int)
   107  	for _, e := range events {
   108  		require.NotZero(t, e.FileNum)
   109  		fileNums[e.FileNum] += 1
   110  	}
   111  	require.GreaterOrEqual(t, len(fileNums), 2)
   112  
   113  	// Open again and do some reads.
   114  	d, err = pebble.Open("", &pebble.Options{FS: fs})
   115  	require.NoError(t, err)
   116  	for _, k := range []string{"0", "a", "d", "ccc", "b"} {
   117  		_, closer, err := d.Get([]byte(k))
   118  		if err == pebble.ErrNotFound {
   119  			continue
   120  		}
   121  		require.NoError(t, err)
   122  		closer.Close()
   123  	}
   124  	require.NoError(t, d.Close())
   125  	events = collectEvents()
   126  	// Expect L6 data block reads.
   127  	require.Greater(t, num(func(e Event) bool {
   128  		return e.Op == objiotracing.ReadOp && e.BlockType == objiotracing.DataBlock && e.LevelPlusOne == 7
   129  	}), 0)
   130  }