github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/cmd/pebble/test.go (about)

     1  // Copyright 2018 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 main
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"os"
    12  	"os/signal"
    13  	"runtime"
    14  	"runtime/pprof"
    15  	"sort"
    16  	"sync"
    17  	"syscall"
    18  	"time"
    19  
    20  	"github.com/HdrHistogram/hdrhistogram-go"
    21  	"github.com/cockroachdb/pebble"
    22  )
    23  
    24  const (
    25  	minLatency = 10 * time.Microsecond
    26  	maxLatency = 10 * time.Second
    27  )
    28  
    29  func startCPUProfile() func() {
    30  	runtime.SetMutexProfileFraction(1000)
    31  
    32  	done := startRecording("cpu.%04d.prof", pprof.StartCPUProfile, pprof.StopCPUProfile)
    33  	return func() {
    34  		done()
    35  		if p := pprof.Lookup("heap"); p != nil {
    36  			f, err := os.Create("heap.prof")
    37  			if err != nil {
    38  				log.Fatal(err)
    39  			}
    40  			if err := p.WriteTo(f, 0); err != nil {
    41  				log.Fatal(err)
    42  			}
    43  			f.Close()
    44  		}
    45  		if p := pprof.Lookup("mutex"); p != nil {
    46  			f, err := os.Create("mutex.prof")
    47  			if err != nil {
    48  				log.Fatal(err)
    49  			}
    50  			if err := p.WriteTo(f, 0); err != nil {
    51  				log.Fatal(err)
    52  			}
    53  			f.Close()
    54  		}
    55  	}
    56  }
    57  
    58  func startRecording(fmtStr string, startFunc func(io.Writer) error, stopFunc func()) func() {
    59  	doneCh := make(chan struct{})
    60  	var doneWG sync.WaitGroup
    61  	doneWG.Add(1)
    62  
    63  	go func() {
    64  		defer doneWG.Done()
    65  
    66  		start := time.Now()
    67  		t := time.NewTicker(10 * time.Second)
    68  		defer t.Stop()
    69  
    70  		var current *os.File
    71  		defer func() {
    72  			if current != nil {
    73  				stopFunc()
    74  				current.Close()
    75  			}
    76  		}()
    77  
    78  		for {
    79  			if current != nil {
    80  				stopFunc()
    81  				current.Close()
    82  				current = nil
    83  			}
    84  			path := fmt.Sprintf(fmtStr, int(time.Since(start).Seconds()+0.5))
    85  			f, err := os.Create(path)
    86  			if err != nil {
    87  				log.Fatalf("unable to create cpu profile: %s", err)
    88  				return
    89  			}
    90  			if err := startFunc(f); err != nil {
    91  				log.Fatalf("unable to start cpu profile: %v", err)
    92  				f.Close()
    93  				return
    94  			}
    95  			current = f
    96  
    97  			select {
    98  			case <-doneCh:
    99  				return
   100  			case <-t.C:
   101  			}
   102  		}
   103  	}()
   104  
   105  	return func() {
   106  		close(doneCh)
   107  		doneWG.Wait()
   108  	}
   109  }
   110  
   111  func newHistogram() *hdrhistogram.Histogram {
   112  	return hdrhistogram.New(minLatency.Nanoseconds(), maxLatency.Nanoseconds(), 1)
   113  }
   114  
   115  type namedHistogram struct {
   116  	name string
   117  	mu   struct {
   118  		sync.Mutex
   119  		current *hdrhistogram.Histogram
   120  	}
   121  }
   122  
   123  func newNamedHistogram(name string) *namedHistogram {
   124  	w := &namedHistogram{name: name}
   125  	w.mu.current = newHistogram()
   126  	return w
   127  }
   128  
   129  func (w *namedHistogram) Record(elapsed time.Duration) {
   130  	if elapsed < minLatency {
   131  		elapsed = minLatency
   132  	} else if elapsed > maxLatency {
   133  		elapsed = maxLatency
   134  	}
   135  
   136  	w.mu.Lock()
   137  	err := w.mu.current.RecordValue(elapsed.Nanoseconds())
   138  	w.mu.Unlock()
   139  
   140  	if err != nil {
   141  		// Note that a histogram only drops recorded values that are out of range,
   142  		// but we clamp the latency value to the configured range to prevent such
   143  		// drops. This code path should never happen.
   144  		panic(fmt.Sprintf(`%s: recording value: %s`, w.name, err))
   145  	}
   146  }
   147  
   148  func (w *namedHistogram) tick(fn func(h *hdrhistogram.Histogram)) {
   149  	w.mu.Lock()
   150  	defer w.mu.Unlock()
   151  	h := w.mu.current
   152  	w.mu.current = newHistogram()
   153  	fn(h)
   154  }
   155  
   156  type histogramTick struct {
   157  	// Name is the name given to the histograms represented by this tick.
   158  	Name string
   159  	// Hist is the merged result of the represented histograms for this tick.
   160  	// Hist.TotalCount() is the number of operations that occurred for this tick.
   161  	Hist *hdrhistogram.Histogram
   162  	// Cumulative is the merged result of the represented histograms for all
   163  	// time. Cumulative.TotalCount() is the total number of operations that have
   164  	// occurred over all time.
   165  	Cumulative *hdrhistogram.Histogram
   166  	// Elapsed is the amount of time since the last tick.
   167  	Elapsed time.Duration
   168  	// Now is the time at which the tick was gathered. It covers the period
   169  	// [Now-Elapsed,Now).
   170  	Now time.Time
   171  }
   172  
   173  type histogramRegistry struct {
   174  	mu struct {
   175  		sync.Mutex
   176  		registered []*namedHistogram
   177  	}
   178  
   179  	start      time.Time
   180  	cumulative map[string]*hdrhistogram.Histogram
   181  	prevTick   map[string]time.Time
   182  }
   183  
   184  func newHistogramRegistry() *histogramRegistry {
   185  	return &histogramRegistry{
   186  		start:      time.Now(),
   187  		cumulative: make(map[string]*hdrhistogram.Histogram),
   188  		prevTick:   make(map[string]time.Time),
   189  	}
   190  }
   191  
   192  func (w *histogramRegistry) Register(name string) *namedHistogram {
   193  	hist := newNamedHistogram(name)
   194  
   195  	w.mu.Lock()
   196  	w.mu.registered = append(w.mu.registered, hist)
   197  	w.mu.Unlock()
   198  
   199  	return hist
   200  }
   201  
   202  func (w *histogramRegistry) Tick(fn func(histogramTick)) {
   203  	w.mu.Lock()
   204  	registered := append([]*namedHistogram(nil), w.mu.registered...)
   205  	w.mu.Unlock()
   206  
   207  	merged := make(map[string]*hdrhistogram.Histogram)
   208  	var names []string
   209  	for _, hist := range registered {
   210  		hist.tick(func(h *hdrhistogram.Histogram) {
   211  			if p, ok := merged[hist.name]; ok {
   212  				p.Merge(h)
   213  			} else {
   214  				merged[hist.name] = h
   215  				names = append(names, hist.name)
   216  			}
   217  		})
   218  	}
   219  
   220  	now := time.Now()
   221  	sort.Strings(names)
   222  	for _, name := range names {
   223  		mergedHist := merged[name]
   224  		if _, ok := w.cumulative[name]; !ok {
   225  			w.cumulative[name] = newHistogram()
   226  		}
   227  		w.cumulative[name].Merge(mergedHist)
   228  
   229  		prevTick, ok := w.prevTick[name]
   230  		if !ok {
   231  			prevTick = w.start
   232  		}
   233  		w.prevTick[name] = now
   234  		fn(histogramTick{
   235  			Name:       name,
   236  			Hist:       merged[name],
   237  			Cumulative: w.cumulative[name],
   238  			Elapsed:    now.Sub(prevTick),
   239  			Now:        now,
   240  		})
   241  	}
   242  }
   243  
   244  type testWithoutDB struct {
   245  	init func(wg *sync.WaitGroup)
   246  	tick func(elapsed time.Duration, i int)
   247  	done func(wg *sync.WaitGroup, elapsed time.Duration)
   248  }
   249  
   250  func runTestWithoutDB(t testWithoutDB) {
   251  	var wg sync.WaitGroup
   252  	t.init(&wg)
   253  
   254  	ticker := time.NewTicker(time.Second)
   255  	defer ticker.Stop()
   256  
   257  	done := make(chan os.Signal, 3)
   258  	workersDone := make(chan struct{})
   259  	signal.Notify(done, os.Interrupt)
   260  
   261  	go func() {
   262  		wg.Wait()
   263  		close(workersDone)
   264  	}()
   265  
   266  	if duration > 0 {
   267  		go func() {
   268  			time.Sleep(duration)
   269  			done <- syscall.Signal(0)
   270  		}()
   271  	}
   272  
   273  	stopProf := startCPUProfile()
   274  	defer stopProf()
   275  
   276  	start := time.Now()
   277  	for i := 0; ; i++ {
   278  		select {
   279  		case <-ticker.C:
   280  			if workersDone != nil {
   281  				t.tick(time.Since(start), i)
   282  			}
   283  
   284  		case <-workersDone:
   285  			workersDone = nil
   286  			t.done(&wg, time.Since(start))
   287  			return
   288  
   289  		case sig := <-done:
   290  			fmt.Println("operating system is killing the op.", sig)
   291  			if workersDone != nil {
   292  				t.done(&wg, time.Since(start))
   293  			}
   294  			return
   295  		}
   296  	}
   297  }
   298  
   299  type test struct {
   300  	init func(db DB, wg *sync.WaitGroup)
   301  	tick func(elapsed time.Duration, i int)
   302  	done func(elapsed time.Duration)
   303  }
   304  
   305  func runTest(dir string, t test) {
   306  	// Check if the directory exists.
   307  	if wipe {
   308  		fmt.Printf("wiping %s\n", dir)
   309  		if err := os.RemoveAll(dir); err != nil {
   310  			log.Fatal(err)
   311  		}
   312  	}
   313  
   314  	fmt.Printf("dir %s\nconcurrency %d\n", dir, concurrency)
   315  
   316  	db := newPebbleDB(dir)
   317  	var wg sync.WaitGroup
   318  	t.init(db, &wg)
   319  
   320  	ticker := time.NewTicker(time.Second)
   321  	defer ticker.Stop()
   322  
   323  	done := make(chan os.Signal, 3)
   324  	workersDone := make(chan struct{})
   325  	signal.Notify(done, os.Interrupt)
   326  
   327  	go func() {
   328  		wg.Wait()
   329  		close(workersDone)
   330  	}()
   331  
   332  	if maxSize > 0 {
   333  		go func() {
   334  			for {
   335  				time.Sleep(10 * time.Second)
   336  				if db.Metrics().DiskSpaceUsage() > maxSize*1e6 {
   337  					fmt.Println("max size reached")
   338  					done <- syscall.Signal(0)
   339  				}
   340  			}
   341  		}()
   342  	}
   343  	if duration > 0 {
   344  		go func() {
   345  			time.Sleep(duration)
   346  			done <- syscall.Signal(0)
   347  		}()
   348  	}
   349  
   350  	stopProf := startCPUProfile()
   351  	defer stopProf()
   352  
   353  	backgroundCompactions := func(p *pebble.Metrics) bool {
   354  		// The last level never gets selected as an input level for compaction,
   355  		// only as an output level, so ignore it for the purposes of determining if
   356  		// background compactions are still needed.
   357  		for i := range p.Levels[:len(p.Levels)-1] {
   358  			if p.Levels[i].Score > 1 {
   359  				return true
   360  			}
   361  		}
   362  		return false
   363  	}
   364  
   365  	start := time.Now()
   366  	for i := 0; ; i++ {
   367  		select {
   368  		case <-ticker.C:
   369  			if workersDone != nil {
   370  				t.tick(time.Since(start), i)
   371  				if verbose && (i%10) == 9 {
   372  					fmt.Printf("%s", db.Metrics())
   373  				}
   374  			} else if waitCompactions {
   375  				p := db.Metrics()
   376  				fmt.Printf("%s", p)
   377  				if !backgroundCompactions(p) {
   378  					return
   379  				}
   380  			}
   381  
   382  		case <-workersDone:
   383  			workersDone = nil
   384  			t.done(time.Since(start))
   385  			p := db.Metrics()
   386  			fmt.Printf("%s", p)
   387  			if !waitCompactions || !backgroundCompactions(p) {
   388  				return
   389  			}
   390  			fmt.Printf("waiting for background compactions\n")
   391  
   392  		case <-done:
   393  			if workersDone != nil {
   394  				t.done(time.Since(start))
   395  			}
   396  			fmt.Printf("%s", db.Metrics())
   397  			return
   398  		}
   399  	}
   400  }