github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/event.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 pebble
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/cockroachdb/errors"
    13  	"github.com/cockroachdb/pebble/internal/base"
    14  	"github.com/cockroachdb/pebble/internal/humanize"
    15  	"github.com/cockroachdb/pebble/internal/invariants"
    16  	"github.com/cockroachdb/pebble/internal/manifest"
    17  	"github.com/cockroachdb/pebble/vfs"
    18  	"github.com/cockroachdb/redact"
    19  )
    20  
    21  // TableInfo exports the manifest.TableInfo type.
    22  type TableInfo = manifest.TableInfo
    23  
    24  func tablesTotalSize(tables []TableInfo) uint64 {
    25  	var size uint64
    26  	for i := range tables {
    27  		size += tables[i].Size
    28  	}
    29  	return size
    30  }
    31  
    32  func formatFileNums(tables []TableInfo) string {
    33  	var buf strings.Builder
    34  	for i := range tables {
    35  		if i > 0 {
    36  			buf.WriteString(" ")
    37  		}
    38  		buf.WriteString(tables[i].FileNum.String())
    39  	}
    40  	return buf.String()
    41  }
    42  
    43  // LevelInfo contains info pertaining to a particular level.
    44  type LevelInfo struct {
    45  	Level  int
    46  	Tables []TableInfo
    47  	Score  float64
    48  }
    49  
    50  func (i LevelInfo) String() string {
    51  	return redact.StringWithoutMarkers(i)
    52  }
    53  
    54  // SafeFormat implements redact.SafeFormatter.
    55  func (i LevelInfo) SafeFormat(w redact.SafePrinter, _ rune) {
    56  	w.Printf("L%d [%s] (%s) Score=%.2f",
    57  		redact.Safe(i.Level),
    58  		redact.Safe(formatFileNums(i.Tables)),
    59  		redact.Safe(humanize.Bytes.Uint64(tablesTotalSize(i.Tables))),
    60  		redact.Safe(i.Score))
    61  }
    62  
    63  // CompactionInfo contains the info for a compaction event.
    64  type CompactionInfo struct {
    65  	// JobID is the ID of the compaction job.
    66  	JobID int
    67  	// Reason is the reason for the compaction.
    68  	Reason string
    69  	// Input contains the input tables for the compaction organized by level.
    70  	Input []LevelInfo
    71  	// Output contains the output tables generated by the compaction. The output
    72  	// tables are empty for the compaction begin event.
    73  	Output LevelInfo
    74  	// Duration is the time spent compacting, including reading and writing
    75  	// sstables.
    76  	Duration time.Duration
    77  	// TotalDuration is the total wall-time duration of the compaction,
    78  	// including applying the compaction to the database. TotalDuration is
    79  	// always ≥ Duration.
    80  	TotalDuration time.Duration
    81  	Done          bool
    82  	Err           error
    83  
    84  	SingleLevelOverlappingRatio float64
    85  	MultiLevelOverlappingRatio  float64
    86  
    87  	// Annotations specifies additional info to appear in a compaction's event log line
    88  	Annotations compactionAnnotations
    89  }
    90  
    91  type compactionAnnotations []string
    92  
    93  // SafeFormat implements redact.SafeFormatter.
    94  func (ca compactionAnnotations) SafeFormat(w redact.SafePrinter, _ rune) {
    95  	if len(ca) == 0 {
    96  		return
    97  	}
    98  	for i := range ca {
    99  		if i != 0 {
   100  			w.Print(" ")
   101  		}
   102  		w.Printf("%s", redact.SafeString(ca[i]))
   103  	}
   104  }
   105  
   106  func (i CompactionInfo) String() string {
   107  	return redact.StringWithoutMarkers(i)
   108  }
   109  
   110  // SafeFormat implements redact.SafeFormatter.
   111  func (i CompactionInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   112  	if i.Err != nil {
   113  		w.Printf("[JOB %d] compaction(%s) to L%d error: %s",
   114  			redact.Safe(i.JobID), redact.SafeString(i.Reason), redact.Safe(i.Output.Level), i.Err)
   115  		return
   116  	}
   117  
   118  	if !i.Done {
   119  		w.Printf("[JOB %d] compacting(%s) ",
   120  			redact.Safe(i.JobID),
   121  			redact.SafeString(i.Reason))
   122  		w.Printf("%s", i.Annotations)
   123  		w.Printf("%s; ", levelInfos(i.Input))
   124  		w.Printf("OverlappingRatio: Single %.2f, Multi %.2f", i.SingleLevelOverlappingRatio, i.MultiLevelOverlappingRatio)
   125  		return
   126  	}
   127  	outputSize := tablesTotalSize(i.Output.Tables)
   128  	w.Printf("[JOB %d] compacted(%s) ", redact.Safe(i.JobID), redact.SafeString(i.Reason))
   129  	w.Printf("%s", i.Annotations)
   130  	w.Print(levelInfos(i.Input))
   131  	w.Printf(" -> L%d [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
   132  		redact.Safe(i.Output.Level),
   133  		redact.Safe(formatFileNums(i.Output.Tables)),
   134  		redact.Safe(humanize.Bytes.Uint64(outputSize)),
   135  		redact.Safe(i.Duration.Seconds()),
   136  		redact.Safe(i.TotalDuration.Seconds()),
   137  		redact.Safe(humanize.Bytes.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
   138  }
   139  
   140  type levelInfos []LevelInfo
   141  
   142  func (i levelInfos) SafeFormat(w redact.SafePrinter, _ rune) {
   143  	for j, levelInfo := range i {
   144  		if j > 0 {
   145  			w.Printf(" + ")
   146  		}
   147  		w.Print(levelInfo)
   148  	}
   149  }
   150  
   151  // DiskSlowInfo contains the info for a disk slowness event when writing to a
   152  // file.
   153  type DiskSlowInfo = vfs.DiskSlowInfo
   154  
   155  // FlushInfo contains the info for a flush event.
   156  type FlushInfo struct {
   157  	// JobID is the ID of the flush job.
   158  	JobID int
   159  	// Reason is the reason for the flush.
   160  	Reason string
   161  	// Input contains the count of input memtables that were flushed.
   162  	Input int
   163  	// InputBytes contains the total in-memory size of the memtable(s) that were
   164  	// flushed. This size includes skiplist indexing data structures.
   165  	InputBytes uint64
   166  	// Output contains the ouptut table generated by the flush. The output info
   167  	// is empty for the flush begin event.
   168  	Output []TableInfo
   169  	// Duration is the time spent flushing. This duration includes writing and
   170  	// syncing all of the flushed keys to sstables.
   171  	Duration time.Duration
   172  	// TotalDuration is the total wall-time duration of the flush, including
   173  	// applying the flush to the database. TotalDuration is always ≥ Duration.
   174  	TotalDuration time.Duration
   175  	// Ingest is set to true if the flush is handling tables that were added to
   176  	// the flushable queue via an ingestion operation.
   177  	Ingest bool
   178  	// IngestLevels are the output levels for each ingested table in the flush.
   179  	// This field is only populated when Ingest is true.
   180  	IngestLevels []int
   181  	Done         bool
   182  	Err          error
   183  }
   184  
   185  func (i FlushInfo) String() string {
   186  	return redact.StringWithoutMarkers(i)
   187  }
   188  
   189  // SafeFormat implements redact.SafeFormatter.
   190  func (i FlushInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   191  	if i.Err != nil {
   192  		w.Printf("[JOB %d] flush error: %s", redact.Safe(i.JobID), i.Err)
   193  		return
   194  	}
   195  
   196  	plural := redact.SafeString("s")
   197  	if i.Input == 1 {
   198  		plural = ""
   199  	}
   200  	if !i.Done {
   201  		w.Printf("[JOB %d] ", redact.Safe(i.JobID))
   202  		if !i.Ingest {
   203  			w.Printf("flushing %d memtable", redact.Safe(i.Input))
   204  			w.SafeString(plural)
   205  			w.Printf(" (%s) to L0", redact.Safe(humanize.Bytes.Uint64(i.InputBytes)))
   206  		} else {
   207  			w.Printf("flushing %d ingested table%s", redact.Safe(i.Input), plural)
   208  		}
   209  		return
   210  	}
   211  
   212  	outputSize := tablesTotalSize(i.Output)
   213  	if !i.Ingest {
   214  		if invariants.Enabled && len(i.IngestLevels) > 0 {
   215  			panic(errors.AssertionFailedf("pebble: expected len(IngestedLevels) == 0"))
   216  		}
   217  		w.Printf("[JOB %d] flushed %d memtable%s (%s) to L0 [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
   218  			redact.Safe(i.JobID), redact.Safe(i.Input), plural,
   219  			redact.Safe(humanize.Bytes.Uint64(i.InputBytes)),
   220  			redact.Safe(formatFileNums(i.Output)),
   221  			redact.Safe(humanize.Bytes.Uint64(outputSize)),
   222  			redact.Safe(i.Duration.Seconds()),
   223  			redact.Safe(i.TotalDuration.Seconds()),
   224  			redact.Safe(humanize.Bytes.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
   225  	} else {
   226  		if invariants.Enabled && len(i.IngestLevels) == 0 {
   227  			panic(errors.AssertionFailedf("pebble: expected len(IngestedLevels) > 0"))
   228  		}
   229  		w.Printf("[JOB %d] flushed %d ingested flushable%s",
   230  			redact.Safe(i.JobID), redact.Safe(len(i.Output)), plural)
   231  		for j, level := range i.IngestLevels {
   232  			file := i.Output[j]
   233  			if j > 0 {
   234  				w.Printf(" +")
   235  			}
   236  			w.Printf(" L%d:%s (%s)", level, file.FileNum, humanize.Bytes.Uint64(file.Size))
   237  		}
   238  		w.Printf(" in %.1fs (%.1fs total), output rate %s/s",
   239  			redact.Safe(i.Duration.Seconds()),
   240  			redact.Safe(i.TotalDuration.Seconds()),
   241  			redact.Safe(humanize.Bytes.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
   242  	}
   243  }
   244  
   245  // ManifestCreateInfo contains info about a manifest creation event.
   246  type ManifestCreateInfo struct {
   247  	// JobID is the ID of the job the caused the manifest to be created.
   248  	JobID int
   249  	Path  string
   250  	// The file number of the new Manifest.
   251  	FileNum base.DiskFileNum
   252  	Err     error
   253  }
   254  
   255  func (i ManifestCreateInfo) String() string {
   256  	return redact.StringWithoutMarkers(i)
   257  }
   258  
   259  // SafeFormat implements redact.SafeFormatter.
   260  func (i ManifestCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   261  	if i.Err != nil {
   262  		w.Printf("[JOB %d] MANIFEST create error: %s", redact.Safe(i.JobID), i.Err)
   263  		return
   264  	}
   265  	w.Printf("[JOB %d] MANIFEST created %s", redact.Safe(i.JobID), i.FileNum)
   266  }
   267  
   268  // ManifestDeleteInfo contains the info for a Manifest deletion event.
   269  type ManifestDeleteInfo struct {
   270  	// JobID is the ID of the job the caused the Manifest to be deleted.
   271  	JobID   int
   272  	Path    string
   273  	FileNum FileNum
   274  	Err     error
   275  }
   276  
   277  func (i ManifestDeleteInfo) String() string {
   278  	return redact.StringWithoutMarkers(i)
   279  }
   280  
   281  // SafeFormat implements redact.SafeFormatter.
   282  func (i ManifestDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   283  	if i.Err != nil {
   284  		w.Printf("[JOB %d] MANIFEST delete error: %s", redact.Safe(i.JobID), i.Err)
   285  		return
   286  	}
   287  	w.Printf("[JOB %d] MANIFEST deleted %s", redact.Safe(i.JobID), i.FileNum)
   288  }
   289  
   290  // TableCreateInfo contains the info for a table creation event.
   291  type TableCreateInfo struct {
   292  	JobID int
   293  	// Reason is the reason for the table creation: "compacting", "flushing", or
   294  	// "ingesting".
   295  	Reason  string
   296  	Path    string
   297  	FileNum FileNum
   298  }
   299  
   300  func (i TableCreateInfo) String() string {
   301  	return redact.StringWithoutMarkers(i)
   302  }
   303  
   304  // SafeFormat implements redact.SafeFormatter.
   305  func (i TableCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   306  	w.Printf("[JOB %d] %s: sstable created %s",
   307  		redact.Safe(i.JobID), redact.Safe(i.Reason), i.FileNum)
   308  }
   309  
   310  // TableDeleteInfo contains the info for a table deletion event.
   311  type TableDeleteInfo struct {
   312  	JobID   int
   313  	Path    string
   314  	FileNum FileNum
   315  	Err     error
   316  }
   317  
   318  func (i TableDeleteInfo) String() string {
   319  	return redact.StringWithoutMarkers(i)
   320  }
   321  
   322  // SafeFormat implements redact.SafeFormatter.
   323  func (i TableDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   324  	if i.Err != nil {
   325  		w.Printf("[JOB %d] sstable delete error %s: %s",
   326  			redact.Safe(i.JobID), i.FileNum, i.Err)
   327  		return
   328  	}
   329  	w.Printf("[JOB %d] sstable deleted %s", redact.Safe(i.JobID), i.FileNum)
   330  }
   331  
   332  // TableIngestInfo contains the info for a table ingestion event.
   333  type TableIngestInfo struct {
   334  	// JobID is the ID of the job the caused the table to be ingested.
   335  	JobID  int
   336  	Tables []struct {
   337  		TableInfo
   338  		Level int
   339  	}
   340  	// GlobalSeqNum is the sequence number that was assigned to all entries in
   341  	// the ingested table.
   342  	GlobalSeqNum uint64
   343  	// flushable indicates whether the ingested sstable was treated as a
   344  	// flushable.
   345  	flushable bool
   346  	Err       error
   347  }
   348  
   349  func (i TableIngestInfo) String() string {
   350  	return redact.StringWithoutMarkers(i)
   351  }
   352  
   353  // SafeFormat implements redact.SafeFormatter.
   354  func (i TableIngestInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   355  	if i.Err != nil {
   356  		w.Printf("[JOB %d] ingest error: %s", redact.Safe(i.JobID), i.Err)
   357  		return
   358  	}
   359  
   360  	if i.flushable {
   361  		w.Printf("[JOB %d] ingested as flushable", redact.Safe(i.JobID))
   362  	} else {
   363  		w.Printf("[JOB %d] ingested", redact.Safe(i.JobID))
   364  	}
   365  
   366  	for j := range i.Tables {
   367  		t := &i.Tables[j]
   368  		if j > 0 {
   369  			w.Printf(",")
   370  		}
   371  		levelStr := ""
   372  		if !i.flushable {
   373  			levelStr = fmt.Sprintf("L%d:", t.Level)
   374  		}
   375  		w.Printf(" %s%s (%s)", redact.Safe(levelStr), t.FileNum,
   376  			redact.Safe(humanize.Bytes.Uint64(t.Size)))
   377  	}
   378  }
   379  
   380  // TableStatsInfo contains the info for a table stats loaded event.
   381  type TableStatsInfo struct {
   382  	// JobID is the ID of the job that finished loading the initial tables'
   383  	// stats.
   384  	JobID int
   385  }
   386  
   387  func (i TableStatsInfo) String() string {
   388  	return redact.StringWithoutMarkers(i)
   389  }
   390  
   391  // SafeFormat implements redact.SafeFormatter.
   392  func (i TableStatsInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   393  	w.Printf("[JOB %d] all initial table stats loaded", redact.Safe(i.JobID))
   394  }
   395  
   396  // TableValidatedInfo contains information on the result of a validation run
   397  // on an sstable.
   398  type TableValidatedInfo struct {
   399  	JobID int
   400  	Meta  *fileMetadata
   401  }
   402  
   403  func (i TableValidatedInfo) String() string {
   404  	return redact.StringWithoutMarkers(i)
   405  }
   406  
   407  // SafeFormat implements redact.SafeFormatter.
   408  func (i TableValidatedInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   409  	w.Printf("[JOB %d] validated table: %s", redact.Safe(i.JobID), i.Meta)
   410  }
   411  
   412  // WALCreateInfo contains info about a WAL creation event.
   413  type WALCreateInfo struct {
   414  	// JobID is the ID of the job the caused the WAL to be created.
   415  	JobID int
   416  	Path  string
   417  	// The file number of the new WAL.
   418  	FileNum base.DiskFileNum
   419  	// The file number of a previous WAL which was recycled to create this
   420  	// one. Zero if recycling did not take place.
   421  	RecycledFileNum FileNum
   422  	Err             error
   423  }
   424  
   425  func (i WALCreateInfo) String() string {
   426  	return redact.StringWithoutMarkers(i)
   427  }
   428  
   429  // SafeFormat implements redact.SafeFormatter.
   430  func (i WALCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   431  	if i.Err != nil {
   432  		w.Printf("[JOB %d] WAL create error: %s", redact.Safe(i.JobID), i.Err)
   433  		return
   434  	}
   435  
   436  	if i.RecycledFileNum == 0 {
   437  		w.Printf("[JOB %d] WAL created %s", redact.Safe(i.JobID), i.FileNum)
   438  		return
   439  	}
   440  
   441  	w.Printf("[JOB %d] WAL created %s (recycled %s)",
   442  		redact.Safe(i.JobID), i.FileNum, i.RecycledFileNum)
   443  }
   444  
   445  // WALDeleteInfo contains the info for a WAL deletion event.
   446  type WALDeleteInfo struct {
   447  	// JobID is the ID of the job the caused the WAL to be deleted.
   448  	JobID   int
   449  	Path    string
   450  	FileNum FileNum
   451  	Err     error
   452  }
   453  
   454  func (i WALDeleteInfo) String() string {
   455  	return redact.StringWithoutMarkers(i)
   456  }
   457  
   458  // SafeFormat implements redact.SafeFormatter.
   459  func (i WALDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   460  	if i.Err != nil {
   461  		w.Printf("[JOB %d] WAL delete error: %s", redact.Safe(i.JobID), i.Err)
   462  		return
   463  	}
   464  	w.Printf("[JOB %d] WAL deleted %s", redact.Safe(i.JobID), i.FileNum)
   465  }
   466  
   467  // WriteStallBeginInfo contains the info for a write stall begin event.
   468  type WriteStallBeginInfo struct {
   469  	Reason string
   470  }
   471  
   472  func (i WriteStallBeginInfo) String() string {
   473  	return redact.StringWithoutMarkers(i)
   474  }
   475  
   476  // SafeFormat implements redact.SafeFormatter.
   477  func (i WriteStallBeginInfo) SafeFormat(w redact.SafePrinter, _ rune) {
   478  	w.Printf("write stall beginning: %s", redact.Safe(i.Reason))
   479  }
   480  
   481  // EventListener contains a set of functions that will be invoked when various
   482  // significant DB events occur. Note that the functions should not run for an
   483  // excessive amount of time as they are invoked synchronously by the DB and may
   484  // block continued DB work. For a similar reason it is advisable to not perform
   485  // any synchronous calls back into the DB.
   486  type EventListener struct {
   487  	// BackgroundError is invoked whenever an error occurs during a background
   488  	// operation such as flush or compaction.
   489  	BackgroundError func(error)
   490  
   491  	// CompactionBegin is invoked after the inputs to a compaction have been
   492  	// determined, but before the compaction has produced any output.
   493  	CompactionBegin func(CompactionInfo)
   494  
   495  	// CompactionEnd is invoked after a compaction has completed and the result
   496  	// has been installed.
   497  	CompactionEnd func(CompactionInfo)
   498  
   499  	// DiskSlow is invoked after a disk write operation on a file created with a
   500  	// disk health checking vfs.FS (see vfs.DefaultWithDiskHealthChecks) is
   501  	// observed to exceed the specified disk slowness threshold duration. DiskSlow
   502  	// is called on a goroutine that is monitoring slowness/stuckness. The callee
   503  	// MUST return without doing any IO, or blocking on anything (like a mutex)
   504  	// that is waiting on IO. This is imperative in order to reliably monitor for
   505  	// slowness, since if this goroutine gets stuck, the monitoring will stop
   506  	// working.
   507  	DiskSlow func(DiskSlowInfo)
   508  
   509  	// FlushBegin is invoked after the inputs to a flush have been determined,
   510  	// but before the flush has produced any output.
   511  	FlushBegin func(FlushInfo)
   512  
   513  	// FlushEnd is invoked after a flush has complated and the result has been
   514  	// installed.
   515  	FlushEnd func(FlushInfo)
   516  
   517  	// FormatUpgrade is invoked after the database's FormatMajorVersion
   518  	// is upgraded.
   519  	FormatUpgrade func(FormatMajorVersion)
   520  
   521  	// ManifestCreated is invoked after a manifest has been created.
   522  	ManifestCreated func(ManifestCreateInfo)
   523  
   524  	// ManifestDeleted is invoked after a manifest has been deleted.
   525  	ManifestDeleted func(ManifestDeleteInfo)
   526  
   527  	// TableCreated is invoked when a table has been created.
   528  	TableCreated func(TableCreateInfo)
   529  
   530  	// TableDeleted is invoked after a table has been deleted.
   531  	TableDeleted func(TableDeleteInfo)
   532  
   533  	// TableIngested is invoked after an externally created table has been
   534  	// ingested via a call to DB.Ingest().
   535  	TableIngested func(TableIngestInfo)
   536  
   537  	// TableStatsLoaded is invoked at most once, when the table stats
   538  	// collector has loaded statistics for all tables that existed at Open.
   539  	TableStatsLoaded func(TableStatsInfo)
   540  
   541  	// TableValidated is invoked after validation runs on an sstable.
   542  	TableValidated func(TableValidatedInfo)
   543  
   544  	// WALCreated is invoked after a WAL has been created.
   545  	WALCreated func(WALCreateInfo)
   546  
   547  	// WALDeleted is invoked after a WAL has been deleted.
   548  	WALDeleted func(WALDeleteInfo)
   549  
   550  	// WriteStallBegin is invoked when writes are intentionally delayed.
   551  	WriteStallBegin func(WriteStallBeginInfo)
   552  
   553  	// WriteStallEnd is invoked when delayed writes are released.
   554  	WriteStallEnd func()
   555  }
   556  
   557  // EnsureDefaults ensures that background error events are logged to the
   558  // specified logger if a handler for those events hasn't been otherwise
   559  // specified. Ensure all handlers are non-nil so that we don't have to check
   560  // for nil-ness before invoking.
   561  func (l *EventListener) EnsureDefaults(logger Logger) {
   562  	if l.BackgroundError == nil {
   563  		if logger != nil {
   564  			l.BackgroundError = func(err error) {
   565  				logger.Errorf("background error: %s", err)
   566  			}
   567  		} else {
   568  			l.BackgroundError = func(error) {}
   569  		}
   570  	}
   571  	if l.CompactionBegin == nil {
   572  		l.CompactionBegin = func(info CompactionInfo) {}
   573  	}
   574  	if l.CompactionEnd == nil {
   575  		l.CompactionEnd = func(info CompactionInfo) {}
   576  	}
   577  	if l.DiskSlow == nil {
   578  		l.DiskSlow = func(info DiskSlowInfo) {}
   579  	}
   580  	if l.FlushBegin == nil {
   581  		l.FlushBegin = func(info FlushInfo) {}
   582  	}
   583  	if l.FlushEnd == nil {
   584  		l.FlushEnd = func(info FlushInfo) {}
   585  	}
   586  	if l.FormatUpgrade == nil {
   587  		l.FormatUpgrade = func(v FormatMajorVersion) {}
   588  	}
   589  	if l.ManifestCreated == nil {
   590  		l.ManifestCreated = func(info ManifestCreateInfo) {}
   591  	}
   592  	if l.ManifestDeleted == nil {
   593  		l.ManifestDeleted = func(info ManifestDeleteInfo) {}
   594  	}
   595  	if l.TableCreated == nil {
   596  		l.TableCreated = func(info TableCreateInfo) {}
   597  	}
   598  	if l.TableDeleted == nil {
   599  		l.TableDeleted = func(info TableDeleteInfo) {}
   600  	}
   601  	if l.TableIngested == nil {
   602  		l.TableIngested = func(info TableIngestInfo) {}
   603  	}
   604  	if l.TableStatsLoaded == nil {
   605  		l.TableStatsLoaded = func(info TableStatsInfo) {}
   606  	}
   607  	if l.TableValidated == nil {
   608  		l.TableValidated = func(validated TableValidatedInfo) {}
   609  	}
   610  	if l.WALCreated == nil {
   611  		l.WALCreated = func(info WALCreateInfo) {}
   612  	}
   613  	if l.WALDeleted == nil {
   614  		l.WALDeleted = func(info WALDeleteInfo) {}
   615  	}
   616  	if l.WriteStallBegin == nil {
   617  		l.WriteStallBegin = func(info WriteStallBeginInfo) {}
   618  	}
   619  	if l.WriteStallEnd == nil {
   620  		l.WriteStallEnd = func() {}
   621  	}
   622  }
   623  
   624  // MakeLoggingEventListener creates an EventListener that logs all events to the
   625  // specified logger.
   626  func MakeLoggingEventListener(logger Logger) EventListener {
   627  	if logger == nil {
   628  		logger = DefaultLogger
   629  	}
   630  
   631  	return EventListener{
   632  		BackgroundError: func(err error) {
   633  			logger.Errorf("background error: %s", err)
   634  		},
   635  		CompactionBegin: func(info CompactionInfo) {
   636  			logger.Infof("%s", info)
   637  		},
   638  		CompactionEnd: func(info CompactionInfo) {
   639  			logger.Infof("%s", info)
   640  		},
   641  		DiskSlow: func(info DiskSlowInfo) {
   642  			logger.Infof("%s", info)
   643  		},
   644  		FlushBegin: func(info FlushInfo) {
   645  			logger.Infof("%s", info)
   646  		},
   647  		FlushEnd: func(info FlushInfo) {
   648  			logger.Infof("%s", info)
   649  		},
   650  		FormatUpgrade: func(v FormatMajorVersion) {
   651  			logger.Infof("upgraded to format version: %s", v)
   652  		},
   653  		ManifestCreated: func(info ManifestCreateInfo) {
   654  			logger.Infof("%s", info)
   655  		},
   656  		ManifestDeleted: func(info ManifestDeleteInfo) {
   657  			logger.Infof("%s", info)
   658  		},
   659  		TableCreated: func(info TableCreateInfo) {
   660  			logger.Infof("%s", info)
   661  		},
   662  		TableDeleted: func(info TableDeleteInfo) {
   663  			logger.Infof("%s", info)
   664  		},
   665  		TableIngested: func(info TableIngestInfo) {
   666  			logger.Infof("%s", info)
   667  		},
   668  		TableStatsLoaded: func(info TableStatsInfo) {
   669  			logger.Infof("%s", info)
   670  		},
   671  		TableValidated: func(info TableValidatedInfo) {
   672  			logger.Infof("%s", info)
   673  		},
   674  		WALCreated: func(info WALCreateInfo) {
   675  			logger.Infof("%s", info)
   676  		},
   677  		WALDeleted: func(info WALDeleteInfo) {
   678  			logger.Infof("%s", info)
   679  		},
   680  		WriteStallBegin: func(info WriteStallBeginInfo) {
   681  			logger.Infof("%s", info)
   682  		},
   683  		WriteStallEnd: func() {
   684  			logger.Infof("write stall ending")
   685  		},
   686  	}
   687  }
   688  
   689  // TeeEventListener wraps two EventListeners, forwarding all events to both.
   690  func TeeEventListener(a, b EventListener) EventListener {
   691  	a.EnsureDefaults(nil)
   692  	b.EnsureDefaults(nil)
   693  	return EventListener{
   694  		BackgroundError: func(err error) {
   695  			a.BackgroundError(err)
   696  			b.BackgroundError(err)
   697  		},
   698  		CompactionBegin: func(info CompactionInfo) {
   699  			a.CompactionBegin(info)
   700  			b.CompactionBegin(info)
   701  		},
   702  		CompactionEnd: func(info CompactionInfo) {
   703  			a.CompactionEnd(info)
   704  			b.CompactionEnd(info)
   705  		},
   706  		DiskSlow: func(info DiskSlowInfo) {
   707  			a.DiskSlow(info)
   708  			b.DiskSlow(info)
   709  		},
   710  		FlushBegin: func(info FlushInfo) {
   711  			a.FlushBegin(info)
   712  			b.FlushBegin(info)
   713  		},
   714  		FlushEnd: func(info FlushInfo) {
   715  			a.FlushEnd(info)
   716  			b.FlushEnd(info)
   717  		},
   718  		FormatUpgrade: func(v FormatMajorVersion) {
   719  			a.FormatUpgrade(v)
   720  			b.FormatUpgrade(v)
   721  		},
   722  		ManifestCreated: func(info ManifestCreateInfo) {
   723  			a.ManifestCreated(info)
   724  			b.ManifestCreated(info)
   725  		},
   726  		ManifestDeleted: func(info ManifestDeleteInfo) {
   727  			a.ManifestDeleted(info)
   728  			b.ManifestDeleted(info)
   729  		},
   730  		TableCreated: func(info TableCreateInfo) {
   731  			a.TableCreated(info)
   732  			b.TableCreated(info)
   733  		},
   734  		TableDeleted: func(info TableDeleteInfo) {
   735  			a.TableDeleted(info)
   736  			b.TableDeleted(info)
   737  		},
   738  		TableIngested: func(info TableIngestInfo) {
   739  			a.TableIngested(info)
   740  			b.TableIngested(info)
   741  		},
   742  		TableStatsLoaded: func(info TableStatsInfo) {
   743  			a.TableStatsLoaded(info)
   744  			b.TableStatsLoaded(info)
   745  		},
   746  		TableValidated: func(info TableValidatedInfo) {
   747  			a.TableValidated(info)
   748  			b.TableValidated(info)
   749  		},
   750  		WALCreated: func(info WALCreateInfo) {
   751  			a.WALCreated(info)
   752  			b.WALCreated(info)
   753  		},
   754  		WALDeleted: func(info WALDeleteInfo) {
   755  			a.WALDeleted(info)
   756  			b.WALDeleted(info)
   757  		},
   758  		WriteStallBegin: func(info WriteStallBeginInfo) {
   759  			a.WriteStallBegin(info)
   760  			b.WriteStallBegin(info)
   761  		},
   762  		WriteStallEnd: func() {
   763  			a.WriteStallEnd()
   764  			b.WriteStallEnd()
   765  		},
   766  	}
   767  }