1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Ericsson
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
10 * Patrick Tasse - Initial API and implementation
11 * Marc-Andre Laperle - Add persistent index support
12 *******************************************************************************/
14 package org
.eclipse
.tracecompass
.tmf
.core
.trace
.text
;
17 import java
.io
.IOException
;
18 import java
.nio
.ByteBuffer
;
19 import java
.util
.Collections
;
20 import java
.util
.List
;
21 import java
.util
.regex
.Matcher
;
22 import java
.util
.regex
.Pattern
;
24 import org
.eclipse
.core
.resources
.IProject
;
25 import org
.eclipse
.core
.resources
.IResource
;
26 import org
.eclipse
.core
.runtime
.IStatus
;
27 import org
.eclipse
.core
.runtime
.Status
;
28 import org
.eclipse
.jdt
.annotation
.NonNull
;
29 import org
.eclipse
.jdt
.annotation
.Nullable
;
30 import org
.eclipse
.tracecompass
.internal
.tmf
.core
.Activator
;
31 import org
.eclipse
.tracecompass
.tmf
.core
.event
.ITmfEvent
;
32 import org
.eclipse
.tracecompass
.tmf
.core
.exceptions
.TmfTraceException
;
33 import org
.eclipse
.tracecompass
.tmf
.core
.io
.BufferedRandomAccessFile
;
34 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalHandler
;
35 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfTraceRangeUpdatedSignal
;
36 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.ITmfTimestamp
;
37 import org
.eclipse
.tracecompass
.tmf
.core
.timestamp
.TmfTimestamp
;
38 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.ITmfContext
;
39 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTrace
;
40 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TmfTraceUtils
;
41 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.TraceValidationStatus
;
42 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.indexer
.ITmfPersistentlyIndexable
;
43 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.indexer
.ITmfTraceIndexer
;
44 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.indexer
.TmfBTreeTraceIndexer
;
45 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.indexer
.checkpoint
.ITmfCheckpoint
;
46 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.indexer
.checkpoint
.TmfCheckpoint
;
47 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.location
.ITmfLocation
;
48 import org
.eclipse
.tracecompass
.tmf
.core
.trace
.location
.TmfLongLocation
;
51 * Extension of TmfTrace for handling of line-based text traces parsed using
52 * regular expressions. Each line that matches the first line pattern indicates
53 * the start of a new event. The subsequent lines can contain additional
54 * information that is added to the current event.
57 * TmfEvent class returned by this trace
59 public abstract class TextTrace
<T
extends TextTraceEvent
> extends TmfTrace
implements ITmfPersistentlyIndexable
{
61 private static final TmfLongLocation NULL_LOCATION
= new TmfLongLocation(-1L);
62 private static final int MAX_LINES
= 100;
63 private static final int MAX_CONFIDENCE
= 100;
65 /** The default separator used for multi-line fields */
66 protected static final String SEPARATOR
= " | "; //$NON-NLS-1$
69 protected BufferedRandomAccessFile fFile
;
80 * The default implementation computes the confidence as the sum of weighted
81 * values of the first 100 lines of the file which match any of the provided
82 * validation patterns. For each matching line a weighted value between 1.5
83 * and 2.0 is assigned based on the group count of the matching patterns.
84 * The higher the group count, the closer the weighted value will be to 2.0.
87 public IStatus
validate(IProject project
, String path
) {
88 File file
= new File(path
);
90 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, "File not found: " + path
); //$NON-NLS-1$
93 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, "Not a file. It's a directory: " + path
); //$NON-NLS-1$
96 if (!TmfTraceUtils
.isText(file
)) {
97 return new TraceValidationStatus(0, Activator
.PLUGIN_ID
);
99 } catch (IOException e
) {
100 Activator
.logError("Error validating file: " + path
, e
); //$NON-NLS-1$
101 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, "IOException validating file: " + path
, e
); //$NON-NLS-1$
104 try (BufferedRandomAccessFile rafile
= new BufferedRandomAccessFile(path
, "r")) { //$NON-NLS-1$
106 double matches
= 0.0;
107 String line
= rafile
.getNextLine();
108 List
<Pattern
> validationPatterns
= getValidationPatterns();
109 while ((line
!= null) && (lineCount
++ < MAX_LINES
)) {
110 line
= preProcessLine(line
);
111 for(Pattern pattern
: validationPatterns
) {
112 Matcher matcher
= pattern
.matcher(line
);
113 if (matcher
.matches()) {
114 int groupCount
= matcher
.groupCount();
115 matches
+= (1.0 + groupCount
/ ((double) groupCount
+ 1));
118 confidence
= (int) (MAX_CONFIDENCE
* matches
/ lineCount
);
119 line
= rafile
.getNextLine();
121 } catch (IOException e
) {
122 Activator
.logError("Error validating file: " + path
, e
); //$NON-NLS-1$
123 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, "IOException validating file: " + path
, e
); //$NON-NLS-1$
126 return new TraceValidationStatus(confidence
, Activator
.PLUGIN_ID
);
130 public void initTrace(IResource resource
, String path
, Class
<?
extends ITmfEvent
> type
) throws TmfTraceException
{
131 super.initTrace(resource
, path
, type
);
135 private void initFile() throws TmfTraceException
{
138 fFile
= new BufferedRandomAccessFile(getPath(), "r"); //$NON-NLS-1$
139 } catch (IOException e
) {
140 throw new TmfTraceException(e
.getMessage(), e
);
145 public synchronized void dispose() {
150 private void closeFile() {
154 } catch (IOException e
) {
162 public synchronized TextTraceContext
seekEvent(ITmfLocation location
) {
163 TextTraceContext context
= new TextTraceContext(NULL_LOCATION
, ITmfContext
.UNKNOWN_RANK
);
164 if (NULL_LOCATION
.equals(location
) || fFile
== null) {
168 if (location
== null) {
170 } else if (location
.getLocationInfo() instanceof Long
) {
171 fFile
.seek((Long
) location
.getLocationInfo());
173 long rawPos
= fFile
.getFilePointer();
174 String line
= fFile
.getNextLine();
175 while (line
!= null) {
176 line
= preProcessLine(line
);
177 Matcher matcher
= getFirstLinePattern().matcher(line
);
178 if (matcher
.matches()) {
179 setupContext(context
, rawPos
, line
, matcher
);
182 rawPos
= fFile
.getFilePointer();
183 line
= fFile
.getNextLine();
186 } catch (IOException e
) {
187 Activator
.logError("Error seeking file: " + getPath(), e
); //$NON-NLS-1$
192 private void setupContext(TextTraceContext context
, long rawPos
, String line
, Matcher matcher
) throws IOException
{
193 context
.setLocation(new TmfLongLocation(rawPos
));
194 context
.firstLineMatcher
= matcher
;
195 context
.firstLine
= line
;
196 context
.nextLineLocation
= fFile
.getFilePointer();
200 public synchronized TextTraceContext
seekEvent(double ratio
) {
202 return new TextTraceContext(NULL_LOCATION
, ITmfContext
.UNKNOWN_RANK
);
205 long pos
= Math
.round(ratio
* fFile
.length());
208 if (fFile
.read() == '\n') {
213 ITmfLocation location
= new TmfLongLocation(Long
.valueOf(pos
));
214 TextTraceContext context
= seekEvent(location
);
215 context
.setRank(ITmfContext
.UNKNOWN_RANK
);
217 } catch (IOException e
) {
218 Activator
.logError("Error seeking file: " + getPath(), e
); //$NON-NLS-1$
219 return new TextTraceContext(NULL_LOCATION
, ITmfContext
.UNKNOWN_RANK
);
224 public double getLocationRatio(ITmfLocation location
) {
229 long length
= fFile
.length();
233 if (location
.getLocationInfo() instanceof Long
) {
234 return (double) ((Long
) location
.getLocationInfo()) / length
;
236 } catch (IOException e
) {
237 Activator
.logError("Error reading file: " + getPath(), e
); //$NON-NLS-1$
243 public ITmfLocation
getCurrentLocation() {
248 public TextTraceEvent
parseEvent(ITmfContext tmfContext
) {
249 TextTraceContext context
= seekEvent(tmfContext
.getLocation());
250 return parse(context
);
254 public synchronized @Nullable T
getNext(ITmfContext context
) {
255 if (!(context
instanceof TextTraceContext
)) {
256 throw new IllegalArgumentException();
258 TextTraceContext savedContext
= new TextTraceContext(context
.getLocation(), context
.getRank());
259 @Nullable T event
= parse((TextTraceContext
) context
);
261 updateAttributes(savedContext
, event
);
262 context
.increaseRank();
268 * Parse the next event. The context is advanced.
272 * @return the next event or null
274 protected synchronized @Nullable T
parse(TextTraceContext tmfContext
) {
278 TextTraceContext context
= tmfContext
;
279 ITmfLocation location
= context
.getLocation();
280 if (location
== null || !(location
.getLocationInfo() instanceof Long
) || NULL_LOCATION
.equals(location
)) {
284 T event
= parseFirstLine(context
.firstLineMatcher
, context
.firstLine
);
287 if (fFile
.getFilePointer() != context
.nextLineLocation
) {
288 fFile
.seek(context
.nextLineLocation
);
290 long rawPos
= fFile
.getFilePointer();
291 String line
= fFile
.getNextLine();
292 while (line
!= null) {
293 line
= preProcessLine(line
);
294 Matcher matcher
= getFirstLinePattern().matcher(line
);
295 if (matcher
.matches()) {
296 setupContext(context
, rawPos
, line
, matcher
);
299 parseNextLine(event
, line
);
300 rawPos
= fFile
.getFilePointer();
301 line
= fFile
.getNextLine();
303 } catch (IOException e
) {
304 Activator
.logError("Error reading file: " + getPath(), e
); //$NON-NLS-1$
307 context
.setLocation(NULL_LOCATION
);
312 * Pre-processes the input line. The default implementation returns the
316 * non-null input string
317 * @return the pre-processed input line
320 protected String
preProcessLine(@NonNull String line
) {
325 * Gets the first line pattern.
327 * @return The first line pattern
329 protected abstract Pattern
getFirstLinePattern();
332 * Parses the first line data and returns a new event. When constructing the
333 * event, the concrete trace should use the trace's timestamp transform to
334 * create the timestamp, by either transforming the parsed time value
335 * directly or by using the method {@link #createTimestamp(long)}.
341 * @return The parsed event
343 protected abstract T
parseFirstLine(Matcher matcher
, String line
);
346 * Parses the next line data for the current event.
349 * The current event being parsed
353 protected abstract void parseNextLine(T event
, String line
);
356 * Returns a ordered list of validation patterns that will be used in
357 * the default {@link #validate(IProject, String)} method to match
358 * the first 100 to compute the confidence level
360 * @return collection of patterns to validate against
362 protected List
<Pattern
> getValidationPatterns() {
363 return Collections
.singletonList(getFirstLinePattern());
366 // ------------------------------------------------------------------------
368 // ------------------------------------------------------------------------
371 * Strip quotes surrounding a string
375 * @return The string without quotes
377 protected static String
replaceQuotes(String input
) {
378 String out
= input
.replaceAll("^\"|(\"\\s*)$", ""); //$NON-NLS-1$//$NON-NLS-2$
383 * Strip brackets surrounding a string
387 * @return The string without brackets
389 protected static String
replaceBrackets(String input
) {
390 String out
= input
.replaceAll("^\\{|(\\}\\s*)$", ""); //$NON-NLS-1$//$NON-NLS-2$
394 private static int fCheckpointSize
= -1;
397 public synchronized int getCheckpointSize() {
398 if (fCheckpointSize
== -1) {
399 TmfCheckpoint c
= new TmfCheckpoint(TmfTimestamp
.ZERO
, new TmfLongLocation(0L), 0);
400 ByteBuffer b
= ByteBuffer
.allocate(ITmfCheckpoint
.MAX_SERIALIZE_SIZE
);
403 fCheckpointSize
= b
.position();
406 return fCheckpointSize
;
410 protected ITmfTraceIndexer
createIndexer(int interval
) {
411 return new TmfBTreeTraceIndexer(this, interval
);
415 public ITmfLocation
restoreLocation(ByteBuffer bufferIn
) {
416 return new TmfLongLocation(bufferIn
);
421 public void traceRangeUpdated(TmfTraceRangeUpdatedSignal signal
) {
422 if (signal
.getTrace() == this) {
424 synchronized (this) {
425 // Reset the file handle in case it has reached the end of the
426 // file already. Otherwise, it will not be able to read new data
427 // pass the previous end.
430 } catch (TmfTraceException e
) {
431 Activator
.logError(e
.getLocalizedMessage(), e
);
434 super.traceRangeUpdated(signal
);
441 public synchronized ITmfTimestamp
readEnd() {
443 Long pos
= fFile
.length() - 1;
444 /* Outer loop to find the first line of a matcher group. */
446 /* Inner loop to find line beginning */
449 if (fFile
.read() == '\n') {
454 ITmfLocation location
= new TmfLongLocation(pos
);
455 ITmfContext context
= seekEvent(location
);
456 ITmfEvent event
= getNext(context
);
459 return event
.getTimestamp();
463 } catch (IOException e
) {