Remove leftover comments
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.core / src / org / eclipse / linuxtools / tmf / core / trace / TmfExperiment.java
CommitLineData
8c8bf09f 1/*******************************************************************************
0316808c 2 * Copyright (c) 2009, 2010, 2012 Ericsson
ce2388e0 3 *
8c8bf09f
ASL
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
ce2388e0 8 *
8c8bf09f
ASL
9 * Contributors:
10 * Francois Chouinard - Initial API and implementation
0316808c 11 * Francois Chouinard - Updated as per TMF Trace Model 1.0
8c8bf09f
ASL
12 *******************************************************************************/
13
9e0640dc 14package org.eclipse.linuxtools.tmf.core.trace;
8c8bf09f 15
a1091415 16import org.eclipse.core.resources.IFile;
12c155f5 17import org.eclipse.core.resources.IProject;
828e5592 18import org.eclipse.core.resources.IResource;
9e0640dc
FC
19import org.eclipse.linuxtools.internal.tmf.core.trace.TmfExperimentContext;
20import org.eclipse.linuxtools.internal.tmf.core.trace.TmfExperimentLocation;
21import org.eclipse.linuxtools.internal.tmf.core.trace.TmfLocationArray;
72f1e62a 22import org.eclipse.linuxtools.tmf.core.event.ITmfEvent;
4df4581d 23import org.eclipse.linuxtools.tmf.core.event.ITmfTimestamp;
6c13869b
FC
24import org.eclipse.linuxtools.tmf.core.event.TmfTimeRange;
25import org.eclipse.linuxtools.tmf.core.event.TmfTimestamp;
0316808c 26import org.eclipse.linuxtools.tmf.core.exceptions.TmfTraceException;
49e2f79a
FC
27import org.eclipse.linuxtools.tmf.core.request.ITmfDataRequest;
28import org.eclipse.linuxtools.tmf.core.request.ITmfEventRequest;
1b70b6dc 29import org.eclipse.linuxtools.tmf.core.signal.TmfEndSynchSignal;
6c13869b
FC
30import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentDisposedSignal;
31import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentRangeUpdatedSignal;
32import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentSelectedSignal;
33import org.eclipse.linuxtools.tmf.core.signal.TmfExperimentUpdatedSignal;
34import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler;
c32744d6 35import org.eclipse.linuxtools.tmf.core.signal.TmfTraceUpdatedSignal;
8c8bf09f
ASL
36
37/**
9e0640dc 38 * TmfExperiment presents a time-ordered, unified view of a set of ITmfTrace:s
cbdacf03 39 * that are part of a tracing experiment.
4b7b3670
FC
40 *
41 * @version 1.0
42 * @author Francois Chouinard
8c8bf09f 43 */
0316808c 44public class TmfExperiment<T extends ITmfEvent> extends TmfTrace<T> implements ITmfEventParser<T> {
8c8bf09f 45
c32744d6
FC
46 // ------------------------------------------------------------------------
47 // Constants
48 // ------------------------------------------------------------------------
49
9e0640dc
FC
50 /**
51 * The default index page size
52 */
53 public static final int DEFAULT_INDEX_PAGE_SIZE = 5000;
c32744d6 54
8c8bf09f
ASL
55 // ------------------------------------------------------------------------
56 // Attributes
57 // ------------------------------------------------------------------------
58
9e0640dc
FC
59 /**
60 * The currently selected experiment (null if none)
61 */
c32744d6 62 protected static TmfExperiment<?> fCurrentExperiment = null;
e31e01e8 63
9e0640dc
FC
64 /**
65 * The set of traces that constitute the experiment
66 */
c32744d6 67 protected ITmfTrace<T>[] fTraces;
8c8bf09f 68
9e0640dc
FC
69 /**
70 * The set of traces that constitute the experiment
71 */
72 private boolean fInitialized = false;
a1091415 73
9e0640dc
FC
74 /**
75 * The experiment bookmarks file
76 */
77 private IFile fBookmarksFile;
828e5592 78
49e2f79a
FC
79
80 // Saved experiment context (optimization)
81 private TmfExperimentContext fExperimentContext;
82
8c8bf09f 83 // ------------------------------------------------------------------------
9e0640dc 84 // Construction
8c8bf09f
ASL
85 // ------------------------------------------------------------------------
86
9e0640dc
FC
87 /**
88 * @param type
89 * @param id
90 * @param traces
91 * @throws TmfTraceException
92 */
93 public TmfExperiment(final Class<T> type, final String id, final ITmfTrace<T>[] traces) {
94 this(type, id, traces, DEFAULT_INDEX_PAGE_SIZE);
96c6806f
PT
95 }
96
8c8bf09f
ASL
97 /**
98 * @param type
99 * @param id
100 * @param traces
8c8bf09f 101 * @param indexPageSize
0316808c 102 * @throws TmfTraceException
8c8bf09f 103 */
0316808c 104 @SuppressWarnings({ "unchecked", "rawtypes" })
9e0640dc 105 public TmfExperiment(final Class<T> type, final String path, final ITmfTrace<T>[] traces, final int indexPageSize) {
0316808c
FC
106 setCacheSize(indexPageSize);
107 setStreamingInterval(0);
07671572 108 setIndexer(new TmfCheckpointIndexer(this, indexPageSize));
0316808c
FC
109 setParser(this);
110 try {
111 super.initialize(null, path, type);
112 } catch (TmfTraceException e) {
113 e.printStackTrace();
114 }
8c8bf09f 115
a79913eb 116 fTraces = traces;
a87cc4ef 117 setTimeRange(TmfTimeRange.NULL_RANGE);
8c8bf09f 118 }
a79913eb 119
8c8bf09f 120 /**
ff4ed569 121 * Clears the experiment
8c8bf09f
ASL
122 */
123 @Override
12c155f5 124 @SuppressWarnings("rawtypes")
a79913eb
FC
125 public synchronized void dispose() {
126
cbdacf03 127 final TmfExperimentDisposedSignal<T> signal = new TmfExperimentDisposedSignal<T>(this, this);
a79913eb 128 broadcast(signal);
9e0640dc
FC
129
130 if (fCurrentExperiment == this) {
09d11238 131 fCurrentExperiment = null;
9e0640dc 132 }
a79913eb 133
77551cc2
FC
134 // Clean up the index if applicable
135 if (getIndexer() != null) {
136 getIndexer().dispose();
137 }
b5ee6881 138
a79913eb 139 if (fTraces != null) {
cbdacf03 140 for (final ITmfTrace trace : fTraces)
a79913eb 141 trace.dispose();
a79913eb
FC
142 fTraces = null;
143 }
2fb2eb37 144 super.dispose();
8c8bf09f
ASL
145 }
146
9e0640dc
FC
147 // ------------------------------------------------------------------------
148 // ITmfTrace - Initializers
149 // ------------------------------------------------------------------------
150
151 /* (non-Javadoc)
152 * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#validate(org.eclipse.core.resources.IProject, java.lang.String)
153 */
154 @Override
155 public boolean validate(final IProject project, final String path) {
156 return true;
157 }
158
159 /* (non-Javadoc)
160 * @see org.eclipse.linuxtools.tmf.core.trace.TmfTrace#initTrace(org.eclipse.core.resources.IResource, java.lang.String, java.lang.Class)
161 */
162 @Override
163 public void initTrace(final IResource resource, final String path, final Class<T> type) {
164 }
165
8c8bf09f 166 // ------------------------------------------------------------------------
e31e01e8 167 // Accessors
8c8bf09f
ASL
168 // ------------------------------------------------------------------------
169
9e0640dc
FC
170 /**
171 * Selects the current, framework-wide, experiment
172 *
173 * @param experiment das experiment
174 */
cbdacf03 175 public static void setCurrentExperiment(final TmfExperiment<?> experiment) {
9e0640dc 176 if (fCurrentExperiment != null && fCurrentExperiment != experiment) {
09d11238 177 fCurrentExperiment.dispose();
9e0640dc 178 }
a79913eb 179 fCurrentExperiment = experiment;
f6b14ce2
FC
180 }
181
9e0640dc
FC
182 /**
183 * @return das experiment
184 */
e31e01e8 185 public static TmfExperiment<?> getCurrentExperiment() {
a79913eb 186 return fCurrentExperiment;
8c8bf09f
ASL
187 }
188
9e0640dc
FC
189 /**
190 * Get the list of traces. Handle with care...
191 *
192 * @return the experiment traces
193 */
12c155f5 194 public ITmfTrace<T>[] getTraces() {
a79913eb 195 return fTraces;
8c8bf09f
ASL
196 }
197
8c8bf09f 198 /**
cbdacf03
FC
199 * Returns the timestamp of the event at the requested index. If none,
200 * returns null.
a79913eb 201 *
0d9a6d76
FC
202 * @param index the event index (rank)
203 * @return the corresponding event timestamp
8c8bf09f 204 */
cbdacf03 205 public ITmfTimestamp getTimestamp(final int index) {
0316808c 206 final ITmfContext context = seekEvent(index);
c32744d6 207 final ITmfEvent event = getNext(context);
a79913eb 208 return (event != null) ? event.getTimestamp() : null;
8c8bf09f
ASL
209 }
210
9e0640dc
FC
211 /**
212 * Set the file to be used for bookmarks on this experiment
213 *
214 * @param file the bookmarks file
215 */
216 public void setBookmarksFile(final IFile file) {
217 fBookmarksFile = file;
218 }
07671572 219
9e0640dc
FC
220 /**
221 * Get the file used for bookmarks on this experiment
222 *
223 * @return the bookmarks file or null if none is set
224 */
225 public IFile getBookmarksFile() {
226 return fBookmarksFile;
a79913eb
FC
227 }
228
49e2f79a
FC
229 // ------------------------------------------------------------------------
230 // Request management
231 // ------------------------------------------------------------------------
232
233 @Override
408e65d2 234 protected synchronized ITmfContext armRequest(final ITmfDataRequest<T> request) {
49e2f79a
FC
235 if (request instanceof ITmfEventRequest<?>
236 && !TmfTimestamp.BIG_BANG.equals(((ITmfEventRequest<T>) request).getRange().getStartTime())
237 && request.getIndex() == 0)
238 {
239 final ITmfContext context = seekEvent(((ITmfEventRequest<T>) request).getRange().getStartTime());
240 ((ITmfEventRequest<T>) request).setStartIndex((int) context.getRank());
241 return context;
242
243 }
244
245 // Check if we are already at the right index
246 if ((fExperimentContext != null) && fExperimentContext.getRank() == request.getIndex()) {
247 return fExperimentContext;
248 }
249
250 return seekEvent(request.getIndex());
251 }
252
a79913eb 253 // ------------------------------------------------------------------------
9f584e4c
FC
254 // ITmfTrace trace positioning
255 // ------------------------------------------------------------------------
256
9e0640dc
FC
257 /* (non-Javadoc)
258 *
259 * Returns a brand new context based on the location provided and
260 * initializes the event queues
261 *
262 * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#seekEvent(org.eclipse.linuxtools.tmf.core.trace.ITmfLocation)
263 */
a79913eb 264 @Override
9e0640dc 265 public synchronized ITmfContext seekEvent(final ITmfLocation<?> location) {
a79913eb 266 // Validate the location
9e0640dc 267 if (location != null && !(location instanceof TmfExperimentLocation)) {
a79913eb 268 return null; // Throw an exception?
9e0640dc
FC
269 }
270 // Make sure we have something to read from
271 if (fTraces == null) {
a79913eb 272 return null;
9e0640dc 273 }
8f50c396 274
a79913eb 275 // Instantiate the location
9e0640dc
FC
276 final TmfExperimentLocation expLocation = (location == null)
277 ? new TmfExperimentLocation(new TmfLocationArray(new ITmfLocation<?>[fTraces.length]))
278 : (TmfExperimentLocation) location.clone();
8f50c396 279
a79913eb 280 // Create and populate the context's traces contexts
0316808c 281 final TmfExperimentContext context = new TmfExperimentContext(new ITmfContext[fTraces.length]);
9b635e61 282
a79913eb
FC
283 for (int i = 0; i < fTraces.length; i++) {
284 // Get the relevant trace attributes
0316808c 285 final ITmfLocation<?> traceLocation = expLocation.getLocation().getLocations()[i];
7e6347b0 286 context.getContexts()[i] = fTraces[i].seekEvent(traceLocation);
0316808c 287 expLocation.getLocation().getLocations()[i] = context.getContexts()[i].getLocation().clone();
c32744d6 288 context.getEvents()[i] = fTraces[i].getNext(context.getContexts()[i]);
a79913eb 289 }
8f50c396 290
a79913eb
FC
291 // Finalize context
292 context.setLocation(expLocation);
293 context.setLastTrace(TmfExperimentContext.NO_TRACE);
0316808c 294 context.setRank(ITmfContext.UNKNOWN_RANK);
49e2f79a
FC
295
296 fExperimentContext = context;
297
9e0640dc 298 return (ITmfContext) context;
a79913eb 299 }
9f584e4c 300
9e0640dc
FC
301 /* (non-Javadoc)
302 * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#seekEvent(double)
303 */
c76c54bb 304 @Override
0316808c
FC
305 public ITmfContext seekEvent(final double ratio) {
306 final ITmfContext context = seekEvent((long) (ratio * getNbEvents()));
c76c54bb
FC
307 return context;
308 }
309
9e0640dc
FC
310 /* (non-Javadoc)
311 * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getLocationRatio(org.eclipse.linuxtools.tmf.core.trace.ITmfLocation)
312 */
a79913eb 313 @Override
cbdacf03 314 public double getLocationRatio(final ITmfLocation<?> location) {
9e0640dc 315 if (location instanceof TmfExperimentLocation) {
7e6347b0 316 return (double) seekEvent(location).getRank() / getNbEvents();
9e0640dc
FC
317 }
318 return 0.0;
c76c54bb
FC
319 }
320
9e0640dc
FC
321 /* (non-Javadoc)
322 * @see org.eclipse.linuxtools.tmf.core.trace.ITmfTrace#getCurrentLocation()
323 */
a79913eb
FC
324 @Override
325 public ITmfLocation<?> getCurrentLocation() {
a87cc4ef
FC
326 ITmfLocation<?>[] locations = new ITmfLocation<?>[fTraces.length];
327 for (int i = 0; i < fTraces.length; i++) {
328 locations[i] = fTraces[i].getCurrentLocation();
329 }
330 return new TmfExperimentLocation(new TmfLocationArray(locations));
a79913eb 331 }
c76c54bb 332
9e0640dc
FC
333 // ------------------------------------------------------------------------
334 // ITmfTrace trace positioning
335 // ------------------------------------------------------------------------
336
07671572 337 /* (non-Javadoc)
408e65d2 338 * @see org.eclipse.linuxtools.tmf.core.trace.ITmfEventParser#parseEvent(org.eclipse.linuxtools.tmf.core.trace.ITmfContext)
07671572
FC
339 */
340 @Override
408e65d2
FC
341 public synchronized T parseEvent(final ITmfContext context) {
342 final ITmfContext savedContext = context.clone();
343 final T event = getNext(savedContext);
07671572
FC
344 return event;
345 }
a79913eb 346
ce2388e0 347 /* (non-Javadoc)
408e65d2 348 * @see org.eclipse.linuxtools.tmf.core.trace.TmfTrace#getNext(org.eclipse.linuxtools.tmf.core.trace.ITmfContext)
a79913eb 349 */
0316808c
FC
350 @SuppressWarnings("unchecked")
351 @Override
408e65d2 352 public synchronized T getNext(ITmfContext context) {
a79913eb
FC
353
354 // Validate the context
9e0640dc 355 if (!(context instanceof TmfExperimentContext)) {
a79913eb 356 return null; // Throw an exception?
9e0640dc 357 }
a87cc4ef 358 TmfExperimentContext expContext = (TmfExperimentContext) context;
a79913eb 359
a87cc4ef 360 // If an event was consumed previously, first get the next one from that trace
cbdacf03 361 final int lastTrace = expContext.getLastTrace();
a79913eb 362 if (lastTrace != TmfExperimentContext.NO_TRACE) {
cbdacf03 363 final ITmfContext traceContext = expContext.getContexts()[lastTrace];
c32744d6 364 expContext.getEvents()[lastTrace] = fTraces[lastTrace].getNext(traceContext);
a79913eb 365 expContext.setLastTrace(TmfExperimentContext.NO_TRACE);
a79913eb
FC
366 }
367
368 // Scan the candidate events and identify the "next" trace to read from
369 int trace = TmfExperimentContext.NO_TRACE;
a4115405 370 ITmfTimestamp timestamp = TmfTimestamp.BIG_CRUNCH;
0316808c 371 for (int i = 0; i < fTraces.length; i++) {
cbdacf03 372 final ITmfEvent event = expContext.getEvents()[i];
a79913eb 373 if (event != null && event.getTimestamp() != null) {
cbdacf03 374 final ITmfTimestamp otherTS = event.getTimestamp();
a79913eb
FC
375 if (otherTS.compareTo(timestamp, true) < 0) {
376 trace = i;
377 timestamp = otherTS;
378 }
379 }
380 }
a87cc4ef
FC
381
382 T event = null;
07671572 383 if (trace != TmfExperimentContext.NO_TRACE) {
a87cc4ef 384 event = (T) expContext.getEvents()[trace];
408e65d2
FC
385 if (event != null) {
386 updateAttributes(expContext, event.getTimestamp());
387 TmfExperimentLocation location = (TmfExperimentLocation) expContext.getLocation();
388 location.getLocation().getLocations()[trace] = expContext.getContexts()[trace].getLocation();
389 expContext.increaseRank();
390 expContext.setLastTrace(trace);
391 fExperimentContext = expContext;
392 processEvent(event);
393 }
07671572 394 }
a87cc4ef 395
a87cc4ef 396 return event;
a79913eb
FC
397 }
398
bcbea6a6 399 /* (non-Javadoc)
a79913eb
FC
400 * @see java.lang.Object#toString()
401 */
402 @Override
3b38ea61 403 @SuppressWarnings("nls")
a79913eb
FC
404 public String toString() {
405 return "[TmfExperiment (" + getName() + ")]";
406 }
8c8bf09f
ASL
407
408 // ------------------------------------------------------------------------
9e0640dc 409 // Streaming support
8c8bf09f
ASL
410 // ------------------------------------------------------------------------
411
1b70b6dc 412 private synchronized void initializeStreamingMonitor() {
9e0640dc
FC
413
414 if (fInitialized) {
828e5592 415 return;
9e0640dc 416 }
828e5592
PT
417 fInitialized = true;
418
1b70b6dc 419 if (getStreamingInterval() == 0) {
0316808c 420 final ITmfContext context = seekEvent(0);
cbdacf03
FC
421 final ITmfEvent event = getNext(context);
422 if (event == null)
1b70b6dc 423 return;
cbdacf03 424 final TmfTimeRange timeRange = new TmfTimeRange(event.getTimestamp().clone(), TmfTimestamp.BIG_CRUNCH);
828e5592
PT
425 final TmfExperimentRangeUpdatedSignal signal = new TmfExperimentRangeUpdatedSignal(this, this, timeRange);
426
427 // Broadcast in separate thread to prevent deadlock
428 new Thread() {
429 @Override
430 public void run() {
431 broadcast(signal);
432 }
433 }.start();
1b70b6dc
PT
434 return;
435 }
436
9e0640dc 437 final Thread thread = new Thread("Streaming Monitor for experiment " + getName()) { //$NON-NLS-1$
bcbea6a6
FC
438 private ITmfTimestamp safeTimestamp = null;
439 private TmfTimeRange timeRange = null;
1b70b6dc
PT
440
441 @Override
442 public void run() {
443 while (!fExecutor.isShutdown()) {
9e0640dc 444 if (!getIndexer().isIndexing()) {
a4115405
FC
445 ITmfTimestamp startTimestamp = TmfTimestamp.BIG_CRUNCH;
446 ITmfTimestamp endTimestamp = TmfTimestamp.BIG_BANG;
cbdacf03
FC
447 for (final ITmfTrace<T> trace : fTraces) {
448 if (trace.getStartTime().compareTo(startTimestamp) < 0)
1b70b6dc 449 startTimestamp = trace.getStartTime();
cbdacf03 450 if (trace.getStreamingInterval() != 0 && trace.getEndTime().compareTo(endTimestamp) > 0)
1b70b6dc 451 endTimestamp = trace.getEndTime();
1b70b6dc 452 }
cbdacf03 453 if (safeTimestamp != null && safeTimestamp.compareTo(getTimeRange().getEndTime(), false) > 0)
1b70b6dc 454 timeRange = new TmfTimeRange(startTimestamp, safeTimestamp);
cbdacf03 455 else
1b70b6dc 456 timeRange = null;
1b70b6dc
PT
457 safeTimestamp = endTimestamp;
458 if (timeRange != null) {
cbdacf03 459 final TmfExperimentRangeUpdatedSignal signal =
1b70b6dc
PT
460 new TmfExperimentRangeUpdatedSignal(TmfExperiment.this, TmfExperiment.this, timeRange);
461 broadcast(signal);
462 }
463 }
464 try {
465 Thread.sleep(getStreamingInterval());
cbdacf03 466 } catch (final InterruptedException e) {
1b70b6dc
PT
467 e.printStackTrace();
468 }
469 }
470 }
471 };
472 thread.start();
473 }
474
9e0640dc 475 /* (non-Javadoc)
1b70b6dc
PT
476 * @see org.eclipse.linuxtools.tmf.trace.ITmfTrace#getStreamingInterval()
477 */
478 @Override
479 public long getStreamingInterval() {
480 long interval = 0;
cbdacf03 481 for (final ITmfTrace<T> trace : fTraces)
1b70b6dc 482 interval = Math.max(interval, trace.getStreamingInterval());
1b70b6dc
PT
483 return interval;
484 }
485
8c8bf09f
ASL
486 // ------------------------------------------------------------------------
487 // Signal handlers
488 // ------------------------------------------------------------------------
489
9e0640dc 490 private Integer fEndSynchReference;
c32744d6 491
9e0640dc
FC
492 /**
493 * Signal handler for the TmfExperimentSelectedSignal signal
494 *
495 * @param signal
496 */
8c8bf09f 497 @TmfSignalHandler
cbdacf03
FC
498 public void experimentSelected(final TmfExperimentSelectedSignal<T> signal) {
499 final TmfExperiment<?> experiment = signal.getExperiment();
a79913eb
FC
500 if (experiment == this) {
501 setCurrentExperiment(experiment);
6e85c58d 502 fEndSynchReference = Integer.valueOf(signal.getReference());
a79913eb 503 }
8c8bf09f
ASL
504 }
505
9e0640dc
FC
506 /**
507 * Signal handler for the TmfEndSynchSignal signal
508 *
509 * @param signal
510 */
1b70b6dc 511 @TmfSignalHandler
cbdacf03 512 public void endSync(final TmfEndSynchSignal signal) {
1b70b6dc
PT
513 if (fEndSynchReference != null && fEndSynchReference.intValue() == signal.getReference()) {
514 fEndSynchReference = null;
515 initializeStreamingMonitor();
516 }
1b70b6dc
PT
517 }
518
828e5592 519 /**
9e0640dc 520 * Signal handler for the TmfTraceUpdatedSignal signal
cbdacf03 521 *
9e0640dc 522 * @param signal
828e5592 523 */
9e0640dc
FC
524 @TmfSignalHandler
525 public void traceUpdated(final TmfTraceUpdatedSignal signal) {
526 if (signal.getTrace() == this) {
527 broadcast(new TmfExperimentUpdatedSignal(this, this));
528 }
a1091415
PT
529 }
530
531 /**
9e0640dc 532 * Signal handler for the TmfExperimentRangeUpdatedSignal signal
cbdacf03 533 *
9e0640dc 534 * @param signal
a1091415 535 */
9e0640dc
FC
536 @TmfSignalHandler
537 public void experimentRangeUpdated(final TmfExperimentRangeUpdatedSignal signal) {
538 if (signal.getExperiment() == this) {
539 getIndexer().buildIndex(getNbEvents(), signal.getRange(), false);
540 }
a1091415
PT
541 }
542
4dc47e28 543}
This page took 0.080861 seconds and 5 git commands to generate.