001/* 002 * Copyright 2015-2022 Transmogrify LLC, 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.pyranid; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.ThreadSafe; 023import java.time.Duration; 024import java.util.Objects; 025import java.util.Optional; 026import java.util.UUID; 027 028import static java.util.Objects.requireNonNull; 029 030/** 031 * Contract for collecting operational metrics from Pyranid. 032 * <p> 033 * The default collector is {@link #disabledInstance()}, which performs no work. {@link #inMemoryInstance()} returns a 034 * fresh counter-only collector useful for tests and ad-hoc inspection through {@link #snapshot()}. 035 * <p> 036 * Implementations must be thread-safe, non-blocking, and failure-tolerant. Pyranid catches and discards collector 037 * exceptions so metrics collection cannot affect database behavior. 038 * 039 * @author <a href="https://www.revetkn.com">Mark Allen</a> 040 * @since 4.2.0 041 */ 042@ThreadSafe 043public interface MetricsCollector { 044 /** 045 * Called immediately before Pyranid attempts to acquire a statement-scoped {@link java.sql.Connection}. 046 * <p> 047 * Statement-scoped connections are used for standalone statement execution and stream opening outside a physical 048 * {@link Transaction}. 049 * 050 * @param ctx statement context for the operation that needs a connection 051 */ 052 default void willAcquireStatementConnection(@NonNull StatementContext<?> ctx) { 053 // No-op by default 054 } 055 056 /** 057 * Called after Pyranid successfully acquires a statement-scoped {@link java.sql.Connection}. 058 * 059 * @param ctx statement context for the operation that acquired a connection 060 * @param acquisitionDuration elapsed time spent acquiring the connection 061 */ 062 default void didAcquireStatementConnection(@NonNull StatementContext<?> ctx, 063 @NonNull Duration acquisitionDuration) { 064 // No-op by default 065 } 066 067 /** 068 * Called after Pyranid fails to acquire a statement-scoped {@link java.sql.Connection}. 069 * 070 * @param ctx statement context for the operation that needed a connection 071 * @param databaseType database type known at the time of failure 072 * @param acquisitionDuration elapsed time spent attempting to acquire the connection 073 * @param throwable failure that prevented connection acquisition 074 */ 075 default void didFailToAcquireStatementConnection(@NonNull StatementContext<?> ctx, 076 @NonNull DatabaseType databaseType, 077 @NonNull Duration acquisitionDuration, 078 @NonNull Throwable throwable) { 079 // No-op by default 080 } 081 082 /** 083 * Called after Pyranid successfully releases a statement-scoped {@link java.sql.Connection}. 084 * 085 * @param ctx statement context for the operation that used the connection 086 * @param heldDuration elapsed time between successful acquisition and release 087 */ 088 default void didReleaseStatementConnection(@NonNull StatementContext<?> ctx, 089 @NonNull Duration heldDuration) { 090 // No-op by default 091 } 092 093 /** 094 * Called after Pyranid fails to release a statement-scoped {@link java.sql.Connection}. 095 * 096 * @param ctx statement context for the operation that used the connection 097 * @param heldDuration elapsed time between successful acquisition and the failed release attempt 098 * @param throwable failure that prevented connection release 099 */ 100 default void didFailToReleaseStatementConnection(@NonNull StatementContext<?> ctx, 101 @NonNull Duration heldDuration, 102 @NonNull Throwable throwable) { 103 // No-op by default 104 } 105 106 /** 107 * Called immediately before Pyranid attempts to acquire the JDBC connection backing a physical transaction. 108 * <p> 109 * Pyranid transactions acquire their JDBC connection lazily, so this callback is emitted only when transaction work 110 * first needs database access. 111 * 112 * @param transaction transaction that needs a physical connection 113 * @param databaseType database type known at the time of acquisition 114 */ 115 default void willAcquireTransactionConnection(@NonNull Transaction transaction, 116 @NonNull DatabaseType databaseType) { 117 // No-op by default 118 } 119 120 /** 121 * Called after Pyranid successfully acquires the JDBC connection backing a physical transaction. 122 * 123 * @param transaction transaction that acquired the connection 124 * @param databaseType database type known at the time of acquisition 125 * @param acquisitionDuration elapsed time spent acquiring the connection 126 */ 127 default void didAcquireTransactionConnection(@NonNull Transaction transaction, 128 @NonNull DatabaseType databaseType, 129 @NonNull Duration acquisitionDuration) { 130 // No-op by default 131 } 132 133 /** 134 * Called after Pyranid fails to acquire the JDBC connection backing a physical transaction. 135 * 136 * @param transaction transaction that needed a connection 137 * @param databaseType database type known at the time of failure 138 * @param acquisitionDuration elapsed time spent attempting to acquire the connection 139 * @param throwable failure that prevented connection acquisition 140 */ 141 default void didFailToAcquireTransactionConnection(@NonNull Transaction transaction, 142 @NonNull DatabaseType databaseType, 143 @NonNull Duration acquisitionDuration, 144 @NonNull Throwable throwable) { 145 // No-op by default 146 } 147 148 /** 149 * Called after Pyranid successfully releases the JDBC connection backing a physical transaction. 150 * 151 * @param transaction transaction that owned the connection 152 * @param databaseType database type known at the time of release 153 * @param heldDuration elapsed time between successful acquisition and release 154 */ 155 default void didReleaseTransactionConnection(@NonNull Transaction transaction, 156 @NonNull DatabaseType databaseType, 157 @NonNull Duration heldDuration) { 158 // No-op by default 159 } 160 161 /** 162 * Called after Pyranid fails to release the JDBC connection backing a physical transaction. 163 * 164 * @param transaction transaction that owned the connection 165 * @param databaseType database type known at the time of failure 166 * @param heldDuration elapsed time between successful acquisition and the failed release attempt 167 * @param throwable failure that prevented connection release 168 */ 169 default void didFailToReleaseTransactionConnection(@NonNull Transaction transaction, 170 @NonNull DatabaseType databaseType, 171 @NonNull Duration heldDuration, 172 @NonNull Throwable throwable) { 173 // No-op by default 174 } 175 176 /** 177 * Called when Pyranid enters a closure-based transaction. 178 * <p> 179 * This is a logical transaction event and may occur even if user code never performs database work and no physical JDBC 180 * transaction is begun. 181 * 182 * @param transaction transaction passed to the closure through Pyranid APIs 183 * @param isolation requested transaction isolation 184 * @param databaseType database type known at transaction entry 185 */ 186 default void didEnterTransactionClosure(@NonNull Transaction transaction, 187 @NonNull TransactionIsolation isolation, 188 @NonNull DatabaseType databaseType) { 189 // No-op by default 190 } 191 192 /** 193 * Called when Pyranid exits a closure-based transaction. 194 * 195 * @param transaction transaction that is exiting 196 * @param outcome logical transaction outcome 197 * @param databaseType database type known at transaction exit 198 * @param logicalDuration elapsed time from transaction entry to exit, including user code, physical transaction work, 199 * cleanup, and post-transaction operations 200 * @param thrown exception or error that caused the transaction to exit abnormally, or {@code null} when the 201 * transaction completed without a reported failure 202 */ 203 default void didExitTransactionClosure(@NonNull Transaction transaction, 204 @NonNull TransactionClosureOutcome outcome, 205 @NonNull DatabaseType databaseType, 206 @NonNull Duration logicalDuration, 207 @Nullable Throwable thrown) { 208 // No-op by default 209 } 210 211 /** 212 * Called after Pyranid successfully begins a physical JDBC transaction. 213 * 214 * @param transaction transaction whose physical JDBC transaction began 215 * @param isolation requested transaction isolation 216 * @param databaseType database type known at transaction begin 217 */ 218 default void didBeginPhysicalTransaction(@NonNull Transaction transaction, 219 @NonNull TransactionIsolation isolation, 220 @NonNull DatabaseType databaseType) { 221 // No-op by default 222 } 223 224 /** 225 * Called after Pyranid fails while beginning a physical JDBC transaction. 226 * 227 * @param transaction transaction whose physical JDBC transaction failed to begin 228 * @param isolation requested transaction isolation 229 * @param phase begin phase that failed 230 * @param databaseType database type known at the time of failure 231 * @param throwable failure that prevented the physical transaction from beginning 232 */ 233 default void didFailToBeginPhysicalTransaction(@NonNull Transaction transaction, 234 @NonNull TransactionIsolation isolation, 235 @NonNull PhysicalTransactionBeginFailurePhase phase, 236 @NonNull DatabaseType databaseType, 237 @NonNull Throwable throwable) { 238 // No-op by default 239 } 240 241 /** 242 * Called after Pyranid successfully commits a physical JDBC transaction. 243 * 244 * @param transaction transaction that committed 245 * @param databaseType database type known at commit time 246 * @param physicalDuration elapsed time between physical transaction begin and commit 247 */ 248 default void didCommitPhysicalTransaction(@NonNull Transaction transaction, 249 @NonNull DatabaseType databaseType, 250 @NonNull Duration physicalDuration) { 251 // No-op by default 252 } 253 254 /** 255 * Called after Pyranid fails to commit a physical JDBC transaction. 256 * <p> 257 * Reported when Pyranid's physical commit attempt fails: the JDBC commit call itself, or the success handling that 258 * immediately follows it. It does not establish that the driver never received the commit, nor that the commit did not 259 * succeed. A completion that refuses the commit before issuing it, such as a savepoint-scope ownership conflict, still 260 * fails the transaction without reaching this callback. 261 * 262 * @param transaction transaction whose commit failed 263 * @param databaseType database type known at commit time 264 * @param physicalDuration elapsed time between physical transaction begin and the failed commit attempt 265 * @param throwable failure that prevented commit from completing normally 266 */ 267 default void didFailToCommitPhysicalTransaction(@NonNull Transaction transaction, 268 @NonNull DatabaseType databaseType, 269 @NonNull Duration physicalDuration, 270 @NonNull Throwable throwable) { 271 // No-op by default 272 } 273 274 /** 275 * Called after Pyranid successfully rolls back a physical JDBC transaction. 276 * 277 * @param transaction transaction that rolled back 278 * @param databaseType database type known at rollback time 279 * @param physicalDuration elapsed time between physical transaction begin and rollback 280 */ 281 default void didRollbackPhysicalTransaction(@NonNull Transaction transaction, 282 @NonNull DatabaseType databaseType, 283 @NonNull Duration physicalDuration) { 284 // No-op by default 285 } 286 287 /** 288 * Called after Pyranid fails to roll back a physical JDBC transaction. 289 * 290 * @param transaction transaction whose rollback failed 291 * @param databaseType database type known at rollback time 292 * @param physicalDuration elapsed time between physical transaction begin and the failed rollback attempt 293 * @param throwable failure that prevented rollback from completing normally 294 */ 295 default void didFailToRollbackPhysicalTransaction(@NonNull Transaction transaction, 296 @NonNull DatabaseType databaseType, 297 @NonNull Duration physicalDuration, 298 @NonNull Throwable throwable) { 299 // No-op by default 300 } 301 302 /** 303 * Called after a post-transaction operation runs. 304 * 305 * @param transaction transaction whose post-transaction operation ran 306 * @param result transaction result visible to the post-transaction operation 307 * @param databaseType database type known at post-transaction execution time 308 * @param duration elapsed time spent running the post-transaction operation 309 * @param throwable failure thrown by the post-transaction operation, or {@code null} when it completed normally 310 */ 311 default void didRunPostTransactionOperation(@NonNull Transaction transaction, 312 @NonNull TransactionResult result, 313 @NonNull DatabaseType databaseType, 314 @NonNull Duration duration, 315 @Nullable Throwable throwable) { 316 // No-op by default 317 } 318 319 /** 320 * Called after Pyranid successfully creates a transaction savepoint. 321 * 322 * @param transaction transaction that created the savepoint 323 * @param databaseType database type known at savepoint creation time 324 */ 325 default void didCreateSavepoint(@NonNull Transaction transaction, 326 @NonNull DatabaseType databaseType) { 327 // No-op by default 328 } 329 330 /** 331 * Called after Pyranid successfully rolls back to a transaction savepoint. 332 * 333 * @param transaction transaction that rolled back to the savepoint 334 * @param databaseType database type known at savepoint rollback time 335 */ 336 default void didRollbackToSavepoint(@NonNull Transaction transaction, 337 @NonNull DatabaseType databaseType) { 338 // No-op by default 339 } 340 341 /** 342 * Called after Pyranid successfully releases a transaction savepoint. 343 * 344 * @param transaction transaction that released the savepoint 345 * @param databaseType database type known at savepoint release time 346 */ 347 default void didReleaseSavepoint(@NonNull Transaction transaction, 348 @NonNull DatabaseType databaseType) { 349 // No-op by default 350 } 351 352 /** 353 * Called immediately before Pyranid executes a statement. 354 * 355 * @param ctx statement context for the statement about to execute 356 */ 357 default void willExecuteStatement(@NonNull StatementContext<?> ctx) { 358 // No-op by default 359 } 360 361 /** 362 * Called after Pyranid successfully executes a statement. 363 * 364 * @param ctx statement context for the executed statement 365 * @param statementLog diagnostic statement log for the execution 366 * @param result statement execution result 367 */ 368 default void didExecuteStatement(@NonNull StatementContext<?> ctx, 369 @NonNull StatementLog<?> statementLog, 370 @NonNull StatementResult result) { 371 // No-op by default 372 } 373 374 /** 375 * Called after Pyranid fails to execute a statement. 376 * 377 * @param ctx statement context for the failed statement 378 * @param statementLog diagnostic statement log for the failed execution 379 * @param databaseType database type known at statement failure time 380 * @param throwable failure thrown while executing the statement 381 */ 382 default void didFailToExecuteStatement(@NonNull StatementContext<?> ctx, 383 @NonNull StatementLog<?> statementLog, 384 @NonNull DatabaseType databaseType, 385 @NonNull Throwable throwable) { 386 // No-op by default 387 } 388 389 /** 390 * Called immediately before Pyranid opens a streaming statement. 391 * 392 * @param ctx statement context for the stream about to open 393 */ 394 default void willOpenStream(@NonNull StatementContext<?> ctx) { 395 // No-op by default 396 } 397 398 /** 399 * Called after Pyranid successfully opens a streaming statement. 400 * 401 * @param ctx statement context for the opened stream 402 * @param openDuration elapsed time spent opening the stream 403 */ 404 default void didOpenStream(@NonNull StatementContext<?> ctx, 405 @NonNull Duration openDuration) { 406 // No-op by default 407 } 408 409 /** 410 * Called after Pyranid fails to open a streaming statement. 411 * 412 * @param ctx statement context for the stream that failed to open 413 * @param databaseType database type known at stream-open failure time 414 * @param openDuration elapsed time spent attempting to open the stream 415 * @param throwable failure that prevented the stream from opening 416 */ 417 default void didFailToOpenStream(@NonNull StatementContext<?> ctx, 418 @NonNull DatabaseType databaseType, 419 @NonNull Duration openDuration, 420 @NonNull Throwable throwable) { 421 // No-op by default 422 } 423 424 /** 425 * Called after an opened stream reaches a terminal state. 426 * <p> 427 * Pyranid does not emit this callback for streams that fail before they open; those failures are reported through 428 * {@link #didFailToOpenStream(StatementContext, DatabaseType, Duration, Throwable)}. 429 * 430 * @param ctx statement context for the stream 431 * @param outcome terminal stream outcome 432 * @param rowsConsumed number of rows consumed from the stream 433 * @param streamDuration elapsed time between stream-open start and terminal close handling 434 * @param throwable failure associated with the terminal outcome, or {@code null} when no failure is available 435 */ 436 default void didCloseStream(@NonNull StatementContext<?> ctx, 437 @NonNull StreamTerminalOutcome outcome, 438 @NonNull Long rowsConsumed, 439 @NonNull Duration streamDuration, 440 @Nullable Throwable throwable) { 441 // No-op by default 442 } 443 444 /** 445 * Called immediately before Pyranid attempts to open a notification session. 446 * <p> 447 * Argument validation, ambient-transaction rejection, database-type resolution, and backend-specific channel 448 * validation occur before this callback. Exactly one of 449 * {@link #didOpenNotificationSession(DatabaseType, UUID, Duration)} or 450 * {@link #didFailToOpenNotificationSession(DatabaseType, UUID, Duration, Throwable)} follows it. 451 * 452 * @param databaseType database type resolved for the notification session 453 * @param notificationSessionId identifier for this notification-session invocation 454 * @since 4.6.0 455 */ 456 default void willOpenNotificationSession(@NonNull DatabaseType databaseType, 457 @NonNull UUID notificationSessionId) { 458 // No-op by default 459 } 460 461 /** 462 * Called after Pyranid successfully opens a notification session and registers every requested channel. 463 * <p> 464 * This callback occurs before the application operation is dispatched. It does not indicate that application 465 * reconciliation or readiness work has completed. 466 * 467 * @param databaseType database type resolved for the notification session 468 * @param notificationSessionId identifier for this notification-session invocation 469 * @param openDuration elapsed time from the corresponding 470 * {@link #willOpenNotificationSession(DatabaseType, UUID)} callback through successful 471 * channel registration 472 * @since 4.6.0 473 */ 474 default void didOpenNotificationSession(@NonNull DatabaseType databaseType, 475 @NonNull UUID notificationSessionId, 476 @NonNull Duration openDuration) { 477 // No-op by default 478 } 479 480 /** 481 * Called after Pyranid fails to open a notification session. 482 * <p> 483 * This is the terminal lifecycle callback for an invocation that never reached 484 * {@link #didOpenNotificationSession(DatabaseType, UUID, Duration)}. It is not followed by 485 * {@link #didCloseNotificationSession(DatabaseType, UUID, NotificationSessionOutcome, Duration, Throwable)}. 486 * Pyranid emits it after selecting the opening failure and before cleaning up any acquired candidate connection. 487 * 488 * @param databaseType database type resolved for the notification session 489 * @param notificationSessionId identifier for this notification-session invocation 490 * @param openDuration elapsed time from the corresponding 491 * {@link #willOpenNotificationSession(DatabaseType, UUID)} callback through the opening 492 * failure 493 * @param throwable failure that prevented the notification session from opening 494 * @since 4.6.0 495 */ 496 default void didFailToOpenNotificationSession(@NonNull DatabaseType databaseType, 497 @NonNull UUID notificationSessionId, 498 @NonNull Duration openDuration, 499 @NonNull Throwable throwable) { 500 // No-op by default 501 } 502 503 /** 504 * Called when Pyranid delivers a nonempty notification batch to application code. 505 * <p> 506 * Empty receive results and notifications discarded during setup or cleanup are not reported. 507 * 508 * @param databaseType database type resolved for the notification session 509 * @param notificationSessionId identifier for the notification session that delivered the batch 510 * @param notificationCount number of notifications in the delivered batch 511 * @since 4.6.0 512 */ 513 default void didDeliverNotificationBatch(@NonNull DatabaseType databaseType, 514 @NonNull UUID notificationSessionId, 515 @NonNull Long notificationCount) { 516 // No-op by default 517 } 518 519 /** 520 * Called when a receive operation detects terminal uncertainty or loss of an opened notification connection. 521 * <p> 522 * This callback is emitted at most once for a notification session. It does not report setup failures, failures 523 * from ordinary database operations performed by application code, or cleanup-only failures. 524 * 525 * @param databaseType database type resolved for the notification session 526 * @param notificationSessionId identifier for the notification session whose connection was lost 527 * @param throwable terminal connection failure 528 * @since 4.6.0 529 */ 530 default void didLoseNotificationConnection(@NonNull DatabaseType databaseType, 531 @NonNull UUID notificationSessionId, 532 @NonNull Throwable throwable) { 533 // No-op by default 534 } 535 536 /** 537 * Called after an opened notification session reaches a terminal state and cleanup completes. 538 * <p> 539 * Every invocation that emits {@link #didOpenNotificationSession(DatabaseType, UUID, Duration)} emits this callback 540 * exactly once. Invocations that fail before opening terminate through 541 * {@link #didFailToOpenNotificationSession(DatabaseType, UUID, Duration, Throwable)} instead. 542 * 543 * @param databaseType database type resolved for the notification session 544 * @param notificationSessionId identifier for the notification session 545 * @param outcome terminal notification-session outcome 546 * @param sessionDuration elapsed time from the corresponding 547 * {@link #willOpenNotificationSession(DatabaseType, UUID)} callback through completed 548 * cleanup 549 * @param throwable selected failure for {@link NotificationSessionOutcome#FAILED}, or {@code null} for 550 * {@link NotificationSessionOutcome#CALLBACK_RETURNED} and 551 * {@link NotificationSessionOutcome#INTERRUPTED} 552 * @since 4.6.0 553 */ 554 default void didCloseNotificationSession(@NonNull DatabaseType databaseType, 555 @NonNull UUID notificationSessionId, 556 @NonNull NotificationSessionOutcome outcome, 557 @NonNull Duration sessionDuration, 558 @Nullable Throwable throwable) { 559 // No-op by default 560 } 561 562 /** 563 * Returns a notification counter snapshot if this collector supports in-process inspection. 564 * <p> 565 * Implementations may read counters independently without cross-counter atomicity. Callers that need 566 * invariant-consistent snapshots should drain in-flight work before reading. 567 * 568 * @return a notification metrics snapshot, if supported 569 * @since 4.6.0 570 */ 571 @NonNull 572 default Optional<NotificationSnapshot> notificationSnapshot() { 573 return Optional.empty(); 574 } 575 576 /** 577 * Returns a counter snapshot if this collector supports in-process inspection. 578 * <p> 579 * Implementations may read counters independently without cross-counter atomicity. Callers that need invariant-consistent 580 * snapshots should drain in-flight work before reading. 581 * 582 * @return a metrics snapshot, if supported 583 */ 584 @NonNull 585 default Optional<Snapshot> snapshot() { 586 return Optional.empty(); 587 } 588 589 /** 590 * Best-effort reset for test and ad-hoc use. 591 * <p> 592 * Concurrent updates may race with reset operations. Production rolling-window semantics should be implemented outside 593 * this interface. 594 */ 595 default void reset() { 596 // No-op by default 597 } 598 599 /** 600 * Returns the shared no-op metrics collector. 601 * 602 * @return no-op metrics collector used when metrics collection is disabled 603 */ 604 @NonNull 605 static MetricsCollector disabledInstance() { 606 return DisabledMetricsCollector.defaultInstance(); 607 } 608 609 /** 610 * Creates a fresh in-memory counter collector. 611 * <p> 612 * This collector is intended for tests and lightweight local inspection through {@link #snapshot()} and 613 * {@link #notificationSnapshot()}. 614 * 615 * @return new in-memory metrics collector 616 */ 617 @NonNull 618 static MetricsCollector inMemoryInstance() { 619 return InMemoryMetricsCollector.defaultInstance(); 620 } 621 622 /** 623 * Terminal outcome for an opened notification session. 624 * 625 * @since 4.6.0 626 */ 627 enum NotificationSessionOutcome { 628 /** 629 * The application callback returned and notification-session cleanup completed normally. 630 */ 631 CALLBACK_RETURNED, 632 633 /** 634 * Cooperative interruption ended the notification session and cleanup completed normally. 635 */ 636 INTERRUPTED, 637 638 /** 639 * Setup after opening, notification receive, application callback, or cleanup failed. 640 */ 641 FAILED 642 } 643 644 /** 645 * Counter snapshot for notification metrics. 646 * <p> 647 * Implementations may read counters independently without cross-counter atomicity. Callers that need 648 * invariant-consistent snapshots should drain in-flight work before reading. 649 * <p> 650 * Instances are immutable value objects created through {@link #of(Long, Long, Long, Long, Long, Long, Long)}. 651 * Future releases may add counters and accessors while retaining this factory with defined defaults for any 652 * newly added values. 653 * 654 * @since 4.6.0 655 */ 656 @ThreadSafe 657 final class NotificationSnapshot { 658 @NonNull 659 private final Long sessionsStarted; 660 @NonNull 661 private final Long sessionsOpened; 662 @NonNull 663 private final Long sessionsCallbackReturned; 664 @NonNull 665 private final Long sessionsInterrupted; 666 @NonNull 667 private final Long sessionsFailed; 668 @NonNull 669 private final Long batchesDelivered; 670 @NonNull 671 private final Long notificationsDelivered; 672 673 private NotificationSnapshot(@NonNull Long sessionsStarted, 674 @NonNull Long sessionsOpened, 675 @NonNull Long sessionsCallbackReturned, 676 @NonNull Long sessionsInterrupted, 677 @NonNull Long sessionsFailed, 678 @NonNull Long batchesDelivered, 679 @NonNull Long notificationsDelivered) { 680 this.sessionsStarted = requireNonNull(sessionsStarted); 681 this.sessionsOpened = requireNonNull(sessionsOpened); 682 this.sessionsCallbackReturned = requireNonNull(sessionsCallbackReturned); 683 this.sessionsInterrupted = requireNonNull(sessionsInterrupted); 684 this.sessionsFailed = requireNonNull(sessionsFailed); 685 this.batchesDelivered = requireNonNull(batchesDelivered); 686 this.notificationsDelivered = requireNonNull(notificationsDelivered); 687 } 688 689 /** 690 * Creates a notification metrics snapshot. 691 * 692 * @param sessionsStarted valid, type-resolved notification-session invocations that entered measured setup 693 * @param sessionsOpened notification sessions that completed channel registration 694 * @param sessionsCallbackReturned opened notification sessions whose callbacks returned and cleanup completed normally 695 * @param sessionsInterrupted opened notification sessions that ended through clean cooperative interruption 696 * @param sessionsFailed notification-session invocations that failed before opening or after opening 697 * @param batchesDelivered nonempty notification batches delivered to application code 698 * @param notificationsDelivered notifications contained in delivered batches 699 * @return notification metrics snapshot 700 * @throws NullPointerException if any counter is null 701 * @since 4.6.0 702 */ 703 @NonNull 704 public static NotificationSnapshot of(@NonNull Long sessionsStarted, 705 @NonNull Long sessionsOpened, 706 @NonNull Long sessionsCallbackReturned, 707 @NonNull Long sessionsInterrupted, 708 @NonNull Long sessionsFailed, 709 @NonNull Long batchesDelivered, 710 @NonNull Long notificationsDelivered) { 711 return new NotificationSnapshot(sessionsStarted, sessionsOpened, sessionsCallbackReturned, sessionsInterrupted, 712 sessionsFailed, batchesDelivered, notificationsDelivered); 713 } 714 715 /** 716 * @return valid, type-resolved notification-session invocations that entered measured setup 717 * @since 4.6.0 718 */ 719 @NonNull 720 public Long sessionsStarted() { 721 return this.sessionsStarted; 722 } 723 724 /** 725 * @return notification sessions that completed channel registration 726 * @since 4.6.0 727 */ 728 @NonNull 729 public Long sessionsOpened() { 730 return this.sessionsOpened; 731 } 732 733 /** 734 * @return opened notification sessions whose callbacks returned and cleanup completed normally 735 * @since 4.6.0 736 */ 737 @NonNull 738 public Long sessionsCallbackReturned() { 739 return this.sessionsCallbackReturned; 740 } 741 742 /** 743 * @return opened notification sessions that ended through clean cooperative interruption 744 * @since 4.6.0 745 */ 746 @NonNull 747 public Long sessionsInterrupted() { 748 return this.sessionsInterrupted; 749 } 750 751 /** 752 * @return notification-session invocations that failed before opening or after opening 753 * @since 4.6.0 754 */ 755 @NonNull 756 public Long sessionsFailed() { 757 return this.sessionsFailed; 758 } 759 760 /** 761 * @return nonempty notification batches delivered to application code 762 * @since 4.6.0 763 */ 764 @NonNull 765 public Long batchesDelivered() { 766 return this.batchesDelivered; 767 } 768 769 /** 770 * @return notifications contained in delivered batches 771 * @since 4.6.0 772 */ 773 @NonNull 774 public Long notificationsDelivered() { 775 return this.notificationsDelivered; 776 } 777 778 @Override 779 public boolean equals(@Nullable Object object) { 780 if (this == object) 781 return true; 782 783 if (!(object instanceof NotificationSnapshot notificationSnapshot)) 784 return false; 785 786 return sessionsStarted().equals(notificationSnapshot.sessionsStarted()) 787 && sessionsOpened().equals(notificationSnapshot.sessionsOpened()) 788 && sessionsCallbackReturned().equals(notificationSnapshot.sessionsCallbackReturned()) 789 && sessionsInterrupted().equals(notificationSnapshot.sessionsInterrupted()) 790 && sessionsFailed().equals(notificationSnapshot.sessionsFailed()) 791 && batchesDelivered().equals(notificationSnapshot.batchesDelivered()) 792 && notificationsDelivered().equals(notificationSnapshot.notificationsDelivered()); 793 } 794 795 @Override 796 public int hashCode() { 797 return Objects.hash(sessionsStarted(), sessionsOpened(), sessionsCallbackReturned(), sessionsInterrupted(), 798 sessionsFailed(), batchesDelivered(), notificationsDelivered()); 799 } 800 801 @Override 802 @NonNull 803 public String toString() { 804 return "NotificationSnapshot[" + 805 "sessionsStarted=" + sessionsStarted() + 806 ", sessionsOpened=" + sessionsOpened() + 807 ", sessionsCallbackReturned=" + sessionsCallbackReturned() + 808 ", sessionsInterrupted=" + sessionsInterrupted() + 809 ", sessionsFailed=" + sessionsFailed() + 810 ", batchesDelivered=" + batchesDelivered() + 811 ", notificationsDelivered=" + notificationsDelivered() + 812 ']'; 813 } 814 } 815 816 /** 817 * Logical outcome for a closure-based transaction. 818 */ 819 enum TransactionClosureOutcome { 820 /** 821 * The transaction used a physical JDBC transaction and commit completed normally. 822 */ 823 COMMITTED, 824 825 /** 826 * The transaction used a physical JDBC transaction and was rolled back, including a recognized commit-time 827 * serialization failure whose follow-up rollback completed normally. 828 */ 829 ROLLED_BACK, 830 831 /** 832 * The transaction closure exited without ever acquiring a JDBC connection. 833 */ 834 NO_PHYSICAL_TX, 835 836 /** 837 * The transaction failed before Pyranid could report a normal commit or rollback outcome. 838 */ 839 FAILED 840 } 841 842 /** 843 * Physical transaction begin phase that failed. 844 */ 845 enum PhysicalTransactionBeginFailurePhase { 846 /** 847 * Connection acquisition from the configured {@link javax.sql.DataSource} failed. 848 */ 849 ACQUIRE_CONNECTION, 850 851 /** 852 * Reading the connection's initial autocommit setting failed. 853 */ 854 READ_INITIAL_AUTOCOMMIT, 855 856 /** 857 * Reading the connection's initial transaction isolation failed. 858 */ 859 READ_INITIAL_ISOLATION, 860 861 /** 862 * Reading the connection's initial read-only setting failed. 863 */ 864 READ_INITIAL_READ_ONLY, 865 866 /** 867 * Disabling autocommit for the transaction failed. 868 */ 869 SET_AUTOCOMMIT_FALSE, 870 871 /** 872 * Applying the requested transaction isolation failed. 873 */ 874 SET_ISOLATION, 875 876 /** 877 * Applying the requested read-only setting failed. 878 */ 879 SET_READ_ONLY 880 } 881 882 /** 883 * Terminal outcome for an opened stream. 884 */ 885 enum StreamTerminalOutcome { 886 /** 887 * Stream iteration reached the end of the result set and cleanup completed without an iteration or callback failure. 888 */ 889 COMPLETED_NORMALLY, 890 891 /** 892 * The stream was closed before all rows were consumed. 893 */ 894 EARLY_CLOSE, 895 896 /** 897 * The caller-provided stream callback failed. 898 */ 899 CALLBACK_FAILURE, 900 901 /** 902 * Result-set iteration failed while the stream was open. 903 */ 904 ITERATION_FAILURE, 905 906 /** 907 * The stream failed before it opened. 908 * <p> 909 * Pyranid normally reports this through {@link #didFailToOpenStream(StatementContext, DatabaseType, Duration, 910 * Throwable)} instead of {@link #didCloseStream(StatementContext, StreamTerminalOutcome, Long, Duration, Throwable)}. 911 */ 912 OPEN_FAILURE 913 } 914 915 /** 916 * Counter snapshot for collectors that support in-process inspection. 917 * <p> 918 * Implementations may read counters independently without cross-counter atomicity. Callers that need invariant-consistent 919 * snapshots should drain in-flight work before reading. 920 * 921 * @param connectionsAcquiredStatementScope statement-scoped connection acquisitions that completed normally 922 * @param connectionsAcquiredTransactionScope transaction-scoped connection acquisitions that completed normally 923 * @param connectionsFailedStatementScope statement-scoped connection acquisitions that failed 924 * @param connectionsFailedTransactionScope transaction-scoped connection acquisitions that failed 925 * @param connectionReleaseFailuresStatementScope statement-scoped connection releases that failed 926 * @param connectionReleaseFailuresTransactionScope transaction-scoped connection releases that failed 927 * @param transactionClosuresEntered closure-based transactions entered 928 * @param transactionClosuresExited closure-based transactions exited 929 * @param transactionClosuresCommitted closure-based transactions that committed 930 * @param transactionClosuresRolledBack closure-based transactions that rolled back 931 * @param transactionClosuresNoPhysical closure-based transactions that exited without a physical JDBC transaction 932 * @param transactionClosuresFailed closure-based transactions that failed before a normal commit or rollback outcome 933 * @param physicalTransactionsBegun physical JDBC transactions begun 934 * @param physicalTransactionsBeginFailed physical JDBC transactions that failed while beginning 935 * @param physicalTransactionsCommitted physical JDBC transactions committed 936 * @param physicalTransactionsCommitFailed physical JDBC transactions whose commit failed 937 * @param physicalTransactionsRolledBack physical JDBC transactions rolled back 938 * @param physicalTransactionsRollbackFailed physical JDBC transactions whose rollback failed 939 * @param savepointsCreated transaction savepoints created 940 * @param savepointsRolledBack transaction savepoints rolled back to 941 * @param savepointsReleased transaction savepoints released 942 * @param statementsExecuted statements that executed successfully 943 * @param statementsFailed statements that failed during execution 944 * @param streamsOpened streams that opened successfully 945 * @param streamsOpenFailures streams that failed before opening 946 * @param streamsClosedNormally opened streams that reached the end of the result set 947 * @param streamsEarlyClosed opened streams that closed before all rows were consumed 948 * @param streamsCallbackFailed opened streams whose caller-provided callback failed 949 * @param streamsIterationFailed opened streams whose result-set iteration failed 950 * @param postTransactionOperationsRun post-transaction operations that ran 951 * @param postTransactionOperationsFailed post-transaction operations that failed 952 */ 953 @ThreadSafe 954 record Snapshot(@NonNull Long connectionsAcquiredStatementScope, 955 @NonNull Long connectionsAcquiredTransactionScope, 956 @NonNull Long connectionsFailedStatementScope, 957 @NonNull Long connectionsFailedTransactionScope, 958 @NonNull Long connectionReleaseFailuresStatementScope, 959 @NonNull Long connectionReleaseFailuresTransactionScope, 960 @NonNull Long transactionClosuresEntered, 961 @NonNull Long transactionClosuresExited, 962 @NonNull Long transactionClosuresCommitted, 963 @NonNull Long transactionClosuresRolledBack, 964 @NonNull Long transactionClosuresNoPhysical, 965 @NonNull Long transactionClosuresFailed, 966 @NonNull Long physicalTransactionsBegun, 967 @NonNull Long physicalTransactionsBeginFailed, 968 @NonNull Long physicalTransactionsCommitted, 969 @NonNull Long physicalTransactionsCommitFailed, 970 @NonNull Long physicalTransactionsRolledBack, 971 @NonNull Long physicalTransactionsRollbackFailed, 972 @NonNull Long savepointsCreated, 973 @NonNull Long savepointsRolledBack, 974 @NonNull Long savepointsReleased, 975 @NonNull Long statementsExecuted, 976 @NonNull Long statementsFailed, 977 @NonNull Long streamsOpened, 978 @NonNull Long streamsOpenFailures, 979 @NonNull Long streamsClosedNormally, 980 @NonNull Long streamsEarlyClosed, 981 @NonNull Long streamsCallbackFailed, 982 @NonNull Long streamsIterationFailed, 983 @NonNull Long postTransactionOperationsRun, 984 @NonNull Long postTransactionOperationsFailed) { 985 } 986}