<commit_before>/*
 * Copyright (C) 2014  The Central Perf authors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.centralperf.service;

import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.centralperf.model.dao.Run;
import org.centralperf.model.graph.ErrorRateGraph;
import org.centralperf.model.graph.ResponseSizeGraph;
import org.centralperf.model.graph.ResponseTimeGraph;
import org.centralperf.model.graph.RunStats;
import org.centralperf.model.graph.SummaryGraph;
import org.centralperf.repository.RunRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * This class is singleton service to collect run data and graphs.<br>
 * A cache mechanism with time eviction is used to avoid database overload if too many refresh
 * are asked by users.
 * @since 1.0
 */

@Service
public class RunStatisticsService {

	@Resource
	private RunRepository runRepository;
	
	@PersistenceContext
	private EntityManager em;
	
	@Resource
	private ScriptLauncherService scriptLauncherService;
	
    @Value("#{appProperties['report.cache.delay.seconds']}")
    private Long cacheRefreshDelay;
    
    @Value("#{appProperties['report.scaling.level1.seconds']}")
    private Long limitOfFisrtScaling;
    
    @Value("#{appProperties['report.scaling.level2.seconds']}")
    private Long limitOfSecondScaling;
	
    //Cache can't be load on object construction as refresh delay is not yet set
    private LoadingCache<Long, SummaryGraph> summaryGraphCache;
    private LoadingCache<Long, ResponseTimeGraph> responseTimeGraphCache;
    private LoadingCache<Long, ResponseSizeGraph> responseSizeGraphCache;
    private LoadingCache<Long, ErrorRateGraph> errorRateGraphCache;
    private LoadingCache<Long, RunStats> runStatsCache;
    
	private static final Logger log = LoggerFactory.getLogger(RunStatisticsService.class);
	
	/*
	 * Use of cache is necessary to avoid database saturation if more than one user
	 * is looking UI run detail
	 */	
	private CacheLoader<Long, SummaryGraph> summaryGraphLoader = new CacheLoader<Long, SummaryGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public SummaryGraph load(Long runId) throws Exception {
	    	//Load stats from database
	    	log.debug("Loading SummaryGraph datas from database (not in cache)");
	    	Query q = em.createQuery("select to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS') from Sample s where run_fk='"+runId+"' group by to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS')");
	    	int nbSeconds=q.getResultList().size();
	    	log.debug("Check for scaling [seconds:"+nbSeconds+" - Fisrt scaling after: "+limitOfFisrtScaling+" s - second scaling after: "+limitOfSecondScaling+" s");
	    	if(nbSeconds<limitOfFisrtScaling){q = em.createQuery("select to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'), round(avg(elapsed),0), count(*) from Sample s where run_fk='"+runId+"' group by to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS') order by to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS')");}
	    	else if(nbSeconds < limitOfSecondScaling) {q = em.createQuery("select concat(substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19),'0') , round(avg(elapsed),0), count(*), substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19) from Sample s where run_fk='"+runId+"'group by substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19) order by substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19)");}
	    	else {q = em.createQuery("select to_char(timestamp, 'DD-MM-YYYY HH24:MI:00'), round(avg(elapsed),0), count(*) from Sample s where run_fk='"+runId+"' group by to_char(timestamp, 'DD-MM-YYYY HH24:MI:00') order by to_char(timestamp, 'DD-MM-YYYY HH24:MI:00')");}
	    	
	    	Run run = runRepository.findOne(runId);
	    	return new SummaryGraph(q.getResultList().iterator(), run.getStartDate());
	    }
	};
	private CacheLoader<Long, ResponseTimeGraph> responseTimeGraphLoader = new CacheLoader<Long, ResponseTimeGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public ResponseTimeGraph load(Long runId) throws Exception {
	    	//Load stats from database
	    	log.debug("Loading ResponseTimeGraph datas from database (not in cache)");
	    	Query q = em.createQuery("SELECT  sampleName, round(avg(elapsed),0), round(avg(latency),0) from Sample s where run_fk='"+runId+"'   GROUP BY sampleName order by sampleName");
	    	return new ResponseTimeGraph(q.getResultList().iterator());
	    }
	};
	private CacheLoader<Long, ResponseSizeGraph> responseSizeGraphLoader = new CacheLoader<Long, ResponseSizeGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public ResponseSizeGraph load(Long runId) throws Exception {
	    	//Load stats from database
	    	log.debug("Loading ResponseSizeGraph datas from database (not in cache)");
	    	Query q = em.createQuery("SELECT  sampleName, round(avg(sizeInOctet),0)  from Sample s where run_fk='"+runId+"'   GROUP BY sampleName order by sampleName");
	    	return new ResponseSizeGraph(q.getResultList().iterator());
	    }
	};
	private CacheLoader<Long, ErrorRateGraph> errorRateGraphLoader = new CacheLoader<Long, ErrorRateGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public ErrorRateGraph load(Long runId) throws Exception {<commit_msg>Fix a bug when reaching 10s based sampling. Have to divide by 10 to return the right value for average concurrent requests during 10s<commit_after>/*
 * Copyright (C) 2014  The Central Perf authors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.centralperf.service;

import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.centralperf.model.dao.Run;
import org.centralperf.model.graph.ErrorRateGraph;
import org.centralperf.model.graph.ResponseSizeGraph;
import org.centralperf.model.graph.ResponseTimeGraph;
import org.centralperf.model.graph.RunStats;
import org.centralperf.model.graph.SummaryGraph;
import org.centralperf.repository.RunRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * This class is singleton service to collect run data and graphs.<br>
 * A cache mechanism with time eviction is used to avoid database overload if too many refresh
 * are asked by users.
 * @since 1.0
 */

@Service
public class RunStatisticsService {

	@Resource
	private RunRepository runRepository;
	
	@PersistenceContext
	private EntityManager em;
	
	@Resource
	private ScriptLauncherService scriptLauncherService;
	
    @Value("#{appProperties['report.cache.delay.seconds']}")
    private Long cacheRefreshDelay;
    
    @Value("#{appProperties['report.scaling.level1.seconds']}")
    private Long limitOfFisrtScaling;
    
    @Value("#{appProperties['report.scaling.level2.seconds']}")
    private Long limitOfSecondScaling;
	
    //Cache can't be load on object construction as refresh delay is not yet set
    private LoadingCache<Long, SummaryGraph> summaryGraphCache;
    private LoadingCache<Long, ResponseTimeGraph> responseTimeGraphCache;
    private LoadingCache<Long, ResponseSizeGraph> responseSizeGraphCache;
    private LoadingCache<Long, ErrorRateGraph> errorRateGraphCache;
    private LoadingCache<Long, RunStats> runStatsCache;
    
	private static final Logger log = LoggerFactory.getLogger(RunStatisticsService.class);
	
	/*
	 * Use of cache is necessary to avoid database saturation if more than one user
	 * is looking UI run detail
	 */	
	private CacheLoader<Long, SummaryGraph> summaryGraphLoader = new CacheLoader<Long, SummaryGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public SummaryGraph load(Long runId) throws Exception {
	    	//Load stats from database
	    	log.debug("Loading SummaryGraph datas from database (not in cache)");
	    	Query q = em.createQuery("select to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS') from Sample s where run_fk='"+runId+"' group by to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS')");
	    	int nbSeconds=q.getResultList().size();
	    	log.debug("Check for scaling [seconds:"+nbSeconds+" - Fisrt scaling after: "+limitOfFisrtScaling+" s - second scaling after: "+limitOfSecondScaling+" s");
	    	// First limit : a line per second, or less
	    	if(nbSeconds<limitOfFisrtScaling){
	    		q = em.createQuery("select to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'), round(avg(elapsed),0), count(*) from Sample s "
	    				+ "where run_fk='"+runId+"' group by to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS') order by to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS')");
	    	}
	    	// Second limit : a line each 10 seconds
	    	else if(nbSeconds < limitOfSecondScaling) {
	    		q = em.createQuery("select concat(substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19),'0') , round(avg(elapsed),0), round(count(*)/10.0,2), substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19) from Sample s "
	    				+ "where run_fk='"+runId+"'group by substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19) order by substring(to_char(timestamp, 'DD-MM-YYYY HH24:MI:SS'),0,19)");
	    	}
	    	// greater than second limit : a line
	    	else {
	    		q = em.createQuery("select to_char(timestamp, 'DD-MM-YYYY HH24:MI:00'), round(avg(elapsed),0), count(*) from Sample s "
	    				+ "where run_fk='"+runId+"' group by to_char(timestamp, 'DD-MM-YYYY HH24:MI:00') order by to_char(timestamp, 'DD-MM-YYYY HH24:MI:00')");
	    	}
	    	
	    	Run run = runRepository.findOne(runId);
	    	return new SummaryGraph(q.getResultList().iterator(), run.getStartDate());
	    }
	};
	private CacheLoader<Long, ResponseTimeGraph> responseTimeGraphLoader = new CacheLoader<Long, ResponseTimeGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public ResponseTimeGraph load(Long runId) throws Exception {
	    	//Load stats from database
	    	log.debug("Loading ResponseTimeGraph datas from database (not in cache)");
	    	Query q = em.createQuery("SELECT  sampleName, round(avg(elapsed),0), round(avg(latency),0) from Sample s where run_fk='"+runId+"'   GROUP BY sampleName order by sampleName");
	    	return new ResponseTimeGraph(q.getResultList().iterator());
	    }
	};
	private CacheLoader<Long, ResponseSizeGraph> responseSizeGraphLoader = new CacheLoader<Long, ResponseSizeGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public ResponseSizeGraph load(Long runId) throws Exception {
	    	//Load stats from database
	    	log.debug("Loading ResponseSizeGraph datas from database (not in cache)");
	    	Query q = em.createQuery("SELECT  sampleName, round(avg(sizeInOctet),0)  from Sample s where run_fk='"+runId+"'   GROUP BY sampleName order by sampleName");
	    	return new ResponseSizeGraph(q.getResultList().iterator());
	    }
	};
	private CacheLoader<Long, ErrorRateGraph> errorRateGraphLoader = new CacheLoader<Long, ErrorRateGraph>() {
	    @SuppressWarnings("unchecked")
		@Override
	    public ErrorRateGraph load(Long runId) throws Exception {
{'fraction_non_alphanumeric': 0.10991977784634373, 'fraction_numerical': 0.010182042579450786, 'mean_word_length': 4.793118856121537, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6815993', 'n_tokens_mistral': 4001, 'n_tokens_neox': 3783, 'n_words': 1085}