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 }