| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2014 Ericsson |
| 3 | * |
| 4 | * All rights reserved. This program and the accompanying materials are |
| 5 | * made available under the terms of the Eclipse Public License v1.0 which |
| 6 | * accompanies this distribution, and is available at |
| 7 | * http://www.eclipse.org/legal/epl-v10.html |
| 8 | * |
| 9 | * Contributors: |
| 10 | * Alexandre Montplaisir - Initial API and implementation |
| 11 | *******************************************************************************/ |
| 12 | |
| 13 | package org.eclipse.tracecompass.tmf.core.trace; |
| 14 | |
| 15 | import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; |
| 16 | |
| 17 | import java.io.BufferedInputStream; |
| 18 | import java.io.File; |
| 19 | import java.io.FileInputStream; |
| 20 | import java.io.IOException; |
| 21 | import java.util.ArrayList; |
| 22 | import java.util.List; |
| 23 | import java.util.function.Predicate; |
| 24 | |
| 25 | import org.eclipse.jdt.annotation.NonNull; |
| 26 | import org.eclipse.jdt.annotation.NonNullByDefault; |
| 27 | import org.eclipse.jdt.annotation.Nullable; |
| 28 | import org.eclipse.tracecompass.common.core.StreamUtils; |
| 29 | import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; |
| 30 | import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider; |
| 31 | import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; |
| 32 | import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect; |
| 33 | import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest; |
| 34 | |
| 35 | import com.google.common.collect.Iterables; |
| 36 | |
| 37 | /** |
| 38 | * Utility methods for ITmfTrace's. |
| 39 | * |
| 40 | * @author Alexandre Montplaisir |
| 41 | */ |
| 42 | @NonNullByDefault |
| 43 | public final class TmfTraceUtils { |
| 44 | |
| 45 | private static final int MAX_NB_BINARY_BYTES = 2048; |
| 46 | |
| 47 | private TmfTraceUtils() { |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * Return the first result of the first analysis module belonging to this trace or its children, |
| 52 | * with the specified ID and class. |
| 53 | * |
| 54 | * @param trace |
| 55 | * The trace for which you want the modules |
| 56 | * @param moduleClass |
| 57 | * Returned modules must extend this class |
| 58 | * @param id |
| 59 | * The ID of the analysis module |
| 60 | * @return The analysis module with specified class and ID, or null if no |
| 61 | * such module exists. |
| 62 | */ |
| 63 | public static @Nullable <T extends IAnalysisModule> T getAnalysisModuleOfClass(ITmfTrace trace, |
| 64 | Class<T> moduleClass, String id) { |
| 65 | Iterable<T> modules = getAnalysisModulesOfClass(trace, moduleClass); |
| 66 | for (T module : modules) { |
| 67 | if (id.equals(module.getId())) { |
| 68 | return module; |
| 69 | } |
| 70 | } |
| 71 | return null; |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Return the analysis modules that are of a given class. The modules will be |
| 76 | * cast to the requested class. If the trace has children, the childrens modules |
| 77 | * are also returned. |
| 78 | * |
| 79 | * @param trace |
| 80 | * The trace for which you want the modules, the children trace modules |
| 81 | * are added as well. |
| 82 | * @param moduleClass |
| 83 | * Returned modules must extend this class |
| 84 | * @return List of modules of class moduleClass |
| 85 | */ |
| 86 | public static <T> Iterable<@NonNull T> getAnalysisModulesOfClass(ITmfTrace trace, Class<T> moduleClass) { |
| 87 | Iterable<IAnalysisModule> analysisModules = trace.getAnalysisModules(); |
| 88 | List<@NonNull T> modules = new ArrayList<>(); |
| 89 | for (IAnalysisModule module : analysisModules) { |
| 90 | if (moduleClass.isAssignableFrom(module.getClass())) { |
| 91 | modules.add(checkNotNull(moduleClass.cast(module))); |
| 92 | } |
| 93 | } |
| 94 | for (ITmfEventProvider child : trace.getChildren()) { |
| 95 | if (child instanceof ITmfTrace) { |
| 96 | ITmfTrace childTrace = (ITmfTrace) child; |
| 97 | Iterables.addAll(modules, getAnalysisModulesOfClass(childTrace, moduleClass)); |
| 98 | } |
| 99 | } |
| 100 | return modules; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Return the first result of the first aspect that resolves as non null for |
| 105 | * the event received in parameter. If the returned value is not null, it |
| 106 | * can be safely cast to the aspect's class proper return type. |
| 107 | * |
| 108 | * @param trace |
| 109 | * The trace for which you want the event aspects |
| 110 | * @param aspectClass |
| 111 | * The class of the aspect(s) to resolve |
| 112 | * @param event |
| 113 | * The event for which to get the aspect |
| 114 | * @return The first result of the |
| 115 | * {@link ITmfEventAspect#resolve(ITmfEvent)} that returns non null |
| 116 | * for the event or {@code null} otherwise |
| 117 | */ |
| 118 | public static <T extends ITmfEventAspect<?>> @Nullable Object resolveEventAspectOfClassForEvent( |
| 119 | ITmfTrace trace, Class<T> aspectClass, ITmfEvent event) { |
| 120 | return StreamUtils.getStream(trace.getEventAspects()) |
| 121 | .filter(aspect -> aspectClass.isAssignableFrom(aspect.getClass())) |
| 122 | .map(aspect -> aspect.resolve(event)) |
| 123 | .filter(obj -> obj != null) |
| 124 | .findFirst().orElse(null); |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Return the first result of the first aspect that resolves as a non-null |
| 129 | * Integer for the event received in parameter. If no matching aspects are |
| 130 | * found then null is returned. |
| 131 | * |
| 132 | * @param trace |
| 133 | * The trace for which you want the event aspects |
| 134 | * @param aspectClass |
| 135 | * The class of the aspect(s) to resolve |
| 136 | * @param event |
| 137 | * The event for which to get the aspect |
| 138 | * @return Integer of the first result of the |
| 139 | * {@link ITmfEventAspect#resolve(ITmfEvent)} that returns non null |
| 140 | * for the event or {@code null} otherwise |
| 141 | * @since 2.0 |
| 142 | */ |
| 143 | public static <T extends ITmfEventAspect<Integer>> @Nullable Integer resolveIntEventAspectOfClassForEvent( |
| 144 | ITmfTrace trace, Class<T> aspectClass, ITmfEvent event) { |
| 145 | return StreamUtils.getStream(trace.getEventAspects()) |
| 146 | .filter(aspect -> aspectClass.isAssignableFrom(aspect.getClass())) |
| 147 | /* Enforced by the T parameter bounding */ |
| 148 | .map(aspect -> (Integer) aspect.resolve(event)) |
| 149 | .filter(obj -> obj != null) |
| 150 | .findFirst().orElse(null); |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Checks for text file. |
| 155 | * |
| 156 | * Note that it checks for binary value 0 in the first MAX_NB_BINARY_BYTES |
| 157 | * bytes to determine if the file is text. |
| 158 | * |
| 159 | * @param file |
| 160 | * the file to check. Caller has to make sure that file exists. |
| 161 | * @return true if it is binary else false |
| 162 | * @throws IOException |
| 163 | * if IOException occurs |
| 164 | * @since 1.2 |
| 165 | */ |
| 166 | public static boolean isText(File file) throws IOException { |
| 167 | try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))) { |
| 168 | int count = 0; |
| 169 | int val = bufferedInputStream.read(); |
| 170 | while ((count < MAX_NB_BINARY_BYTES) && (val >= 0)) { |
| 171 | if (val == 0) { |
| 172 | return false; |
| 173 | } |
| 174 | count++; |
| 175 | val = bufferedInputStream.read(); |
| 176 | } |
| 177 | } |
| 178 | return true; |
| 179 | } |
| 180 | |
| 181 | // ------------------------------------------------------------------------ |
| 182 | // Event matching methods |
| 183 | // ------------------------------------------------------------------------ |
| 184 | |
| 185 | /** |
| 186 | * Retrieve from a trace the next event, from a starting rank, matching the |
| 187 | * given predicate. |
| 188 | * |
| 189 | * @param trace |
| 190 | * The trace |
| 191 | * @param startRank |
| 192 | * The rank of the event at which to start searching. Use |
| 193 | * <code>0</code> to search from the start of the trace. |
| 194 | * @param predicate |
| 195 | * The predicate to test events against |
| 196 | * @return The first event matching the predicate, or null if the end of the |
| 197 | * trace was reached and no event was found |
| 198 | * @since 2.1 |
| 199 | */ |
| 200 | public static @Nullable ITmfEvent getNextEventMatching(ITmfTrace trace, long startRank, Predicate<ITmfEvent> predicate) { |
| 201 | /* rank + 1 because we do not want to include the start event itself in the search */ |
| 202 | EventMatchingRequest req = new EventMatchingRequest(startRank + 1, predicate, false); |
| 203 | trace.sendRequest(req); |
| 204 | try { |
| 205 | req.waitForCompletion(); |
| 206 | } catch (InterruptedException e) { |
| 207 | return null; |
| 208 | } |
| 209 | |
| 210 | return req.getFoundEvent(); |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Retrieve from a trace the previous event, from a given rank, matching the |
| 215 | * given predicate. |
| 216 | * |
| 217 | * @param trace |
| 218 | * The trace |
| 219 | * @param startRank |
| 220 | * The rank of the event at which to start searching backwards. |
| 221 | * @param predicate |
| 222 | * The predicate to test events against |
| 223 | * @return The first event found matching the predicate, or null if the |
| 224 | * beginning of the trace was reached and no event was found |
| 225 | * @since 2.1 |
| 226 | */ |
| 227 | public static @Nullable ITmfEvent getPreviousEventMatching(ITmfTrace trace, long startRank, Predicate<ITmfEvent> predicate) { |
| 228 | /* |
| 229 | * Slightly less straightforward since we unfortunately cannot iterate |
| 230 | * backwards on events. Do a series of forward-queries. |
| 231 | */ |
| 232 | final int targetStep = 100; |
| 233 | |
| 234 | /* |
| 235 | * If we are close to the beginning of the trace, make sure we only look |
| 236 | * for the events before the startRank. |
| 237 | */ |
| 238 | int step = (startRank < targetStep ? (int) startRank : targetStep); |
| 239 | |
| 240 | long currentRank = startRank; |
| 241 | try { |
| 242 | while (currentRank > 0) { |
| 243 | currentRank = Math.max(currentRank - step, 0); |
| 244 | |
| 245 | EventMatchingRequest req = new EventMatchingRequest(currentRank, step, predicate, true); |
| 246 | trace.sendRequest(req); |
| 247 | req.waitForCompletion(); |
| 248 | |
| 249 | ITmfEvent event = req.getFoundEvent(); |
| 250 | if (event != null) { |
| 251 | /* We found an actual event, return it! */ |
| 252 | return event; |
| 253 | } |
| 254 | /* Keep searching, next loop */ |
| 255 | |
| 256 | } |
| 257 | } catch (InterruptedException e) { |
| 258 | return null; |
| 259 | } |
| 260 | |
| 261 | /* |
| 262 | * We searched up to the beginning of the trace and didn't find |
| 263 | * anything. |
| 264 | */ |
| 265 | return null; |
| 266 | |
| 267 | } |
| 268 | |
| 269 | /** |
| 270 | * Event request looking for an event matching a Predicate. |
| 271 | */ |
| 272 | private static class EventMatchingRequest extends TmfEventRequest { |
| 273 | |
| 274 | private final Predicate<ITmfEvent> fPredicate; |
| 275 | private final boolean fReturnLast; |
| 276 | |
| 277 | private @Nullable ITmfEvent fFoundEvent = null; |
| 278 | |
| 279 | /** |
| 280 | * Basic constructor, will query the trace until the end. |
| 281 | * |
| 282 | * @param startRank |
| 283 | * The rank at which to start, use 0 for the beginning |
| 284 | * @param predicate |
| 285 | * The predicate to test against each event |
| 286 | * @param returnLast |
| 287 | * Should we return the last or first event found. If false, |
| 288 | * the request ends as soon as a matching event is found. If |
| 289 | * false, we will go through all events to find a possible |
| 290 | * last-match. |
| 291 | */ |
| 292 | public EventMatchingRequest(long startRank, Predicate<ITmfEvent> predicate, boolean returnLast) { |
| 293 | super(ITmfEvent.class, startRank, ALL_DATA, ExecutionType.FOREGROUND); |
| 294 | fPredicate = predicate; |
| 295 | fReturnLast = returnLast; |
| 296 | } |
| 297 | |
| 298 | /** |
| 299 | * Basic constructor, will query the trace the limit is reached. |
| 300 | * |
| 301 | * @param startRank |
| 302 | * The rank at which to start, use 0 for the beginning |
| 303 | * @param limit |
| 304 | * The limit on the number of events |
| 305 | * @param predicate |
| 306 | * The predicate to test against each event |
| 307 | * @param returnLast |
| 308 | * Should we return the last or first event found. If false, |
| 309 | * the request ends as soon as a matching event is found. If |
| 310 | * false, we will go through all events to find a possible |
| 311 | * last-match. |
| 312 | */ |
| 313 | public EventMatchingRequest(long startRank, int limit, Predicate<ITmfEvent> predicate, boolean returnLast) { |
| 314 | super(ITmfEvent.class, startRank, limit, ExecutionType.FOREGROUND); |
| 315 | fPredicate = predicate; |
| 316 | fReturnLast = returnLast; |
| 317 | } |
| 318 | |
| 319 | public @Nullable ITmfEvent getFoundEvent() { |
| 320 | return fFoundEvent; |
| 321 | } |
| 322 | |
| 323 | @Override |
| 324 | public void handleData(ITmfEvent event) { |
| 325 | super.handleData(event); |
| 326 | |
| 327 | if (fPredicate.test(event)) { |
| 328 | fFoundEvent = event; |
| 329 | if (!fReturnLast) { |
| 330 | this.done(); |
| 331 | } |
| 332 | } |
| 333 | } |
| 334 | } |
| 335 | } |