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 * 257 * @param transaction transaction whose commit failed 258 * @param databaseType database type known at commit time 259 * @param physicalDuration elapsed time between physical transaction begin and the failed commit attempt 260 * @param throwable failure that prevented commit from completing normally 261 */ 262 default void didFailToCommitPhysicalTransaction(@NonNull Transaction transaction, 263 @NonNull DatabaseType databaseType, 264 @NonNull Duration physicalDuration, 265 @NonNull Throwable throwable) { 266 // No-op by default 267 } 268 269 /** 270 * Called after Pyranid successfully rolls back a physical JDBC transaction. 271 * 272 * @param transaction transaction that rolled back 273 * @param databaseType database type known at rollback time 274 * @param physicalDuration elapsed time between physical transaction begin and rollback 275 */ 276 default void didRollbackPhysicalTransaction(@NonNull Transaction transaction, 277 @NonNull DatabaseType databaseType, 278 @NonNull Duration physicalDuration) { 279 // No-op by default 280 } 281 282 /** 283 * Called after Pyranid fails to roll back a physical JDBC transaction. 284 * 285 * @param transaction transaction whose rollback failed 286 * @param databaseType database type known at rollback time 287 * @param physicalDuration elapsed time between physical transaction begin and the failed rollback attempt 288 * @param throwable failure that prevented rollback from completing normally 289 */ 290 default void didFailToRollbackPhysicalTransaction(@NonNull Transaction transaction, 291 @NonNull DatabaseType databaseType, 292 @NonNull Duration physicalDuration, 293 @NonNull Throwable throwable) { 294 // No-op by default 295 } 296 297 /** 298 * Called after a post-transaction operation runs. 299 * 300 * @param transaction transaction whose post-transaction operation ran 301 * @param result transaction result visible to the post-transaction operation 302 * @param databaseType database type known at post-transaction execution time 303 * @param duration elapsed time spent running the post-transaction operation 304 * @param throwable failure thrown by the post-transaction operation, or {@code null} when it completed normally 305 */ 306 default void didRunPostTransactionOperation(@NonNull Transaction transaction, 307 @NonNull TransactionResult result, 308 @NonNull DatabaseType databaseType, 309 @NonNull Duration duration, 310 @Nullable Throwable throwable) { 311 // No-op by default 312 } 313 314 /** 315 * Called after Pyranid successfully creates a transaction savepoint. 316 * 317 * @param transaction transaction that created the savepoint 318 * @param databaseType database type known at savepoint creation time 319 */ 320 default void didCreateSavepoint(@NonNull Transaction transaction, 321 @NonNull DatabaseType databaseType) { 322 // No-op by default 323 } 324 325 /** 326 * Called after Pyranid successfully rolls back to a transaction savepoint. 327 * 328 * @param transaction transaction that rolled back to the savepoint 329 * @param databaseType database type known at savepoint rollback time 330 */ 331 default void didRollbackToSavepoint(@NonNull Transaction transaction, 332 @NonNull DatabaseType databaseType) { 333 // No-op by default 334 } 335 336 /** 337 * Called after Pyranid successfully releases a transaction savepoint. 338 * 339 * @param transaction transaction that released the savepoint 340 * @param databaseType database type known at savepoint release time 341 */ 342 default void didReleaseSavepoint(@NonNull Transaction transaction, 343 @NonNull DatabaseType databaseType) { 344 // No-op by default 345 } 346 347 /** 348 * Called immediately before Pyranid executes a statement. 349 * 350 * @param ctx statement context for the statement about to execute 351 */ 352 default void willExecuteStatement(@NonNull StatementContext<?> ctx) { 353 // No-op by default 354 } 355 356 /** 357 * Called after Pyranid successfully executes a statement. 358 * 359 * @param ctx statement context for the executed statement 360 * @param statementLog diagnostic statement log for the execution 361 * @param result statement execution result 362 */ 363 default void didExecuteStatement(@NonNull StatementContext<?> ctx, 364 @NonNull StatementLog<?> statementLog, 365 @NonNull StatementResult result) { 366 // No-op by default 367 } 368 369 /** 370 * Called after Pyranid fails to execute a statement. 371 * 372 * @param ctx statement context for the failed statement 373 * @param statementLog diagnostic statement log for the failed execution 374 * @param databaseType database type known at statement failure time 375 * @param throwable failure thrown while executing the statement 376 */ 377 default void didFailToExecuteStatement(@NonNull StatementContext<?> ctx, 378 @NonNull StatementLog<?> statementLog, 379 @NonNull DatabaseType databaseType, 380 @NonNull Throwable throwable) { 381 // No-op by default 382 } 383 384 /** 385 * Called immediately before Pyranid opens a streaming statement. 386 * 387 * @param ctx statement context for the stream about to open 388 */ 389 default void willOpenStream(@NonNull StatementContext<?> ctx) { 390 // No-op by default 391 } 392 393 /** 394 * Called after Pyranid successfully opens a streaming statement. 395 * 396 * @param ctx statement context for the opened stream 397 * @param openDuration elapsed time spent opening the stream 398 */ 399 default void didOpenStream(@NonNull StatementContext<?> ctx, 400 @NonNull Duration openDuration) { 401 // No-op by default 402 } 403 404 /** 405 * Called after Pyranid fails to open a streaming statement. 406 * 407 * @param ctx statement context for the stream that failed to open 408 * @param databaseType database type known at stream-open failure time 409 * @param openDuration elapsed time spent attempting to open the stream 410 * @param throwable failure that prevented the stream from opening 411 */ 412 default void didFailToOpenStream(@NonNull StatementContext<?> ctx, 413 @NonNull DatabaseType databaseType, 414 @NonNull Duration openDuration, 415 @NonNull Throwable throwable) { 416 // No-op by default 417 } 418 419 /** 420 * Called after an opened stream reaches a terminal state. 421 * <p> 422 * Pyranid does not emit this callback for streams that fail before they open; those failures are reported through 423 * {@link #didFailToOpenStream(StatementContext, DatabaseType, Duration, Throwable)}. 424 * 425 * @param ctx statement context for the stream 426 * @param outcome terminal stream outcome 427 * @param rowsConsumed number of rows consumed from the stream 428 * @param streamDuration elapsed time between stream-open start and terminal close handling 429 * @param throwable failure associated with the terminal outcome, or {@code null} when no failure is available 430 */ 431 default void didCloseStream(@NonNull StatementContext<?> ctx, 432 @NonNull StreamTerminalOutcome outcome, 433 @NonNull Long rowsConsumed, 434 @NonNull Duration streamDuration, 435 @Nullable Throwable throwable) { 436 // No-op by default 437 } 438 439 /** 440 * Called immediately before Pyranid attempts to open a notification session. 441 * <p> 442 * Argument validation, ambient-transaction rejection, database-type resolution, and backend-specific channel 443 * validation occur before this callback. Exactly one of 444 * {@link #didOpenNotificationSession(DatabaseType, UUID, Duration)} or 445 * {@link #didFailToOpenNotificationSession(DatabaseType, UUID, Duration, Throwable)} follows it. 446 * 447 * @param databaseType database type resolved for the notification session 448 * @param notificationSessionId identifier for this notification-session invocation 449 * @since 4.6.0 450 */ 451 default void willOpenNotificationSession(@NonNull DatabaseType databaseType, 452 @NonNull UUID notificationSessionId) { 453 // No-op by default 454 } 455 456 /** 457 * Called after Pyranid successfully opens a notification session and registers every requested channel. 458 * <p> 459 * This callback occurs before the application operation is dispatched. It does not indicate that application 460 * reconciliation or readiness work has completed. 461 * 462 * @param databaseType database type resolved for the notification session 463 * @param notificationSessionId identifier for this notification-session invocation 464 * @param openDuration elapsed time from the corresponding 465 * {@link #willOpenNotificationSession(DatabaseType, UUID)} callback through successful 466 * channel registration 467 * @since 4.6.0 468 */ 469 default void didOpenNotificationSession(@NonNull DatabaseType databaseType, 470 @NonNull UUID notificationSessionId, 471 @NonNull Duration openDuration) { 472 // No-op by default 473 } 474 475 /** 476 * Called after Pyranid fails to open a notification session. 477 * <p> 478 * This is the terminal lifecycle callback for an invocation that never reached 479 * {@link #didOpenNotificationSession(DatabaseType, UUID, Duration)}. It is not followed by 480 * {@link #didCloseNotificationSession(DatabaseType, UUID, NotificationSessionOutcome, Duration, Throwable)}. 481 * Pyranid emits it after selecting the opening failure and before cleaning up any acquired candidate connection. 482 * 483 * @param databaseType database type resolved for the notification session 484 * @param notificationSessionId identifier for this notification-session invocation 485 * @param openDuration elapsed time from the corresponding 486 * {@link #willOpenNotificationSession(DatabaseType, UUID)} callback through the opening 487 * failure 488 * @param throwable failure that prevented the notification session from opening 489 * @since 4.6.0 490 */ 491 default void didFailToOpenNotificationSession(@NonNull DatabaseType databaseType, 492 @NonNull UUID notificationSessionId, 493 @NonNull Duration openDuration, 494 @NonNull Throwable throwable) { 495 // No-op by default 496 } 497 498 /** 499 * Called when Pyranid delivers a nonempty notification batch to application code. 500 * <p> 501 * Empty receive results and notifications discarded during setup or cleanup are not reported. 502 * 503 * @param databaseType database type resolved for the notification session 504 * @param notificationSessionId identifier for the notification session that delivered the batch 505 * @param notificationCount number of notifications in the delivered batch 506 * @since 4.6.0 507 */ 508 default void didDeliverNotificationBatch(@NonNull DatabaseType databaseType, 509 @NonNull UUID notificationSessionId, 510 @NonNull Long notificationCount) { 511 // No-op by default 512 } 513 514 /** 515 * Called when a receive operation detects terminal uncertainty or loss of an opened notification connection. 516 * <p> 517 * This callback is emitted at most once for a notification session. It does not report setup failures, failures 518 * from ordinary database operations performed by application code, or cleanup-only failures. 519 * 520 * @param databaseType database type resolved for the notification session 521 * @param notificationSessionId identifier for the notification session whose connection was lost 522 * @param throwable terminal connection failure 523 * @since 4.6.0 524 */ 525 default void didLoseNotificationConnection(@NonNull DatabaseType databaseType, 526 @NonNull UUID notificationSessionId, 527 @NonNull Throwable throwable) { 528 // No-op by default 529 } 530 531 /** 532 * Called after an opened notification session reaches a terminal state and cleanup completes. 533 * <p> 534 * Every invocation that emits {@link #didOpenNotificationSession(DatabaseType, UUID, Duration)} emits this callback 535 * exactly once. Invocations that fail before opening terminate through 536 * {@link #didFailToOpenNotificationSession(DatabaseType, UUID, Duration, Throwable)} instead. 537 * 538 * @param databaseType database type resolved for the notification session 539 * @param notificationSessionId identifier for the notification session 540 * @param outcome terminal notification-session outcome 541 * @param sessionDuration elapsed time from the corresponding 542 * {@link #willOpenNotificationSession(DatabaseType, UUID)} callback through completed 543 * cleanup 544 * @param throwable selected failure for {@link NotificationSessionOutcome#FAILED}, or {@code null} for 545 * {@link NotificationSessionOutcome#CALLBACK_RETURNED} and 546 * {@link NotificationSessionOutcome#INTERRUPTED} 547 * @since 4.6.0 548 */ 549 default void didCloseNotificationSession(@NonNull DatabaseType databaseType, 550 @NonNull UUID notificationSessionId, 551 @NonNull NotificationSessionOutcome outcome, 552 @NonNull Duration sessionDuration, 553 @Nullable Throwable throwable) { 554 // No-op by default 555 } 556 557 /** 558 * Returns a notification counter snapshot if this collector supports in-process inspection. 559 * <p> 560 * Implementations may read counters independently without cross-counter atomicity. Callers that need 561 * invariant-consistent snapshots should drain in-flight work before reading. 562 * 563 * @return a notification metrics snapshot, if supported 564 * @since 4.6.0 565 */ 566 @NonNull 567 default Optional<NotificationSnapshot> notificationSnapshot() { 568 return Optional.empty(); 569 } 570 571 /** 572 * Returns a counter snapshot if this collector supports in-process inspection. 573 * <p> 574 * Implementations may read counters independently without cross-counter atomicity. Callers that need invariant-consistent 575 * snapshots should drain in-flight work before reading. 576 * 577 * @return a metrics snapshot, if supported 578 */ 579 @NonNull 580 default Optional<Snapshot> snapshot() { 581 return Optional.empty(); 582 } 583 584 /** 585 * Best-effort reset for test and ad-hoc use. 586 * <p> 587 * Concurrent updates may race with reset operations. Production rolling-window semantics should be implemented outside 588 * this interface. 589 */ 590 default void reset() { 591 // No-op by default 592 } 593 594 /** 595 * Returns the shared no-op metrics collector. 596 * 597 * @return no-op metrics collector used when metrics collection is disabled 598 */ 599 @NonNull 600 static MetricsCollector disabledInstance() { 601 return DisabledMetricsCollector.defaultInstance(); 602 } 603 604 /** 605 * Creates a fresh in-memory counter collector. 606 * <p> 607 * This collector is intended for tests and lightweight local inspection through {@link #snapshot()} and 608 * {@link #notificationSnapshot()}. 609 * 610 * @return new in-memory metrics collector 611 */ 612 @NonNull 613 static MetricsCollector inMemoryInstance() { 614 return InMemoryMetricsCollector.defaultInstance(); 615 } 616 617 /** 618 * Terminal outcome for an opened notification session. 619 * 620 * @since 4.6.0 621 */ 622 enum NotificationSessionOutcome { 623 /** 624 * The application callback returned and notification-session cleanup completed normally. 625 */ 626 CALLBACK_RETURNED, 627 628 /** 629 * Cooperative interruption ended the notification session and cleanup completed normally. 630 */ 631 INTERRUPTED, 632 633 /** 634 * Setup after opening, notification receive, application callback, or cleanup failed. 635 */ 636 FAILED 637 } 638 639 /** 640 * Counter snapshot for notification metrics. 641 * <p> 642 * Implementations may read counters independently without cross-counter atomicity. Callers that need 643 * invariant-consistent snapshots should drain in-flight work before reading. 644 * <p> 645 * Instances are immutable value objects created through {@link #of(Long, Long, Long, Long, Long, Long, Long)}. 646 * Future releases may add counters and accessors while retaining this factory with defined defaults for any 647 * newly added values. 648 * 649 * @since 4.6.0 650 */ 651 @ThreadSafe 652 final class NotificationSnapshot { 653 @NonNull 654 private final Long sessionsStarted; 655 @NonNull 656 private final Long sessionsOpened; 657 @NonNull 658 private final Long sessionsCallbackReturned; 659 @NonNull 660 private final Long sessionsInterrupted; 661 @NonNull 662 private final Long sessionsFailed; 663 @NonNull 664 private final Long batchesDelivered; 665 @NonNull 666 private final Long notificationsDelivered; 667 668 private NotificationSnapshot(@NonNull Long sessionsStarted, 669 @NonNull Long sessionsOpened, 670 @NonNull Long sessionsCallbackReturned, 671 @NonNull Long sessionsInterrupted, 672 @NonNull Long sessionsFailed, 673 @NonNull Long batchesDelivered, 674 @NonNull Long notificationsDelivered) { 675 this.sessionsStarted = requireNonNull(sessionsStarted); 676 this.sessionsOpened = requireNonNull(sessionsOpened); 677 this.sessionsCallbackReturned = requireNonNull(sessionsCallbackReturned); 678 this.sessionsInterrupted = requireNonNull(sessionsInterrupted); 679 this.sessionsFailed = requireNonNull(sessionsFailed); 680 this.batchesDelivered = requireNonNull(batchesDelivered); 681 this.notificationsDelivered = requireNonNull(notificationsDelivered); 682 } 683 684 /** 685 * Creates a notification metrics snapshot. 686 * 687 * @param sessionsStarted valid, type-resolved notification-session invocations that entered measured setup 688 * @param sessionsOpened notification sessions that completed channel registration 689 * @param sessionsCallbackReturned opened notification sessions whose callbacks returned and cleanup completed normally 690 * @param sessionsInterrupted opened notification sessions that ended through clean cooperative interruption 691 * @param sessionsFailed notification-session invocations that failed before opening or after opening 692 * @param batchesDelivered nonempty notification batches delivered to application code 693 * @param notificationsDelivered notifications contained in delivered batches 694 * @return notification metrics snapshot 695 * @throws NullPointerException if any counter is null 696 * @since 4.6.0 697 */ 698 @NonNull 699 public static NotificationSnapshot of(@NonNull Long sessionsStarted, 700 @NonNull Long sessionsOpened, 701 @NonNull Long sessionsCallbackReturned, 702 @NonNull Long sessionsInterrupted, 703 @NonNull Long sessionsFailed, 704 @NonNull Long batchesDelivered, 705 @NonNull Long notificationsDelivered) { 706 return new NotificationSnapshot(sessionsStarted, sessionsOpened, sessionsCallbackReturned, sessionsInterrupted, 707 sessionsFailed, batchesDelivered, notificationsDelivered); 708 } 709 710 /** 711 * @return valid, type-resolved notification-session invocations that entered measured setup 712 * @since 4.6.0 713 */ 714 @NonNull 715 public Long sessionsStarted() { 716 return this.sessionsStarted; 717 } 718 719 /** 720 * @return notification sessions that completed channel registration 721 * @since 4.6.0 722 */ 723 @NonNull 724 public Long sessionsOpened() { 725 return this.sessionsOpened; 726 } 727 728 /** 729 * @return opened notification sessions whose callbacks returned and cleanup completed normally 730 * @since 4.6.0 731 */ 732 @NonNull 733 public Long sessionsCallbackReturned() { 734 return this.sessionsCallbackReturned; 735 } 736 737 /** 738 * @return opened notification sessions that ended through clean cooperative interruption 739 * @since 4.6.0 740 */ 741 @NonNull 742 public Long sessionsInterrupted() { 743 return this.sessionsInterrupted; 744 } 745 746 /** 747 * @return notification-session invocations that failed before opening or after opening 748 * @since 4.6.0 749 */ 750 @NonNull 751 public Long sessionsFailed() { 752 return this.sessionsFailed; 753 } 754 755 /** 756 * @return nonempty notification batches delivered to application code 757 * @since 4.6.0 758 */ 759 @NonNull 760 public Long batchesDelivered() { 761 return this.batchesDelivered; 762 } 763 764 /** 765 * @return notifications contained in delivered batches 766 * @since 4.6.0 767 */ 768 @NonNull 769 public Long notificationsDelivered() { 770 return this.notificationsDelivered; 771 } 772 773 @Override 774 public boolean equals(@Nullable Object object) { 775 if (this == object) 776 return true; 777 778 if (!(object instanceof NotificationSnapshot notificationSnapshot)) 779 return false; 780 781 return sessionsStarted().equals(notificationSnapshot.sessionsStarted()) 782 && sessionsOpened().equals(notificationSnapshot.sessionsOpened()) 783 && sessionsCallbackReturned().equals(notificationSnapshot.sessionsCallbackReturned()) 784 && sessionsInterrupted().equals(notificationSnapshot.sessionsInterrupted()) 785 && sessionsFailed().equals(notificationSnapshot.sessionsFailed()) 786 && batchesDelivered().equals(notificationSnapshot.batchesDelivered()) 787 && notificationsDelivered().equals(notificationSnapshot.notificationsDelivered()); 788 } 789 790 @Override 791 public int hashCode() { 792 return Objects.hash(sessionsStarted(), sessionsOpened(), sessionsCallbackReturned(), sessionsInterrupted(), 793 sessionsFailed(), batchesDelivered(), notificationsDelivered()); 794 } 795 796 @Override 797 @NonNull 798 public String toString() { 799 return "NotificationSnapshot[" + 800 "sessionsStarted=" + sessionsStarted() + 801 ", sessionsOpened=" + sessionsOpened() + 802 ", sessionsCallbackReturned=" + sessionsCallbackReturned() + 803 ", sessionsInterrupted=" + sessionsInterrupted() + 804 ", sessionsFailed=" + sessionsFailed() + 805 ", batchesDelivered=" + batchesDelivered() + 806 ", notificationsDelivered=" + notificationsDelivered() + 807 ']'; 808 } 809 } 810 811 /** 812 * Logical outcome for a closure-based transaction. 813 */ 814 enum TransactionClosureOutcome { 815 /** 816 * The transaction used a physical JDBC transaction and commit completed normally. 817 */ 818 COMMITTED, 819 820 /** 821 * The transaction used a physical JDBC transaction and was rolled back, including a recognized commit-time 822 * serialization failure whose follow-up rollback completed normally. 823 */ 824 ROLLED_BACK, 825 826 /** 827 * The transaction closure exited without ever acquiring a JDBC connection. 828 */ 829 NO_PHYSICAL_TX, 830 831 /** 832 * The transaction failed before Pyranid could report a normal commit or rollback outcome. 833 */ 834 FAILED 835 } 836 837 /** 838 * Physical transaction begin phase that failed. 839 */ 840 enum PhysicalTransactionBeginFailurePhase { 841 /** 842 * Connection acquisition from the configured {@link javax.sql.DataSource} failed. 843 */ 844 ACQUIRE_CONNECTION, 845 846 /** 847 * Reading the connection's initial autocommit setting failed. 848 */ 849 READ_INITIAL_AUTOCOMMIT, 850 851 /** 852 * Reading the connection's initial transaction isolation failed. 853 */ 854 READ_INITIAL_ISOLATION, 855 856 /** 857 * Reading the connection's initial read-only setting failed. 858 */ 859 READ_INITIAL_READ_ONLY, 860 861 /** 862 * Disabling autocommit for the transaction failed. 863 */ 864 SET_AUTOCOMMIT_FALSE, 865 866 /** 867 * Applying the requested transaction isolation failed. 868 */ 869 SET_ISOLATION, 870 871 /** 872 * Applying the requested read-only setting failed. 873 */ 874 SET_READ_ONLY 875 } 876 877 /** 878 * Terminal outcome for an opened stream. 879 */ 880 enum StreamTerminalOutcome { 881 /** 882 * Stream iteration reached the end of the result set and cleanup completed without an iteration or callback failure. 883 */ 884 COMPLETED_NORMALLY, 885 886 /** 887 * The stream was closed before all rows were consumed. 888 */ 889 EARLY_CLOSE, 890 891 /** 892 * The caller-provided stream callback failed. 893 */ 894 CALLBACK_FAILURE, 895 896 /** 897 * Result-set iteration failed while the stream was open. 898 */ 899 ITERATION_FAILURE, 900 901 /** 902 * The stream failed before it opened. 903 * <p> 904 * Pyranid normally reports this through {@link #didFailToOpenStream(StatementContext, DatabaseType, Duration, 905 * Throwable)} instead of {@link #didCloseStream(StatementContext, StreamTerminalOutcome, Long, Duration, Throwable)}. 906 */ 907 OPEN_FAILURE 908 } 909 910 /** 911 * Counter snapshot for collectors that support in-process inspection. 912 * <p> 913 * Implementations may read counters independently without cross-counter atomicity. Callers that need invariant-consistent 914 * snapshots should drain in-flight work before reading. 915 * 916 * @param connectionsAcquiredStatementScope statement-scoped connection acquisitions that completed normally 917 * @param connectionsAcquiredTransactionScope transaction-scoped connection acquisitions that completed normally 918 * @param connectionsFailedStatementScope statement-scoped connection acquisitions that failed 919 * @param connectionsFailedTransactionScope transaction-scoped connection acquisitions that failed 920 * @param connectionReleaseFailuresStatementScope statement-scoped connection releases that failed 921 * @param connectionReleaseFailuresTransactionScope transaction-scoped connection releases that failed 922 * @param transactionClosuresEntered closure-based transactions entered 923 * @param transactionClosuresExited closure-based transactions exited 924 * @param transactionClosuresCommitted closure-based transactions that committed 925 * @param transactionClosuresRolledBack closure-based transactions that rolled back 926 * @param transactionClosuresNoPhysical closure-based transactions that exited without a physical JDBC transaction 927 * @param transactionClosuresFailed closure-based transactions that failed before a normal commit or rollback outcome 928 * @param physicalTransactionsBegun physical JDBC transactions begun 929 * @param physicalTransactionsBeginFailed physical JDBC transactions that failed while beginning 930 * @param physicalTransactionsCommitted physical JDBC transactions committed 931 * @param physicalTransactionsCommitFailed physical JDBC transactions whose commit failed 932 * @param physicalTransactionsRolledBack physical JDBC transactions rolled back 933 * @param physicalTransactionsRollbackFailed physical JDBC transactions whose rollback failed 934 * @param savepointsCreated transaction savepoints created 935 * @param savepointsRolledBack transaction savepoints rolled back to 936 * @param savepointsReleased transaction savepoints released 937 * @param statementsExecuted statements that executed successfully 938 * @param statementsFailed statements that failed during execution 939 * @param streamsOpened streams that opened successfully 940 * @param streamsOpenFailures streams that failed before opening 941 * @param streamsClosedNormally opened streams that reached the end of the result set 942 * @param streamsEarlyClosed opened streams that closed before all rows were consumed 943 * @param streamsCallbackFailed opened streams whose caller-provided callback failed 944 * @param streamsIterationFailed opened streams whose result-set iteration failed 945 * @param postTransactionOperationsRun post-transaction operations that ran 946 * @param postTransactionOperationsFailed post-transaction operations that failed 947 */ 948 @ThreadSafe 949 record Snapshot(@NonNull Long connectionsAcquiredStatementScope, 950 @NonNull Long connectionsAcquiredTransactionScope, 951 @NonNull Long connectionsFailedStatementScope, 952 @NonNull Long connectionsFailedTransactionScope, 953 @NonNull Long connectionReleaseFailuresStatementScope, 954 @NonNull Long connectionReleaseFailuresTransactionScope, 955 @NonNull Long transactionClosuresEntered, 956 @NonNull Long transactionClosuresExited, 957 @NonNull Long transactionClosuresCommitted, 958 @NonNull Long transactionClosuresRolledBack, 959 @NonNull Long transactionClosuresNoPhysical, 960 @NonNull Long transactionClosuresFailed, 961 @NonNull Long physicalTransactionsBegun, 962 @NonNull Long physicalTransactionsBeginFailed, 963 @NonNull Long physicalTransactionsCommitted, 964 @NonNull Long physicalTransactionsCommitFailed, 965 @NonNull Long physicalTransactionsRolledBack, 966 @NonNull Long physicalTransactionsRollbackFailed, 967 @NonNull Long savepointsCreated, 968 @NonNull Long savepointsRolledBack, 969 @NonNull Long savepointsReleased, 970 @NonNull Long statementsExecuted, 971 @NonNull Long statementsFailed, 972 @NonNull Long streamsOpened, 973 @NonNull Long streamsOpenFailures, 974 @NonNull Long streamsClosedNormally, 975 @NonNull Long streamsEarlyClosed, 976 @NonNull Long streamsCallbackFailed, 977 @NonNull Long streamsIterationFailed, 978 @NonNull Long postTransactionOperationsRun, 979 @NonNull Long postTransactionOperationsFailed) { 980 } 981}