/*******************************************************************************
* Copyright (c) 2012, 2014 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexandre Montplaisir - Initial API and implementation
******************************************************************************/
package org.eclipse.tracecompass.tmf.core.statistics;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfLostEvent;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
/**
* Implementation of ITmfStatistics which uses event requests to the trace to
* retrieve its information.
*
* There is almost no setup time, but queries themselves are longer than with a
* TmfStateStatistics. Queries are O(n * m), where n is the size of the trace,
* and m is the portion of the trace covered by the selected interval.
*
* @author Alexandre Montplaisir
*/
public class TmfEventsStatistics implements ITmfStatistics {
/* All timestamps should be stored in nanoseconds in the statistics backend */
private static final int SCALE = ITmfTimestamp.NANOSECOND_SCALE;
private final ITmfTrace trace;
/* Event request objects for the time-range request. */
private StatsTotalRequest totalRequest = null;
private StatsPerTypeRequest perTypeRequest = null;
/**
* Constructor
*
* @param trace
* The trace for which we are building the statistics
*/
public TmfEventsStatistics(ITmfTrace trace) {
this.trace = trace;
}
@Override
public void dispose() {
cancelOngoingRequests();
}
@Override
public List<Long> histogramQuery(long start, long end, int nb) {
final long[] borders = new long[nb];
final long increment = (end - start) / nb;
long curTime = start;
for (int i = 0; i < nb; i++) {
borders[i] = curTime;
curTime += increment;
}
HistogramQueryRequest req = new HistogramQueryRequest(borders, end);
sendAndWait(req);
List<Long> results = new LinkedList<>(req.getResults());
return results;
}
private synchronized void cancelOngoingRequests() {
if (totalRequest != null && totalRequest.isRunning()) {
totalRequest.cancel();
}
if (perTypeRequest != null && perTypeRequest.isRunning()) {
perTypeRequest.cancel();
}
}
@Override
public long getEventsTotal() {
StatsTotalRequest request = new StatsTotalRequest(trace, TmfTimeRange.ETERNITY);
sendAndWait(request);
long total = request.getResult();
return total;
}
@Override
public Map<@NonNull String, @NonNull Long> getEventTypesTotal() {
StatsPerTypeRequest request = new StatsPerTypeRequest(trace, TmfTimeRange.ETERNITY);
sendAndWait(request);
return request.getResults();
}
@Override
public long getEventsInRange(long start, long end) {
ITmfTimestamp startTS = TmfTimestamp.create(start, SCALE);
ITmfTimestamp endTS = TmfTimestamp.create(end, SCALE);
TmfTimeRange range = new TmfTimeRange(startTS, endTS);
StatsTotalRequest request = new StatsTotalRequest(trace, range);
sendAndWait(request);
long total = request.getResult();
return total;
}
@Override
public Map<String, Long> getEventTypesInRange(long start, long end) {
ITmfTimestamp startTS = TmfTimestamp.create(start, SCALE);
ITmfTimestamp endTS = TmfTimestamp.create(end, SCALE);
TmfTimeRange range = new TmfTimeRange(startTS, endTS);
StatsPerTypeRequest request = new StatsPerTypeRequest(trace, range);
sendAndWait(request);
Map<String, Long> stats = request.getResults();
return stats;
}
private void sendAndWait(TmfEventRequest request) {
trace.sendRequest(request);
try {
request.waitForCompletion();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Event request to get the total number of events
*/
private class StatsTotalRequest extends TmfEventRequest {
/* Total number of events the request has found */
private long total;
public StatsTotalRequest(ITmfTrace trace, TmfTimeRange range) {
super(trace.getEventType(), range, 0, ITmfEventRequest.ALL_DATA,
ITmfEventRequest.ExecutionType.BACKGROUND);
total = 0;
}
public long getResult() {
return total;
}
@Override
public void handleData(final ITmfEvent event) {
super.handleData(event);
if (!(event instanceof ITmfLostEvent) && event.getTrace() == trace) {
total += 1;
}
}
}
/**
* Event request to get the counts per event type
*/
private class StatsPerTypeRequest extends TmfEventRequest {
/* Map in which the results are saved */
private final Map<@NonNull String, @NonNull Long> stats;
public StatsPerTypeRequest(ITmfTrace trace, TmfTimeRange range) {
super(trace.getEventType(), range, 0, ITmfEventRequest.ALL_DATA,
ITmfEventRequest.ExecutionType.BACKGROUND);
this.stats = new HashMap<>();
}
public Map<@NonNull String, @NonNull Long> getResults() {
return stats;
}
@Override
public void handleData(final ITmfEvent event) {
super.handleData(event);
if (event.getTrace() == trace) {
String eventType = event.getName();
/*
* Special handling for lost events: instead of counting just
* one, we will count how many actual events it represents.
*/
if (event instanceof ITmfLostEvent) {
ITmfLostEvent le = (ITmfLostEvent) event;
incrementStats(eventType, le.getNbLostEvents());
return;
}
/* For standard event types, just increment by one */
incrementStats(eventType, 1L);
}
}
private void incrementStats(@NonNull String key, long count) {
if (stats.containsKey(key)) {
long curValue = checkNotNull(stats.get(key));
stats.put(key, curValue + count);
} else {
stats.put(key, count);
}
}
}
/**
* Event request for histogram queries. It is much faster to do one event
* request then set the results accordingly than doing thousands of them one
* by one.
*/
private class HistogramQueryRequest extends TmfEventRequest {
/** Map of <borders, number of events> */
private final TreeMap<Long, Long> results;
/**
* New histogram request
*
* @param borders
* The array of borders (not including the end time). The
* first element should be the start time of the queries.
* @param endTime
* The end time of the query. Not used in the results map,
* but we need to know when to stop the event request.
*/
public HistogramQueryRequest(long[] borders, long endTime) {
super(trace.getEventType(),
new TmfTimeRange(
TmfTimestamp.create(borders[0], SCALE),
TmfTimestamp.create(endTime, SCALE)),
0,
ITmfEventRequest.ALL_DATA,
ITmfEventRequest.ExecutionType.BACKGROUND);
/* Prepare the results map, with all counts at 0 */
results = new TreeMap<>();
for (long border : borders) {
results.put(border, 0L);
}
}
public Collection<Long> getResults() {
return results.values();
}
@Override
public void handleData(ITmfEvent event) {
super.handleData(event);
if (event.getTrace() == trace) {
long ts = event.getTimestamp().toNanos();
Long key = results.floorKey(ts);
if (key != null) {
incrementValue(key);
}
}
}
private void incrementValue(Long key) {
long value = checkNotNull(results.get(key));
value++;
results.put(key, value);
}
}
}