Commit | Line | Data |
---|---|---|
866e5b51 | 1 | /******************************************************************************* |
11252342 | 2 | * Copyright (c) 2011, 2013 Ericsson, Ecole Polytechnique de Montreal and others |
866e5b51 FC |
3 | * |
4 | * All rights reserved. This program and the accompanying materials are made | |
5 | * 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: Matthew Khouzam - Initial API and implementation | |
10 | * Contributors: Simon Marchi - Initial API and implementation | |
11 | *******************************************************************************/ | |
12 | ||
486efb2e | 13 | package org.eclipse.linuxtools.ctf.core.trace; |
866e5b51 FC |
14 | |
15 | import java.io.File; | |
aa3b05ef FC |
16 | import java.io.IOException; |
17 | import java.nio.MappedByteBuffer; | |
866e5b51 | 18 | import java.nio.channels.FileChannel; |
aa3b05ef | 19 | import java.nio.channels.FileChannel.MapMode; |
866e5b51 FC |
20 | import java.util.UUID; |
21 | ||
486efb2e | 22 | import org.eclipse.linuxtools.ctf.core.event.io.BitBuffer; |
866e5b51 FC |
23 | import org.eclipse.linuxtools.ctf.core.event.types.ArrayDefinition; |
24 | import org.eclipse.linuxtools.ctf.core.event.types.Definition; | |
21fb02fa MK |
25 | import org.eclipse.linuxtools.ctf.core.event.types.EnumDefinition; |
26 | import org.eclipse.linuxtools.ctf.core.event.types.FloatDefinition; | |
866e5b51 FC |
27 | import org.eclipse.linuxtools.ctf.core.event.types.IDefinitionScope; |
28 | import org.eclipse.linuxtools.ctf.core.event.types.IntegerDefinition; | |
21fb02fa | 29 | import org.eclipse.linuxtools.ctf.core.event.types.StringDefinition; |
866e5b51 | 30 | import org.eclipse.linuxtools.ctf.core.event.types.StructDefinition; |
486efb2e AM |
31 | import org.eclipse.linuxtools.internal.ctf.core.trace.StreamInputPacketIndex; |
32 | import org.eclipse.linuxtools.internal.ctf.core.trace.StreamInputPacketIndexEntry; | |
866e5b51 FC |
33 | |
34 | /** | |
35 | * <b><u>StreamInput</u></b> | |
36 | * <p> | |
37 | * Represents a trace file that belongs to a certain stream. | |
486efb2e | 38 | * @since 2.0 |
866e5b51 FC |
39 | */ |
40 | public class StreamInput implements IDefinitionScope { | |
41 | ||
42 | // ------------------------------------------------------------------------ | |
43 | // Attributes | |
44 | // ------------------------------------------------------------------------ | |
45 | ||
46 | /** | |
47 | * The associated Stream | |
48 | */ | |
49 | private final Stream stream; | |
50 | ||
51 | /** | |
52 | * FileChannel to the trace file | |
53 | */ | |
54 | private final FileChannel fileChannel; | |
55 | ||
56 | /** | |
57 | * Information on the file (used for debugging) | |
58 | */ | |
0594c61c | 59 | private final File file; |
866e5b51 FC |
60 | |
61 | /** | |
62 | * The packet index of this input | |
63 | */ | |
bfe038ff | 64 | private final StreamInputPacketIndex index; |
866e5b51 FC |
65 | |
66 | private long timestampEnd; | |
67 | ||
bfe038ff MK |
68 | /* |
69 | * Definition of trace packet header | |
70 | */ | |
0594c61c | 71 | private StructDefinition tracePacketHeaderDef = null; |
bfe038ff MK |
72 | |
73 | /* | |
74 | * Definition of trace stream packet context | |
75 | */ | |
0594c61c | 76 | private StructDefinition streamPacketContextDef = null; |
bfe038ff | 77 | |
132a02b0 MK |
78 | /* |
79 | * Total number of lost events in this stream | |
80 | */ | |
0594c61c | 81 | private long lostSoFar = 0; |
132a02b0 | 82 | |
866e5b51 FC |
83 | // ------------------------------------------------------------------------ |
84 | // Constructors | |
85 | // ------------------------------------------------------------------------ | |
86 | ||
87 | /** | |
88 | * Constructs a StreamInput. | |
89 | * | |
90 | * @param stream | |
91 | * The stream to which this StreamInput belongs to. | |
92 | * @param fileChannel | |
93 | * The FileChannel to the trace file. | |
94 | * @param file | |
95 | * Information about the trace file (for debugging purposes). | |
96 | */ | |
97 | public StreamInput(Stream stream, FileChannel fileChannel, File file) { | |
98 | this.stream = stream; | |
99 | this.fileChannel = fileChannel; | |
100 | this.file = file; | |
1fbaecd1 | 101 | this.index = stream.getTrace().getIndex(this); |
866e5b51 FC |
102 | } |
103 | ||
104 | // ------------------------------------------------------------------------ | |
105 | // Getters/Setters/Predicates | |
106 | // ------------------------------------------------------------------------ | |
107 | ||
9ac2eb62 MK |
108 | /** |
109 | * Gets the stream the streamInput wrapper is wrapping | |
21fb02fa | 110 | * |
9ac2eb62 MK |
111 | * @return the stream the streamInput wrapper is wrapping |
112 | */ | |
866e5b51 FC |
113 | public Stream getStream() { |
114 | return stream; | |
115 | } | |
116 | ||
9ac2eb62 | 117 | /** |
ecb12461 | 118 | * The common streamInput Index |
21fb02fa | 119 | * |
9ac2eb62 MK |
120 | * @return the stream input Index |
121 | */ | |
486efb2e | 122 | StreamInputPacketIndex getIndex() { |
866e5b51 FC |
123 | return index; |
124 | } | |
125 | ||
9ac2eb62 | 126 | /** |
21fb02fa | 127 | * Gets the filechannel of the streamInput. This is a limited Java |
ecb12461 | 128 | * resource. |
21fb02fa | 129 | * |
9ac2eb62 MK |
130 | * @return the filechannel |
131 | */ | |
866e5b51 FC |
132 | public FileChannel getFileChannel() { |
133 | return fileChannel; | |
134 | } | |
135 | ||
9ac2eb62 MK |
136 | /** |
137 | * Gets the filename of the streamInput file. | |
21fb02fa | 138 | * |
9ac2eb62 MK |
139 | * @return the filename of the streaminput file. |
140 | */ | |
866e5b51 FC |
141 | public String getFilename() { |
142 | return file.getName(); | |
143 | } | |
144 | ||
9ac2eb62 | 145 | /** |
ecb12461 | 146 | * Gets the last read timestamp of a stream. (this is not necessarily the |
21fb02fa MK |
147 | * last time in the stream.) |
148 | * | |
9ac2eb62 MK |
149 | * @return the last read timestamp |
150 | */ | |
866e5b51 FC |
151 | public long getTimestampEnd() { |
152 | return timestampEnd; | |
153 | } | |
154 | ||
9ac2eb62 | 155 | /** |
21fb02fa MK |
156 | * Sets the last read timestamp of a stream. (this is not necessarily the |
157 | * last time in the stream.) | |
158 | * | |
159 | * @param timestampEnd | |
160 | * the last read timestamp | |
9ac2eb62 | 161 | */ |
866e5b51 FC |
162 | public void setTimestampEnd(long timestampEnd) { |
163 | this.timestampEnd = timestampEnd; | |
164 | } | |
165 | ||
9ac2eb62 | 166 | /** |
ecb12461 | 167 | * Useless for streaminputs |
9ac2eb62 | 168 | */ |
866e5b51 FC |
169 | @Override |
170 | public String getPath() { | |
171 | return ""; //$NON-NLS-1$ | |
172 | } | |
173 | ||
174 | // ------------------------------------------------------------------------ | |
175 | // Operations | |
176 | // ------------------------------------------------------------------------ | |
177 | ||
178 | @Override | |
179 | public Definition lookupDefinition(String lookupPath) { | |
180 | /* TODO: lookup in different dynamic scopes is not supported yet. */ | |
181 | return null; | |
182 | } | |
183 | ||
184 | /** | |
185 | * Create the index for this trace file. | |
bfe038ff MK |
186 | */ |
187 | public void setupIndex() { | |
188 | ||
bfe038ff MK |
189 | /* |
190 | * The BitBuffer to extract data from the StreamInput | |
191 | */ | |
192 | BitBuffer bitBuffer = new BitBuffer(); | |
8b8e48ed | 193 | bitBuffer.setByteOrder(this.getStream().getTrace().getByteOrder()); |
bfe038ff MK |
194 | |
195 | /* | |
196 | * Create the definitions we need to read the packet headers + contexts | |
197 | */ | |
198 | if (getStream().getTrace().getPacketHeader() != null) { | |
199 | tracePacketHeaderDef = getStream().getTrace().getPacketHeader() | |
200 | .createDefinition(this, "trace.packet.header"); //$NON-NLS-1$ | |
201 | } | |
202 | ||
203 | if (getStream().getPacketContextDecl() != null) { | |
204 | streamPacketContextDef = getStream().getPacketContextDecl() | |
205 | .createDefinition(this, "stream.packet.context"); //$NON-NLS-1$ | |
206 | } | |
207 | ||
208 | } | |
209 | ||
9ac2eb62 MK |
210 | /** |
211 | * Adds the next packet header index entry to the index of a stream input. | |
be6df2d8 AM |
212 | * |
213 | * @warning slow, can corrupt data if not used properly | |
9ac2eb62 MK |
214 | * @return true if there are more packets to add |
215 | * @throws CTFReaderException | |
be6df2d8 | 216 | * If there was a problem reading the packed header |
9ac2eb62 | 217 | */ |
bfe038ff MK |
218 | public boolean addPacketHeaderIndex() throws CTFReaderException { |
219 | long currentPos = 0L; | |
220 | if (!index.getEntries().isEmpty()) { | |
221 | StreamInputPacketIndexEntry pos = index.getEntries().lastElement(); | |
222 | currentPos = computeNextOffset(pos); | |
223 | } | |
224 | long fileSize = getStreamSize(); | |
225 | if (currentPos < fileSize) { | |
226 | BitBuffer bitBuffer = new BitBuffer(); | |
8b8e48ed | 227 | bitBuffer.setByteOrder(this.getStream().getTrace().getByteOrder()); |
bfe038ff MK |
228 | StreamInputPacketIndexEntry packetIndex = new StreamInputPacketIndexEntry( |
229 | currentPos); | |
230 | createPacketIndexEntry(fileSize, currentPos, packetIndex, | |
231 | tracePacketHeaderDef, streamPacketContextDef, bitBuffer); | |
232 | index.addEntry(packetIndex); | |
233 | return true; | |
234 | } | |
235 | return false; | |
236 | } | |
237 | ||
bfe038ff MK |
238 | private long getStreamSize() { |
239 | return file.length(); | |
240 | } | |
241 | ||
bfe038ff MK |
242 | private long createPacketIndexEntry(long fileSizeBytes, |
243 | long packetOffsetBytes, StreamInputPacketIndexEntry packetIndex, | |
244 | StructDefinition tracePacketHeaderDef, | |
245 | StructDefinition streamPacketContextDef, BitBuffer bitBuffer) | |
246 | throws CTFReaderException { | |
0594c61c AM |
247 | |
248 | /* Ignoring the return value, but this call is needed to initialize the input */ | |
249 | createPacketBitBuffer(fileSizeBytes, packetOffsetBytes, packetIndex, bitBuffer); | |
bfe038ff | 250 | |
866e5b51 | 251 | /* |
bfe038ff | 252 | * Read the trace packet header if it exists. |
866e5b51 | 253 | */ |
bfe038ff MK |
254 | if (tracePacketHeaderDef != null) { |
255 | parseTracePacketHeader(tracePacketHeaderDef, bitBuffer); | |
866e5b51 FC |
256 | } |
257 | ||
258 | /* | |
bfe038ff | 259 | * Read the stream packet context if it exists. |
866e5b51 | 260 | */ |
bfe038ff MK |
261 | if (streamPacketContextDef != null) { |
262 | parsePacketContext(fileSizeBytes, streamPacketContextDef, | |
263 | bitBuffer, packetIndex); | |
264 | } else { | |
265 | setPacketContextNull(fileSizeBytes, packetIndex); | |
266 | } | |
267 | ||
268 | /* Basic validation */ | |
269 | if (packetIndex.getContentSizeBits() > packetIndex.getPacketSizeBits()) { | |
270 | throw new CTFReaderException("Content size > packet size"); //$NON-NLS-1$ | |
271 | } | |
866e5b51 | 272 | |
bfe038ff MK |
273 | if (packetIndex.getPacketSizeBits() > ((fileSizeBytes - packetIndex |
274 | .getOffsetBytes()) * 8)) { | |
275 | throw new CTFReaderException( | |
276 | "Not enough data remaining in the file for the size of this packet"); //$NON-NLS-1$ | |
277 | } | |
278 | ||
279 | /* | |
280 | * Offset in the file, in bits | |
281 | */ | |
282 | packetIndex.setDataOffsetBits(bitBuffer.position()); | |
283 | ||
284 | /* | |
285 | * Update the counting packet offset | |
286 | */ | |
21fb02fa | 287 | return computeNextOffset(packetIndex); |
bfe038ff MK |
288 | } |
289 | ||
290 | /** | |
291 | * @param packetIndex | |
292 | * @return | |
293 | */ | |
294 | private static long computeNextOffset( | |
295 | StreamInputPacketIndexEntry packetIndex) { | |
296 | return packetIndex.getOffsetBytes() | |
297 | + ((packetIndex.getPacketSizeBits() + 7) / 8); | |
298 | } | |
299 | ||
aa3b05ef FC |
300 | /** |
301 | * @param fileSizeBytes | |
302 | * @param packetOffsetBytes | |
303 | * @param packetIndex | |
304 | * @param bitBuffer | |
305 | * @return | |
306 | * @throws CTFReaderException | |
307 | */ | |
308 | private MappedByteBuffer createPacketBitBuffer(long fileSizeBytes, | |
309 | long packetOffsetBytes, StreamInputPacketIndexEntry packetIndex, | |
310 | BitBuffer bitBuffer) throws CTFReaderException { | |
311 | /* | |
312 | * Initial size, it should map at least the packet header + context | |
313 | * size. | |
314 | * | |
315 | * TODO: use a less arbitrary size. | |
316 | */ | |
317 | long mapSize = 4096; | |
318 | /* | |
319 | * If there is less data remaining than what we want to map, reduce the | |
320 | * map size. | |
321 | */ | |
322 | if ((fileSizeBytes - packetIndex.getOffsetBytes()) < mapSize) { | |
323 | mapSize = fileSizeBytes - packetIndex.getOffsetBytes(); | |
324 | } | |
325 | ||
326 | /* | |
327 | * Map the packet. | |
328 | */ | |
329 | MappedByteBuffer bb; | |
330 | ||
331 | try { | |
332 | bb = fileChannel.map(MapMode.READ_ONLY, packetOffsetBytes, mapSize); | |
333 | } catch (IOException e) { | |
334 | throw new CTFReaderException(e); | |
335 | } | |
336 | bitBuffer.setByteBuffer(bb); | |
337 | return bb; | |
338 | } | |
bfe038ff | 339 | |
bfe038ff MK |
340 | private void parseTracePacketHeader(StructDefinition tracePacketHeaderDef, |
341 | BitBuffer bitBuffer) throws CTFReaderException { | |
342 | tracePacketHeaderDef.read(bitBuffer); | |
866e5b51 FC |
343 | |
344 | /* | |
bfe038ff | 345 | * Check the CTF magic number |
866e5b51 | 346 | */ |
bfe038ff MK |
347 | IntegerDefinition magicDef = (IntegerDefinition) tracePacketHeaderDef |
348 | .lookupDefinition("magic"); //$NON-NLS-1$ | |
349 | if (magicDef != null) { | |
350 | int magic = (int) magicDef.getValue(); | |
351 | if (magic != Utils.CTF_MAGIC) { | |
352 | throw new CTFReaderException( | |
353 | "CTF magic mismatch " + Integer.toHexString(magic) + " vs " + Integer.toHexString(Utils.CTF_MAGIC)); //$NON-NLS-1$//$NON-NLS-2$ | |
354 | } | |
866e5b51 FC |
355 | } |
356 | ||
357 | /* | |
bfe038ff | 358 | * Check the trace UUID |
866e5b51 | 359 | */ |
0594c61c AM |
360 | ArrayDefinition uuidDef = |
361 | (ArrayDefinition) tracePacketHeaderDef.lookupDefinition("uuid"); //$NON-NLS-1$ | |
bfe038ff MK |
362 | if (uuidDef != null) { |
363 | byte[] uuidArray = new byte[16]; | |
364 | ||
0594c61c AM |
365 | for (int i = 0; i < uuidArray.length; i++) { |
366 | IntegerDefinition uuidByteDef = (IntegerDefinition) uuidDef.getElem(i); | |
bfe038ff | 367 | uuidArray[i] = (byte) uuidByteDef.getValue(); |
866e5b51 | 368 | } |
866e5b51 | 369 | |
bfe038ff | 370 | UUID uuid = Utils.makeUUID(uuidArray); |
866e5b51 | 371 | |
bfe038ff MK |
372 | if (!getStream().getTrace().getUUID().equals(uuid)) { |
373 | throw new CTFReaderException("UUID mismatch"); //$NON-NLS-1$ | |
866e5b51 | 374 | } |
bfe038ff | 375 | } |
866e5b51 | 376 | |
bfe038ff MK |
377 | /* |
378 | * Check that the stream id did not change | |
379 | */ | |
380 | IntegerDefinition streamIDDef = (IntegerDefinition) tracePacketHeaderDef | |
381 | .lookupDefinition("stream_id"); //$NON-NLS-1$ | |
382 | if (streamIDDef != null) { | |
383 | long streamID = streamIDDef.getValue(); | |
866e5b51 | 384 | |
bfe038ff | 385 | if (streamID != getStream().getId()) { |
866e5b51 | 386 | throw new CTFReaderException( |
bfe038ff | 387 | "Stream ID changing within a StreamInput"); //$NON-NLS-1$ |
866e5b51 | 388 | } |
bfe038ff MK |
389 | } |
390 | } | |
866e5b51 | 391 | |
bfe038ff MK |
392 | private static void setPacketContextNull(long fileSizeBytes, |
393 | StreamInputPacketIndexEntry packetIndex) { | |
394 | /* | |
395 | * If there is no packet context, infer the content and packet size from | |
396 | * the file size (assume that there is only one packet and no padding) | |
397 | */ | |
47ca6c05 SM |
398 | packetIndex.setContentSizeBits(fileSizeBytes * 8); |
399 | packetIndex.setPacketSizeBits(fileSizeBytes * 8); | |
bfe038ff | 400 | } |
866e5b51 | 401 | |
bfe038ff MK |
402 | private void parsePacketContext(long fileSizeBytes, |
403 | StructDefinition streamPacketContextDef, BitBuffer bitBuffer, | |
404 | StreamInputPacketIndexEntry packetIndex) { | |
405 | streamPacketContextDef.read(bitBuffer); | |
866e5b51 | 406 | |
21fb02fa MK |
407 | for (String field : streamPacketContextDef.getDeclaration() |
408 | .getFieldsList()) { | |
409 | Definition id = streamPacketContextDef.lookupDefinition(field); | |
410 | if (id instanceof IntegerDefinition) { | |
411 | packetIndex.addAttribute(field, | |
412 | ((IntegerDefinition) id).getValue()); | |
413 | } else if (id instanceof FloatDefinition) { | |
414 | packetIndex.addAttribute(field, | |
415 | ((FloatDefinition) id).getValue()); | |
416 | } else if (id instanceof EnumDefinition) { | |
417 | packetIndex.addAttribute(field, | |
418 | ((EnumDefinition) id).getValue()); | |
419 | } else if (id instanceof StringDefinition) { | |
420 | packetIndex.addAttribute(field, | |
421 | ((StringDefinition) id).getValue()); | |
422 | } | |
21fb02fa MK |
423 | } |
424 | ||
425 | Long contentSize = (Long) packetIndex.lookupAttribute("content_size"); //$NON-NLS-1$ | |
426 | Long packetSize = (Long) packetIndex.lookupAttribute("packet_size"); //$NON-NLS-1$ | |
0594c61c AM |
427 | Long tsBegin = (Long) packetIndex.lookupAttribute("timestamp_begin"); //$NON-NLS-1$ |
428 | Long tsEnd = (Long) packetIndex.lookupAttribute("timestamp_end"); //$NON-NLS-1$ | |
21fb02fa MK |
429 | String device = (String) packetIndex.lookupAttribute("device"); //$NON-NLS-1$ |
430 | // LTTng Specific | |
0594c61c | 431 | Long cpuId = (Long) packetIndex.lookupAttribute("cpu_id"); //$NON-NLS-1$ |
132a02b0 MK |
432 | Long lostEvents = (Long) packetIndex.lookupAttribute("events_discarded"); //$NON-NLS-1$ |
433 | ||
434 | /* Read the content size in bits */ | |
21fb02fa MK |
435 | if (contentSize != null) { |
436 | packetIndex.setContentSizeBits(contentSize.intValue()); | |
bfe038ff MK |
437 | } else { |
438 | packetIndex.setContentSizeBits((int) (fileSizeBytes * 8)); | |
439 | } | |
866e5b51 | 440 | |
132a02b0 | 441 | /* Read the packet size in bits */ |
21fb02fa MK |
442 | if (packetSize != null) { |
443 | packetIndex.setPacketSizeBits(packetSize.intValue()); | |
bfe038ff MK |
444 | } else { |
445 | if (packetIndex.getContentSizeBits() != 0) { | |
446 | packetIndex.setPacketSizeBits(packetIndex.getContentSizeBits()); | |
447 | } else { | |
448 | packetIndex.setPacketSizeBits((int) (fileSizeBytes * 8)); | |
449 | } | |
450 | } | |
451 | ||
132a02b0 | 452 | /* Read the begin timestamp */ |
0594c61c AM |
453 | if (tsBegin != null) { |
454 | packetIndex.setTimestampBegin(tsBegin.longValue()); | |
bfe038ff MK |
455 | } |
456 | ||
132a02b0 | 457 | /* Read the end timestamp */ |
0594c61c | 458 | if (tsEnd != null) { |
ecb12461 | 459 | if (tsEnd == -1) { |
0594c61c | 460 | tsEnd = Long.MAX_VALUE; |
21fb02fa | 461 | } |
0594c61c | 462 | packetIndex.setTimestampEnd(tsEnd.longValue()); |
bfe038ff | 463 | setTimestampEnd(packetIndex.getTimestampEnd()); |
866e5b51 | 464 | } |
21fb02fa MK |
465 | |
466 | if (device != null) { | |
467 | packetIndex.setTarget(device); | |
468 | } | |
469 | ||
0594c61c AM |
470 | if (cpuId != null) { |
471 | packetIndex.setTarget("CPU" + cpuId.toString()); //$NON-NLS-1$ | |
21fb02fa | 472 | } |
132a02b0 MK |
473 | |
474 | if (lostEvents != null) { | |
475 | packetIndex.setLostEvents(lostEvents - lostSoFar); | |
476 | this.lostSoFar = lostEvents; | |
477 | } | |
866e5b51 FC |
478 | } |
479 | ||
81c8e6f7 MK |
480 | @Override |
481 | public int hashCode() { | |
482 | final int prime = 31; | |
483 | int result = 1; | |
484 | result = (prime * result) + ((file == null) ? 0 : file.hashCode()); | |
485 | return result; | |
486 | } | |
487 | ||
81c8e6f7 MK |
488 | @Override |
489 | public boolean equals(Object obj) { | |
490 | if (this == obj) { | |
491 | return true; | |
492 | } | |
493 | if (obj == null) { | |
494 | return false; | |
495 | } | |
496 | if (!(obj instanceof StreamInput)) { | |
497 | return false; | |
498 | } | |
499 | StreamInput other = (StreamInput) obj; | |
500 | if (file == null) { | |
501 | if (other.file != null) { | |
502 | return false; | |
503 | } | |
504 | } else if (!file.equals(other.file)) { | |
505 | return false; | |
506 | } | |
507 | return true; | |
508 | } | |
509 | ||
866e5b51 | 510 | } |