| /******************************************************************************* |
| * Copyright (c) 2009, 2015 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Francois Chouinard - Initial API and implementation |
| * Alexandre Montplaisir - Merge with TmfCoalescedDataRequest |
| * Bernd Hufmann - Updated dispatching of events and added requests cache |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.internal.tmf.core.request; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.tracecompass.internal.tmf.core.Activator; |
| import org.eclipse.tracecompass.internal.tmf.core.TmfCoreTracer; |
| import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; |
| 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; |
| |
| /** |
| * The TMF coalesced event request |
| * |
| * @author Francois Chouinard |
| */ |
| public class TmfCoalescedEventRequest extends TmfEventRequest { |
| |
| // ------------------------------------------------------------------------ |
| // Attributes |
| // ------------------------------------------------------------------------ |
| |
| /** The list of coalesced requests */ |
| private final List<ITmfEventRequest> fRequests = new ArrayList<>(); |
| |
| /** |
| * We do not use super.fRange, because in the case of coalesced requests, |
| * the global range can be modified as sub-request are added. |
| */ |
| private TmfTimeRange fRange; |
| |
| /** |
| * The requests cache to avoid iterating over all requests for each event. |
| */ |
| private Map<String, Set<ITmfEventRequest>> fRequestsCache = new HashMap<>(); |
| |
| // ------------------------------------------------------------------------ |
| // Constructor |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Request 'n' events of a given type for the given time range (given |
| * priority). Events are returned in blocks of the given size. |
| * |
| * @param dataType |
| * The requested data type |
| * @param range |
| * The range of the request. You can use |
| * {@link TmfTimeRange#ETERNITY} to request all events. |
| * @param index |
| * The index of the first event to retrieve. Use '0' to start at |
| * the beginning. |
| * @param nbRequested |
| * The number of events requested. You can use |
| * {@link TmfEventRequest#ALL_DATA} to request all events. |
| * @param priority |
| * The requested execution priority |
| * @param dependencyLevel |
| * The dependency level. Use 0 if no dependency with other |
| * requests. |
| */ |
| public TmfCoalescedEventRequest(Class<? extends ITmfEvent> dataType, |
| TmfTimeRange range, |
| long index, |
| int nbRequested, |
| ExecutionType priority, |
| int dependencyLevel) { |
| super(ITmfEvent.class, null, index, nbRequested, priority, dependencyLevel); |
| fRange = range; |
| |
| if (TmfCoreTracer.isRequestTraced()) { |
| String type = getClass().getName(); |
| type = type.substring(type.lastIndexOf('.') + 1); |
| @SuppressWarnings("nls") |
| String message = "CREATED " |
| + (getExecType() == ITmfEventRequest.ExecutionType.BACKGROUND ? "(BG)" : "(FG)") |
| + " Type=" + type + " Index=" + getIndex() + " NbReq=" + getNbRequested() |
| + " Range=" + getRange() |
| + " DataType=" + getDataType().getSimpleName(); |
| TmfCoreTracer.traceRequest(getRequestId(), message); |
| } |
| } |
| |
| @Override |
| public TmfTimeRange getRange() { |
| return fRange; |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Management |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Add a request to this one. |
| * |
| * @param request |
| * The request to add |
| */ |
| public void addRequest(ITmfEventRequest request) { |
| // If it is a coalesced request only add the sub-requests |
| if (request instanceof TmfCoalescedEventRequest) { |
| TmfCoalescedEventRequest otherRequest = (TmfCoalescedEventRequest)request; |
| for (ITmfEventRequest subRequest : otherRequest.fRequests) { |
| fRequests.add(subRequest); |
| merge(subRequest); |
| } |
| } else { |
| fRequests.add(request); |
| merge(request); |
| } |
| } |
| |
| /** |
| * Check if a request is compatible with the current coalesced one |
| * |
| * @param request |
| * The request to verify |
| * @return If the request is compatible, true or false |
| */ |
| public boolean isCompatible(ITmfEventRequest request) { |
| return (request.getExecType() == getExecType() && |
| request.getDependencyLevel() == getDependencyLevel() && |
| ranksOverlap(request) && timeRangesOverlap(request)); |
| |
| } |
| |
| private boolean ranksOverlap(ITmfEventRequest request) { |
| long start = request.getIndex(); |
| long end = start + request.getNbRequested(); |
| |
| // Return true if either the start or end index falls within |
| // the coalesced request boundaries |
| return (start <= (fIndex + fNbRequested + 1) && (end >= fIndex - 1)); |
| } |
| |
| private boolean timeRangesOverlap(ITmfEventRequest request) { |
| ITmfTimestamp startTime = request.getRange().getStartTime(); |
| ITmfTimestamp endTime = request.getRange().getEndTime(); |
| return (startTime.compareTo(endTime) <= 0) && |
| (fRange.getStartTime().compareTo(fRange.getEndTime()) <= 0); |
| } |
| |
| private void merge(ITmfEventRequest request) { |
| long start = request.getIndex(); |
| long end = Math.min(start + request.getNbRequested(), ITmfEventRequest.ALL_DATA); |
| |
| if (start < fIndex) { |
| if (fNbRequested != ITmfEventRequest.ALL_DATA) { |
| fNbRequested += (fIndex - start); |
| } |
| fIndex = start; |
| } |
| if ((request.getNbRequested() == ITmfEventRequest.ALL_DATA) || |
| (fNbRequested == ITmfEventRequest.ALL_DATA)) { |
| fNbRequested = ITmfEventRequest.ALL_DATA; |
| } else { |
| fNbRequested = (int) Math.max(end - fIndex, fNbRequested); |
| } |
| |
| ITmfTimestamp startTime = request.getRange().getStartTime(); |
| ITmfTimestamp endTime = request.getRange().getEndTime(); |
| if (!fRange.contains(startTime) && fRange.getStartTime().compareTo(startTime) > 0) { |
| fRange = new TmfTimeRange(startTime, fRange.getEndTime()); |
| } |
| if (!fRange.contains(endTime) && fRange.getEndTime().compareTo(endTime) < 0) { |
| fRange = new TmfTimeRange(fRange.getStartTime(), endTime); |
| } |
| } |
| |
| /** |
| * @return The list of IDs of the sub-requests |
| */ |
| @SuppressWarnings("nls") |
| public String getSubRequestIds() { |
| StringBuffer result = new StringBuffer("["); |
| for (int i = 0; i < fRequests.size(); i++) { |
| if (i != 0) { |
| result.append(", "); |
| } |
| result.append(fRequests.get(i).getRequestId()); |
| } |
| result.append("]"); |
| return result.toString(); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // ITmfEventRequest |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public void handleData(ITmfEvent data) { |
| super.handleData(data); |
| |
| long index = getIndex() + getNbRead() - 1; |
| |
| String traceName = data.getTrace().getName(); |
| Set<ITmfEventRequest> requests = fRequestsCache.get(traceName); |
| |
| if (requests == null) { |
| // Populate requests cache |
| requests = new HashSet<>(); |
| for (ITmfEventRequest myRequest : fRequests) { |
| if (myRequest.getProviderFilter().matches(data)) { |
| requests.add(myRequest); |
| } |
| } |
| fRequestsCache.put(traceName, requests); |
| } |
| |
| // dispatch event to relevant requests |
| for (ITmfEventRequest request : requests) { |
| long start = request.getIndex(); |
| if (!request.isCompleted() && index >= start && request.getNbRead() < request.getNbRequested()) { |
| ITmfTimestamp ts = data.getTimestamp(); |
| if (request.getRange().contains(ts)) { |
| if (request.getDataType().isInstance(data)) { |
| try { |
| request.handleData(data); |
| } catch (Exception e) { |
| /* |
| * We don't usually catch all exception, but here it |
| * is important because this will cause the request |
| * thread to hang forever and the other requests to |
| * be stopped. This should properly cancel the |
| * request with the exception and let the rest |
| * continue. |
| */ |
| Activator.logError("An uncaught exception happened on request " + request + ": " + e.getMessage()); //$NON-NLS-1$//$NON-NLS-2$ |
| request.fail(e); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void start() { |
| for (ITmfEventRequest request : fRequests) { |
| if (!request.isCompleted()) { |
| request.start(); |
| } |
| } |
| super.start(); |
| } |
| |
| @Override |
| public synchronized void done() { |
| for (ITmfEventRequest request : fRequests) { |
| if (!request.isCompleted()) { |
| request.done(); |
| } |
| } |
| super.done(); |
| } |
| |
| @Override |
| public void fail(Exception e) { |
| for (ITmfEventRequest request : fRequests) { |
| request.fail(e); |
| } |
| super.fail(e); |
| } |
| |
| @Override |
| public void cancel() { |
| for (ITmfEventRequest request : fRequests) { |
| if (!request.isCompleted()) { |
| request.cancel(); |
| } |
| } |
| super.cancel(); |
| } |
| |
| @Override |
| public synchronized boolean isCompleted() { |
| // Firstly, check if coalescing request is completed |
| if (super.isCompleted()) { |
| return true; |
| } |
| |
| // Secondly, check if all sub-requests are finished |
| if (!fRequests.isEmpty()) { |
| // If all sub requests are completed the coalesced request is |
| // treated as completed, too. |
| for (ITmfEventRequest request : fRequests) { |
| if (!request.isCompleted()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Coalescing request is not finished if there are no sub-requests |
| return false; |
| } |
| |
| @Override |
| public synchronized boolean isCancelled() { |
| // Firstly, check if coalescing request is canceled |
| if (super.isCancelled()) { |
| return true; |
| } |
| |
| // Secondly, check if all sub-requests are canceled |
| if (!fRequests.isEmpty()) { |
| // If all sub requests are canceled the coalesced request is |
| // treated as completed, too. |
| for (ITmfEventRequest request : fRequests) { |
| if (!request.isCancelled()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Coalescing request is not canceled if there are no sub-requests |
| return false; |
| |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Object |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| @SuppressWarnings("nls") |
| public String toString() { |
| return "[TmfCoalescedEventRequest(" + getRequestId() + "," + getDataType().getSimpleName() |
| + "," + getExecType() + "," + getRange() + "," + getIndex() + "," + getNbRequested() |
| + ", " + fRequests.toString() + ")]"; |
| } |
| |
| } |