/* * Copyright 2012 * Ubiquitous Knowledge Processing (UKP) Lab * Technische Universität Darmstadt * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.tudarmstadt.ukp.dkpro.core.performance; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Formatter; import java.util.List; import java.util.Locale; import java.util.Properties; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.apache.uima.UimaContext; import org.apache.uima.analysis_engine.AnalysisEngineProcessException; import org.apache.uima.fit.component.JCasAnnotator_ImplBase; import org.apache.uima.fit.descriptor.ConfigurationParameter; import org.apache.uima.fit.descriptor.TypeCapability; import org.apache.uima.fit.util.JCasUtil; import org.apache.uima.jcas.JCas; import org.apache.uima.resource.ResourceInitializationException; import de.tudarmstadt.ukp.dkpro.core.performance.type.TimerAnnotation; /** * Can be used to measure how long the processing between two points in a pipeline takes. * For that purpose, the AE needs to be added two times, before and after the part of the pipeline that should be measured. */ @TypeCapability( inputs={ "de.tudarmstadt.ukp.dkpro.core.type.TimerAnnotation"}, outputs={ "de.tudarmstadt.ukp.dkpro.core.type.TimerAnnotation"}) public class Stopwatch extends JCasAnnotator_ImplBase { private Boolean isDownstreamTimer; private JCas jcas;; public static final String KEY_MEAN = "mean"; public static final String KEY_SUM = "sum"; public static final String KEY_STDDEV = "stddev"; public static final String PARAM_TIMER_NAME = "timerName"; /** * Name of the timer pair. * Upstream and downstream timer need to use the same name. */ @ConfigurationParameter(name = PARAM_TIMER_NAME, mandatory = true) private String timerName; public static final String PARAM_OUTPUT_FILE = "timerOutputFile"; /** * Name of the timer pair. * Upstream and downstream timer need to use the same name. */ @ConfigurationParameter(name = PARAM_OUTPUT_FILE, mandatory = false) private File outputFile; private List<Long> times; @Override public void initialize(UimaContext context) throws ResourceInitializationException { super.initialize(context); times = new ArrayList<Long>(); isDownstreamTimer = null; } @Override public void process(JCas jcas) throws AnalysisEngineProcessException { this.jcas = jcas; long currentTime = System.currentTimeMillis(); if (isDownstreamTimer()) { TimerAnnotation timerAnno = JCasUtil.selectSingle(jcas, TimerAnnotation.class); timerAnno.setEndTime(currentTime); long startTime = timerAnno.getStartTime(); times.add(currentTime - startTime); } else { TimerAnnotation timerAnno = new TimerAnnotation(jcas); timerAnno.setName(timerName); timerAnno.setStartTime(currentTime); timerAnno.addToIndexes(); } } @Override public void collectionProcessComplete() throws AnalysisEngineProcessException { super.collectionProcessComplete(); if (isDownstreamTimer()) { getLogger().info("Results from Timer '" + timerName + "' after processing all documents."); DescriptiveStatistics statTimes = new DescriptiveStatistics(); for (Long timeValue : times) { statTimes.addValue((double) timeValue / 1000); } double sum = statTimes.getSum(); double mean = statTimes.getMean(); double stddev = statTimes.getStandardDeviation(); StringBuilder sb = new StringBuilder(); sb.append("Estimate after processing " + times.size() + " documents."); sb.append("\n"); Formatter formatter = new Formatter(sb, Locale.US); formatter.format("Aggregated time: %,.1fs\n", sum); formatter.format("Time / Document: %,.3fs (%,.3fs)\n", mean, stddev); formatter.close(); getLogger().info(sb.toString()); if (outputFile != null) { try { Properties props = new Properties(); props.setProperty(KEY_SUM, ""+sum); props.setProperty(KEY_MEAN, ""+mean); props.setProperty(KEY_STDDEV, ""+stddev); OutputStream out = new FileOutputStream(outputFile); props.store(out, "timer " + timerName + " result file"); } catch (FileNotFoundException e) { throw new AnalysisEngineProcessException(e); } catch (IOException e) { throw new AnalysisEngineProcessException(e); } } } } private boolean isDownstreamTimer() { if (isDownstreamTimer == null) { // this is only a downstream timer if there already is a timer annotation with the same name for (TimerAnnotation timer : JCasUtil.select(jcas, TimerAnnotation.class)) { if (timer.getName().equals(timerName)) { isDownstreamTimer = true; } } } if (isDownstreamTimer == null) { isDownstreamTimer = false; } return isDownstreamTimer; } }