package org.juxtasoftware.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.juxtasoftware.Constants;
import org.juxtasoftware.dao.ComparisonSetDao;
import org.juxtasoftware.dao.MetricsDao;
import org.juxtasoftware.dao.SourceDao;
import org.juxtasoftware.dao.WorkspaceDao;
import org.juxtasoftware.model.ComparisonSet;
import org.juxtasoftware.model.Metrics;
import org.juxtasoftware.model.Source;
import org.juxtasoftware.model.Workspace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* Helper component used to initialze the metrics for any new users since the
* last time the service was started
*
* @author lfoster
*
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class MetricsHelper {
@Autowired private WorkspaceDao workspaceDao;
@Autowired private MetricsDao metricsDao;
@Autowired private SourceDao srcDao;
@Autowired private ComparisonSetDao setDao;
@Autowired private Boolean captureMetrics;
private ConcurrentHashMap<Long, Long> collationStartTimes = new ConcurrentHashMap<Long, Long>();
private static final Logger LOG = LoggerFactory.getLogger( Constants.METRICS_LOGGER_NAME );
private static final Logger DEBUG_LOG = LoggerFactory.getLogger( Constants.WS_LOGGER_NAME );
@Scheduled(cron="0 0 12 * * *")
public void logMetrics() {
if ( this.captureMetrics ) {
for ( Metrics m : this.metricsDao.list() ) {
LOG.info( this.toCsv(m, true) );
}
}
}
public String toCsv( Metrics m ) {
return this.toCsv(m,false);
}
public String toCsv( Metrics m, boolean dateStamp ) {
StringBuilder sb = new StringBuilder();
sb.append(m.getWorkspace()).append(",");
sb.append(m.getNumSources()).append(",");
sb.append(m.getMinSourceSize()).append(",");
sb.append(m.getMaxSourceSize()).append(",");
sb.append(m.getMeanSourceSize()).append(",");
sb.append(m.getTotalSourcesSize()).append(",");
sb.append(m.getTotalTimeCollating()).append(",");
sb.append(m.getNumCollationsStarted()).append(",");
sb.append(m.getNumCollationsFinished());
if ( dateStamp ) {
sb.append(",");
sb.append( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format( new Date()));
}
return sb.toString();
}
public void init() {
if ( this.captureMetrics ) {
for (Workspace ws : this.workspaceDao.list()) {
Metrics m = this.metricsDao.get(ws);
if (m == null) {
// New! Create a metrics entry for it
m = new Metrics();
m.setWorkspace(ws.getName());
updateSourceMetrics(ws, m);
updateSetMetrics(ws, m);
this.metricsDao.create( m );
} else {
updateSourceMetrics(ws, m);
updateSetMetrics(ws, m);
this.metricsDao.update(m);
}
}
}
}
public void workspaceAdded( final Workspace ws ) {
if ( this.captureMetrics ) {
try {
DEBUG_LOG.info("Created new user workspace "+ws.getName());
Metrics m = new Metrics();
m.setWorkspace(ws.getName());
this.metricsDao.create(m);
} catch (Exception e) {
DEBUG_LOG.error("Unable to create metrics entry for " + ws, e);
}
}
}
public void workspaceRemoved(final Workspace ws) {
if ( this.captureMetrics ) {
try {
DEBUG_LOG.info("Deleted user workspace "+ws.getName());
Metrics m = new Metrics();
m.setWorkspace(ws.getName());
this.metricsDao.delete(m);
} catch (Exception e) {
DEBUG_LOG.error("Unable to delete metrics entry for " + ws, e);
}
}
}
public void sourceAdded(final Workspace ws, final Source src) {
if ( this.captureMetrics ) {
try {
Metrics m = this.metricsDao.get(ws);
m.setNumSources( m.getNumSources()+1 );
int size = (int)src.getText().getLength();
m.setTotalSourcesSize( m.getTotalSourcesSize()+size);
if ( size < m.getMinSourceSize() || m.getMinSourceSize() == 0) {
m.setMinSourceSize(size);
}
if ( size > m.getMaxSourceSize() ) {
m.setMaxSourceSize(size);
}
int oldMean = m.getMeanSourceSize();
m.setMeanSourceSize( (oldMean+size)/2 );
this.metricsDao.update( m );
} catch (Exception e) {
DEBUG_LOG.error("Metrics error tracking add "+src, e);
}
}
}
private void updateSetMetrics( final Workspace ws, Metrics m ) {
if ( this.captureMetrics ) {
try {
int max = -1;
int min = Integer.MAX_VALUE;
int total = 0;
List<ComparisonSet> sets = this.setDao.list(ws);
if ( sets.size() > 0 ) {
for (ComparisonSet set : sets) {
int witCnt = this.setDao.getWitnesses(set).size();
if ( witCnt > max ) {
max = witCnt;
}
if ( witCnt < min ) {
min = witCnt;
}
total += witCnt;
}
float mean = (float)total / (float)sets.size();
m.setMinSetWitnesses(min);
m.setMaxSetWitnesses(max);
m.setMeanSetWitnesses(Math.round(mean));
}
} catch (Exception e ) {
DEBUG_LOG.error("Metrics error tracking set witness counts ", e);
}
}
}
private void updateSourceMetrics( final Workspace ws, Metrics m) {
if ( this.captureMetrics ) {
try {
List<Source> srcs = this.srcDao.list(ws);
m.setNumSources(srcs.size());
if ( srcs.size() > 0 ) {
int minSize = Integer.MAX_VALUE;
int maxSize = -1;
int total = 0;
for (Source s : srcs) {
int size = (int)s.getText().getLength();
total += size;
if (size > maxSize) {
maxSize = size;
}
if (size < minSize) {
minSize = size;
}
}
m.setMaxSourceSize(maxSize);
m.setMinSourceSize(minSize);
m.setMeanSourceSize(total / srcs.size() );
m.setTotalSourcesSize(total);
} else {
m.setMaxSourceSize(0);
m.setMinSourceSize(0);
m.setMeanSourceSize(0);
m.setTotalSourcesSize(0);
}
} catch ( Exception e) {
DEBUG_LOG.error("Metrics error updating sources", e);
}
}
}
public void sourceRemoved(final Workspace ws, final Source src) {
if ( this.captureMetrics ) {
try {
Metrics m = this.metricsDao.get(ws);
updateSourceMetrics(ws, m);
this.metricsDao.update( m );
} catch ( Exception e ) {
DEBUG_LOG.error("Metrics error tracking "+src+" removal", e);
}
}
}
public void setWitnessCountChanged( final Workspace ws) {
if ( this.captureMetrics ) {
Metrics m = this.metricsDao.get(ws);
updateSetMetrics(ws, m);
this.metricsDao.update(m);
}
}
public void collationStarted( final Workspace ws, final ComparisonSet set ) {
if ( this.captureMetrics ) {
try {
Metrics m = this.metricsDao.get(ws);
m.setNumCollationsStarted( m.getNumCollationsStarted()+1 );
this.metricsDao.update(m);
this.collationStartTimes.put(set.getId(), System.currentTimeMillis());
DEBUG_LOG.info("Mark collation start of "+set);
DEBUG_LOG.info("Timestamp "+this.collationStartTimes.get(set.getId()));
} catch ( Exception e ) {
DEBUG_LOG.error("Metrics error tracking "+set+" collation start", e);
}
}
}
public void collationFinished( final Workspace ws, final ComparisonSet set ) {
if ( this.captureMetrics ) {
try {
Metrics m = this.metricsDao.get(ws);
m.setNumCollationsFinished( m.getNumCollationsFinished()+1 );
Long startTime = this.collationStartTimes.get(set.getId());
DEBUG_LOG.info("Mark collation END of "+set);
DEBUG_LOG.info("Timestamp "+this.collationStartTimes.get(set.getId()));
this.collationStartTimes.remove(set.getId());
if (startTime != null ) {
long deltaMs = System.currentTimeMillis() - startTime;
m.setTotalTimeCollating( m.getTotalTimeCollating()+deltaMs );
} else {
DEBUG_LOG.error("No start time for collating "+set+". Can't track timing metrics");
}
this.metricsDao.update(m);
} catch ( Exception e ) {
DEBUG_LOG.error("Metrics error tracking "+set+" collation end", e);
}
}
}
}