diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle index eec8d15dcc..eb50f191b4 100644 --- a/gradle/java-module.gradle +++ b/gradle/java-module.gradle @@ -89,6 +89,8 @@ dependencies { testImplementation testLibs.byteman + testImplementation testLibs.jfrUnit + testRuntimeOnly testLibs.log4j2 testRuntimeOnly libs.byteBuddy diff --git a/hibernate-core/src/main/java/org/hibernate/event/jfr/SessionClosedEvent.java b/hibernate-core/src/main/java/org/hibernate/event/jfr/SessionClosedEvent.java new file mode 100644 index 0000000000..66244806a9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/jfr/SessionClosedEvent.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.event.jfr; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +/** + * @author Steve Ebersole + */ +@Name(SessionClosedEvent.NAME) +@Label("Session Closed") +@Category("Hibernate ORM") +@Description("Hibernate Session closed") +@StackTrace(false) +public class SessionClosedEvent extends Event { + public static final String NAME = "org.hibernate.orm.SessionClosed"; + + @Label("Session Identifier" ) + public String sessionIdentifier; + + @Override + public String toString() { + return NAME + "(" + sessionIdentifier + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/jfr/SessionOpenEvent.java b/hibernate-core/src/main/java/org/hibernate/event/jfr/SessionOpenEvent.java new file mode 100644 index 0000000000..4620808699 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/jfr/SessionOpenEvent.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.event.jfr; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.StackTrace; + +/** + * @author Steve Ebersole + */ +@Name(SessionOpenEvent.NAME) +@Label("Session Opened") +@Category("Hibernate ORM") +@Description("Hibernate Session opened") +@StackTrace(false) +public class SessionOpenEvent extends Event { + public static final String NAME = "org.hibernate.orm.SessionOpened"; + + @Label("Session Identifier" ) + public String sessionIdentifier; + + @Override + public String toString() { + return NAME + "(" + sessionIdentifier + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 91eb0fac5e..3965abf4dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -69,6 +69,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.engine.transaction.spi.TransactionObserver; +import org.hibernate.event.jfr.SessionClosedEvent; +import org.hibernate.event.jfr.SessionOpenEvent; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.AutoFlushEventListener; import org.hibernate.event.spi.ClearEvent; @@ -228,6 +230,11 @@ public class SessionImpl public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); + final SessionOpenEvent sessionOpenEvent = new SessionOpenEvent(); + if ( sessionOpenEvent.isEnabled() ) { + sessionOpenEvent.begin(); + } + persistenceContext = createPersistenceContext(); actionQueue = createActionQueue(); @@ -272,6 +279,14 @@ public class SessionImpl if ( log.isTraceEnabled() ) { log.tracef( "Opened Session [%s] at timestamp: %s", getSessionIdentifier(), currentTimeMillis() ); } + + if ( sessionOpenEvent.isEnabled() ) { + sessionOpenEvent.end(); + if ( sessionOpenEvent.shouldCommit() ) { + sessionOpenEvent.sessionIdentifier = getSessionIdentifier().toString(); + sessionOpenEvent.commit(); + } + } } private FlushMode getInitialFlushMode() { @@ -413,6 +428,11 @@ public class SessionImpl log.tracef( "Closing session [%s]", getSessionIdentifier() ); } + final SessionClosedEvent sessionClosedEvent = new SessionClosedEvent(); + if ( sessionClosedEvent.isEnabled() ) { + sessionClosedEvent.begin(); + } + // todo : we want this check if usage is JPA, but not native Hibernate usage final SessionFactoryImplementor sessionFactory = getSessionFactory(); if ( sessionFactory.getSessionFactoryOptions().isJpaBootstrap() ) { @@ -435,6 +455,14 @@ public class SessionImpl if ( statistics.isStatisticsEnabled() ) { statistics.closeSession(); } + + if ( sessionClosedEvent.isEnabled() ) { + sessionClosedEvent.end(); + if ( sessionClosedEvent.shouldCommit() ) { + sessionClosedEvent.sessionIdentifier = getSessionIdentifier().toString(); + sessionClosedEvent.commit(); + } + } } private boolean isTransactionInProgressAndNotMarkedForRollback() { diff --git a/hibernate-core/src/test/java17/org/hibernate/orm/test/event/jfr/SessionEventTests.java b/hibernate-core/src/test/java17/org/hibernate/orm/test/event/jfr/SessionEventTests.java new file mode 100644 index 0000000000..8008867b72 --- /dev/null +++ b/hibernate-core/src/test/java17/org/hibernate/orm/test/event/jfr/SessionEventTests.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.event.jfr; + +import java.util.List; + +import org.hibernate.event.jfr.SessionClosedEvent; +import org.hibernate.event.jfr.SessionOpenEvent; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import jdk.jfr.consumer.RecordedEvent; +import org.moditect.jfrunit.EnableEvent; +import org.moditect.jfrunit.JfrEventTest; +import org.moditect.jfrunit.JfrEvents; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * @author Steve Ebersole + */ +@JfrEventTest +@DomainModel +@SessionFactory +public class SessionEventTests { + public JfrEvents jfrEvents = new JfrEvents(); + + @Test + @EnableEvent(SessionOpenEvent.NAME) + @EnableEvent(SessionClosedEvent.NAME) + public void testSessionOpenEvent(SessionFactoryScope scope) { + final String openedSessionIdentifier = scope.fromSession( (session) -> { + final List events = jfrEvents.events().filter( recordedEvent -> recordedEvent.hasField("sessionIdentifier" ) ).toList(); + assertThat( events ).hasSize( 1 ); + final RecordedEvent event = events.get( 0 ); + assertThat( event.getEventType().getName() ).isEqualTo( SessionOpenEvent.NAME ); + assertThat( event.getString( "sessionIdentifier" ) ).isEqualTo( session.getSessionIdentifier().toString() ); + + jfrEvents.reset(); + + return event.getString( "sessionIdentifier" ); + } ); + + final List events = jfrEvents.events().filter( recordedEvent -> recordedEvent.hasField("sessionIdentifier" ) ).toList(); + assertThat( events ).hasSize( 1 ); + final RecordedEvent event = events.get( 0 ); + assertThat( event.getEventType().getName() ).isEqualTo( SessionClosedEvent.NAME ); + assertThat( event.getString( "sessionIdentifier" ) ).isEqualTo( openedSessionIdentifier ); + } +} diff --git a/hibernate-core/src/test/java17/org/hibernate/orm/test/event/jfr/package-info.java b/hibernate-core/src/test/java17/org/hibernate/orm/test/event/jfr/package-info.java new file mode 100644 index 0000000000..c8db47661e --- /dev/null +++ b/hibernate-core/src/test/java17/org/hibernate/orm/test/event/jfr/package-info.java @@ -0,0 +1,5 @@ +/** + * Tests for Hibernate's JFR events. Well the events are roduced on Java 11, the JfrUnit + * framewqork used in testing only works on JDK 16+. + */ +package org.hibernate.test.event.jfr; \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index bbd18b2eee..5b6da5ad73 100644 --- a/settings.gradle +++ b/settings.gradle @@ -177,6 +177,8 @@ dependencyResolutionManagement { def wildFlyTxnClientVersion = version "wildFlyTxnClient", "2.0.0.Final" def xapoolVersion = version "xapool", "1.5.0" + def jfrUnitVersion = version "jfrUnit", "1.0.0.Alpha2" + library( "junit5Api", "org.junit.jupiter", "junit-jupiter-api" ).versionRef( junit5Version ) library( "junit5Engine", "org.junit.jupiter", "junit-jupiter-engine" ).versionRef( junit5Version ) library( "junit5Params", "org.junit.jupiter", "junit-jupiter-params" ).versionRef( junit5Version ) @@ -206,6 +208,8 @@ dependencyResolutionManagement { library( "wildFlyTxnClient", "org.wildfly.transaction", "wildfly-transaction-client-jakarta" ).versionRef( wildFlyTxnClientVersion ) library( "weld", "org.jboss.weld.se", "weld-se-shaded" ).versionRef( weldVersion ) + + library( "jfrUnit", "org.moditect.jfrunit", "jfrunit-core" ).versionRef( jfrUnitVersion ) } dbLibs { def h2Version = version "h2", overrideableVersion( "gradle.libs.versions.h2", "2.2.224" )