/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.server.util;

import java.lang.management.ManagementFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

import javax.management.MBeanServer;
import javax.persistence.EntityManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.stat.QueryStatistics;
import org.hibernate.stat.Statistics;

import org.rhq.core.server.PersistenceUtility;

/**
 * @author Joseph Marques
 */
public class HibernatePerformanceMonitor {
    private static final Log log = LogFactory.getLog(HibernatePerformanceMonitor.class);

    private ConcurrentMap<Long, HibernateStatisticsStopWatch> watches;
    private static HibernatePerformanceMonitor singleton = new HibernatePerformanceMonitor();
    private AtomicLong idGenerator = new AtomicLong(0);

    private HibernatePerformanceMonitor() {
        watches = new ConcurrentHashMap<Long, HibernateStatisticsStopWatch>();
    }

    public static HibernatePerformanceMonitor get() {
        return singleton;
    }

    public void zeroStats() {
        if (log.isDebugEnabled()) {
            EntityManager entityManager = LookupUtil.getEntityManager();
            MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
            Statistics stats = PersistenceUtility.getStatisticsService(entityManager, platformMBeanServer);
            stats.clear();
        }
    }

    public long start() {
        if (log.isDebugEnabled()) {
            EntityManager entityManager = LookupUtil.getEntityManager();
            HibernateStatisticsStopWatch watch = new HibernateStatisticsStopWatch(entityManager);
            long id = idGenerator.incrementAndGet();
            watches.put(id, watch);
            watch.start();
            return id;
        }
        return 0;
    }

    public void stop(long id, String logPrefix) {
        if (log.isDebugEnabled()) {
            HibernateStatisticsStopWatch watch = watches.remove(id);
            if (watch == null) {
                return; // could happen if debugging was turned on and the start() call was already skipped
            }
            watch.stop();

            String cause = "";
            if (watch.getQueryExecutions() != 0) {
                if ((watch.getConnects() / (double) (watch.getEntityLoads() + watch.getQueryExecutions())) >= 5.0) {
                    cause = "(N+1 issue?) ";// might indicate need for LEFT JOIN FETCHes
                }
                if ((watch.getTransations() / (double) watch.getQueryExecutions()) >= 5.0) {
                    cause = "(xaction nesting?) "; // might indicate excessive @REQUIRES_NEW
                } else if (watch.getTransations() > 10) {
                    cause = "(too many xactions?";
                }
            }
            if (watch.getTime() > 3000) {
                cause = "(slowness?) "; // might indicate inefficient query or table contention
            }

            String callingContext = " for " + (logPrefix == null ? "(unknown)" : logPrefix);
            log.debug(watch.toString() + cause + callingContext);

            if (logPrefix.contains("URL")) {
                String[] queries = watch.getStats().getQueries();
                for (int i = 0; i < queries.length; i++) {
                    String query = queries[i];
                    QueryStatistics queryStats = watch.getStats().getQueryStatistics(query);
                    log.debug("queryString[" + i + "]=" + queries[i]);
                    log.debug("queryStats[" + i + "=" + queryStats);
                }
                //watch.getStats().logSummary();
            }
        }
    }

}
