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