* Copyright 2017 Pivotal Software, Inc.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * See the License for the specific language governing permissions and
 * limitations under the License.
package io.micrometer.core.instrument.binder.jpa;

import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.hibernate.SessionFactory;
import org.hibernate.stat.Statistics;

import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import java.util.concurrent.TimeUnit;
import java.util.function.ToDoubleFunction;

 * A {@link MeterBinder} implementation that provides Hibernate metrics. It exposes the
 * same stats as would be exposed when calling {@code Statistics#logSummary}.
 * @author Marten Deinum
 * @author Jon Schneider
public class HibernateMetrics implements MeterBinder {

    public static void monitor(MeterRegistry meterRegistry, EntityManagerFactory emf, String name, String... tags) {
        monitor(meterRegistry, emf, name, Tags.zip(tags));

    public static void monitor(MeterRegistry meterRegistry, EntityManagerFactory emf, String name, Iterable<Tag> tags) {
        new HibernateMetrics(emf, name, tags).bindTo(meterRegistry);

    private final Iterable<Tag> tags;
    private final Statistics stats;

    private HibernateMetrics(EntityManagerFactory emf, String name, Iterable<Tag> tags) {
        this.tags = Tags.concat(tags, "entityManagerFactory", name);
        this.stats = hasStatisticsEnabled(emf) ? getStatistics(emf) : null;

    private void counter(MeterRegistry registry, String name, String description, ToDoubleFunction<Statistics> f, String... extraTags) {
        FunctionCounter.builder(name, stats, f)

    public void bindTo(MeterRegistry registry) {
        if (this.stats == null)

        // Session stats
        counter(registry, "hibernate.sessions.open", "Sessions opened", Statistics::getSessionOpenCount);
        counter(registry, "hibernate.sessions.closed", "Sessions closed", Statistics::getSessionCloseCount);

        // Transaction stats
        counter(registry, "hibernate.transactions", "The number of transactions we know to have been successful",
            Statistics::getSuccessfulTransactionCount, "result", "success");
        counter(registry, "hibernate.transactions", "The number of transactions we know to have failed",
            s -> s.getTransactionCount() - s.getSuccessfulTransactionCount(), "result", "failure");
        counter(registry, "hibernate.optimistic.failures", "The number of StaleObjectStateExceptions that have occurred",

        counter(registry, "hibernate.flushes", "The global number of flushes executed by sessions (either implicit or explicit)",
        counter(registry, "hibernate.connections.obtained", "Get the global number of connections asked by the sessions " +
            "(the actual number of connections used may be much smaller depending " +
            "whether you use a connection pool or not)", Statistics::getConnectCount);

        // Statements
        counter(registry, "hibernate.statements", "The number of prepared statements that were acquired",
            Statistics::getPrepareStatementCount, "status", "prepared");
        counter(registry, "hibernate.statements", "The number of prepared statements that were released",
            Statistics::getCloseStatementCount, "status", "closed");

        // Second Level Caching
        counter(registry, "hibernate.second.level.cache.requests", "The number of cacheable entities/collections successfully retrieved from the cache",
            Statistics::getSecondLevelCacheHitCount, "result", "hit");
        counter(registry, "hibernate.second.level.cache.requests", "The number of cacheable entities/collections not found in the cache and loaded from the database",
            Statistics::getSecondLevelCacheMissCount, "result", "miss");
        counter(registry, "hibernate.second.level.cache.puts", "The number of cacheable entities/collections put in the cache",

        // Entity information
        counter(registry, "hibernate.entities.deletes", "The number of entity deletes", Statistics::getEntityDeleteCount);
        counter(registry, "hibernate.entities.fetches", "The number of entity fetches", Statistics::getEntityFetchCount);
        counter(registry, "hibernate.entities.inserts", "The number of entity inserts", Statistics::getEntityInsertCount);
        counter(registry, "hibernate.entities.loads", "The number of entity loads", Statistics::getEntityLoadCount);
        counter(registry, "hibernate.entities.updates", "The number of entity updates", Statistics::getEntityUpdateCount);

        // Collections
        counter(registry, "hibernate.collections.deletes", "The number of collection deletes", Statistics::getCollectionRemoveCount);
        counter(registry, "hibernate.collections.fetches", "The number of collection fetches", Statistics::getCollectionFetchCount);
        counter(registry, "hibernate.collections.loads", "The number of collection loads", Statistics::getCollectionLoadCount);
        counter(registry, "hibernate.collections.recreates", "The number of collections recreated", Statistics::getCollectionRecreateCount);
        counter(registry, "hibernate.collections.updates", "The number of collection updates", Statistics::getCollectionUpdateCount);

        // Natural Id cache
        counter(registry, "hibernate.cache.natural.id.requests", "The number of cached naturalId lookups successfully retrieved from cache",
            Statistics::getNaturalIdCacheHitCount, "result", "hit");
        counter(registry, "hibernate.cache.natural.id.requests", "The number of cached naturalId lookups not found in cache",
            Statistics::getNaturalIdCacheMissCount, "result", "miss");
        counter(registry, "hibernate.cache.natural.id.puts", "The number of cacheable naturalId lookups put in cache",

        counter(registry, "hibernate.query.natural.id.executions", "The number of naturalId queries executed against the database",

        TimeGauge.builder("hibernate.query.natural.id.executions.max", stats, TimeUnit.MILLISECONDS, Statistics::getNaturalIdQueryExecutionMaxTime)
            .description("The maximum query time for naturalId queries executed against the database")

        // Query stats
        counter(registry, "hibernate.query.executions", "The number of executed queries", Statistics::getQueryExecutionCount);

        TimeGauge.builder("hibernate.query.executions.max", stats, TimeUnit.MILLISECONDS, Statistics::getQueryExecutionMaxTime)
            .description("The time of the slowest query")

        // Update timestamp cache
        counter(registry, "hibernate.cache.update.timestamps.requests", "The number of timestamps successfully retrieved from cache",
            Statistics::getUpdateTimestampsCacheHitCount, "result", "hit");
        counter(registry, "hibernate.cache.update.timestamps.requests", "The number of tables for which no update timestamps was not found in cache",
            Statistics::getUpdateTimestampsCacheMissCount, "result", "miss");
        counter(registry, "hibernate.cache.update.timestamps.puts", "The number of timestamps put in cache",

        // Query Caching
        counter(registry, "hibernate.cache.query.requests", "The number of cached queries successfully retrieved from cache",
            Statistics::getQueryCacheHitCount, "result", "hit");
        counter(registry, "hibernate.cache.query.requests", "The number of cached queries not found in cache",
            Statistics::getQueryCacheMissCount, "result", "miss");
        counter(registry, "hibernate.cache.query.puts", "The number of cacheable queries put in cache",

    private boolean hasStatisticsEnabled(EntityManagerFactory emf) {
        final Statistics stats = getStatistics(emf);
        return (stats != null && stats.isStatisticsEnabled());

     * Get the {@code Statistics} object from the underlying {@code SessionFactory}. If it isn't hibernate that is
     * used return {@code null}.
     * @param emf an {@code EntityManagerFactory}
     * @return the {@code Statistics} from the underlying {@code SessionFactory} or {@code null}.
    private Statistics getStatistics(EntityManagerFactory emf) {
        try {
            SessionFactory sf = emf.unwrap(SessionFactory.class);
            return sf.getStatistics();
        } catch (PersistenceException pe) {
            return null;