723b958b51cb33ced1f458ffef1ba85c96bb5bdd
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / internal / tmf / core / synchronization / SyncAlgorithmFullyIncremental.java
1 /*******************************************************************************
2 * Copyright (c) 2013, 2014 École Polytechnique de Montréal
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:
10 * Geneviève Bastien - Initial implementation and API
11 * Francis Giraldeau - Transform computation using synchronization graph
12 *******************************************************************************/
13
14 package org.eclipse.tracecompass.internal.tmf.core.synchronization;
15
16 import java.io.IOException;
17 import java.io.ObjectInputStream;
18 import java.io.Serializable;
19 import java.math.BigDecimal;
20 import java.math.MathContext;
21 import java.util.Collection;
22 import java.util.LinkedHashMap;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.eclipse.tracecompass.common.core.NonNullUtils;
28 import org.eclipse.tracecompass.internal.tmf.core.synchronization.graph.SyncSpanningTree;
29 import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
30 import org.eclipse.tracecompass.tmf.core.event.matching.TmfEventDependency;
31 import org.eclipse.tracecompass.tmf.core.synchronization.ITmfTimestampTransform;
32 import org.eclipse.tracecompass.tmf.core.synchronization.Messages;
33 import org.eclipse.tracecompass.tmf.core.synchronization.SynchronizationAlgorithm;
34 import org.eclipse.tracecompass.tmf.core.synchronization.TimestampTransformFactory;
35 import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
36 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
37
38 /**
39 * Class implementing fully incremental trace synchronization approach as
40 * described in
41 *
42 * Masoume Jabbarifar, Michel Dagenais and Alireza Shameli-Sendi,
43 * "Streaming Mode Incremental Clock Synchronization"
44 *
45 * Since the algorithm itself applies to two traces, it is implemented in a
46 * private class, while this public class manages the synchronization between
47 * all traces.
48 *
49 * @author Geneviève Bastien
50 */
51 public class SyncAlgorithmFullyIncremental extends SynchronizationAlgorithm {
52
53 /**
54 * Auto-generated serial UID
55 */
56 private static final long serialVersionUID = -1782788842774838830L;
57
58 private static final MathContext fMc = MathContext.DECIMAL128;
59
60 /** @Serial */
61 private final List<ConvexHull> fSyncs;
62
63 private transient SyncSpanningTree fTree = null;
64
65 /**
66 * Initialization of the attributes
67 */
68 public SyncAlgorithmFullyIncremental() {
69 fSyncs = new LinkedList<>();
70 }
71
72 /**
73 * Function called after all matching has been done, to do any post-match
74 * treatment. For this class, it calculates stats, while the data is
75 * available
76 */
77 @Override
78 public void matchingEnded() {
79 getStats();
80 }
81
82 @Override
83 public void init(Collection<ITmfTrace> traces) {
84 ITmfTrace[] traceArr = traces.toArray(new ITmfTrace[traces.size()]);
85 fSyncs.clear();
86 /* Create a convex hull for all trace pairs */
87 // FIXME: is it necessary to make ConvexHull for every pairs up-front?
88 // The ConvexHull seems to be created on the fly in processMatch().
89 for (int i = 0; i < traceArr.length; i++) {
90 for (int j = i + 1; j < traceArr.length; j++) {
91 if (!traceArr[i].getHostId().equals(traceArr[j].getHostId())) {
92 ConvexHull algo = new ConvexHull(traceArr[i].getHostId(), traceArr[j].getHostId());
93 fSyncs.add(algo);
94 }
95 }
96 }
97 }
98
99 @Override
100 protected void processMatch(TmfEventDependency match) {
101 String host1 = match.getSourceEvent().getTrace().getHostId();
102 String host2 = match.getDestinationEvent().getTrace().getHostId();
103
104 /* Process only if source and destination are different */
105 if (host1.equals(host2)) {
106 return;
107 }
108
109 /* Check if a convex hull algorithm already exists for these 2 hosts */
110 ConvexHull algo = null;
111 for (ConvexHull traceSync : fSyncs) {
112 if (traceSync.isForHosts(host1, host2)) {
113 algo = traceSync;
114 }
115 }
116 if (algo == null) {
117 algo = new ConvexHull(host1, host2);
118 fSyncs.add(algo);
119 }
120 algo.processMatch(match);
121 invalidateSyncGraph();
122 }
123
124 private void invalidateSyncGraph() {
125 fTree = null;
126 }
127
128 @Override
129 public ITmfTimestampTransform getTimestampTransform(ITmfTrace trace) {
130 return getTimestampTransform(trace.getHostId());
131 }
132
133 @Override
134 public ITmfTimestampTransform getTimestampTransform(String hostId) {
135 SyncSpanningTree tree = getSyncTree();
136 return tree.getTimestampTransform(hostId);
137 }
138
139 /**
140 * Each convex hull computes the synchronization between 2 given hosts. A
141 * synchronization can be done on multiple hosts that may not all
142 * communicate with each other. We must use another algorithm to determine
143 * which host will be the reference node and what synchronization formula
144 * will be used between each host and this reference node.
145 *
146 * For example, take traces a, b and c where a and c talk to b but do not
147 * know each other ({@literal a <-> b <-> c}). The convex hulls will contain
148 * the formulae between their 2 traces, but if a is the reference node, then
149 * the resulting formula of c would be the composition of {@literal a <-> b}
150 * and {@literal b <-> c}
151 *
152 * @return The synchronization spanning tree for this synchronization
153 */
154 private SyncSpanningTree getSyncTree() {
155 if (fTree == null) {
156 fTree = new SyncSpanningTree(getRootNode());
157 for (ConvexHull traceSync : fSyncs) {
158 SyncQuality q = traceSync.getQuality();
159 if (q == SyncQuality.ACCURATE || q == SyncQuality.APPROXIMATE) {
160 String from = traceSync.getReferenceHost();
161 String to = traceSync.getOtherHost();
162 fTree.addSynchronization(from, to, traceSync.getTimestampTransform(to), traceSync.getAccuracy());
163 }
164 }
165 }
166 return fTree;
167 }
168
169 @Override
170 public SyncQuality getSynchronizationQuality(ITmfTrace trace1, ITmfTrace trace2) {
171 for (ConvexHull traceSync : fSyncs) {
172 if (traceSync.isForHosts(trace1.getHostId(), trace2.getHostId())) {
173 return traceSync.getQuality();
174 }
175 }
176 return SyncQuality.ABSENT;
177 }
178
179 @Override
180 public boolean isTraceSynced(String hostId) {
181 ITmfTimestampTransform t = getTimestampTransform(hostId);
182 return !t.equals(TimestampTransformFactory.getDefaultTransform());
183 }
184
185 @Override
186 public Map<String, Map<String, Object>> getStats() {
187 /*
188 * TODO: Stats, while still accurate, may be misleading now that the
189 * sync tree changes synchronization formula. The stats should use the
190 * tree instead
191 */
192 Map<String, Map<String, Object>> statmap = new LinkedHashMap<>();
193 for (ConvexHull traceSync : fSyncs) {
194 statmap.put(traceSync.getReferenceHost() + " <==> " + traceSync.getOtherHost(), traceSync.getStats()); //$NON-NLS-1$
195 }
196 return statmap;
197 }
198
199 @Override
200 public String toString() {
201 return getClass().getSimpleName() + ' ' + fSyncs.toString();
202 }
203
204 /**
205 * This is the actual synchronization algorithm between two traces using
206 * convex hull
207 */
208 private class ConvexHull implements Serializable {
209
210 private static final long serialVersionUID = 8309351175030935291L;
211
212 private final String fReferenceHost;
213 private final String fOtherHost;
214
215 /**
216 * Slopes and ordinate at origin of respectively fLmin, fLmax and the
217 * bisector
218 */
219 private BigDecimal fAlphamin, fBetamax, fAlphamax, fBetamin, fAlpha, fBeta;
220 private int fNbMatches, fNbAccurateMatches;
221 private SyncQuality fQuality;
222
223 /**
224 * The list of meaningful points on the upper hull (received by the
225 * reference trace, below in a graph)
226 */
227 private transient LinkedList<SyncPoint> fUpperBoundList = new LinkedList<>();
228 /**
229 * The list of meaninful points on the lower hull (sent by the reference
230 * trace, above in a graph)
231 */
232 private transient LinkedList<SyncPoint> fLowerBoundList = new LinkedList<>();
233
234 /** Points forming the line with maximum slope */
235 private transient SyncPoint[] fLmax = new SyncPoint[2];
236 /** Points forming the line with minimum slope */
237 private transient SyncPoint[] fLmin = new SyncPoint[2];
238
239 private transient Map<String, Object> fStats = new LinkedHashMap<>();
240
241 /**
242 * Initialization of the attributes
243 *
244 * @param host1
245 * ID of the first host
246 * @param host2
247 * ID of the second host
248 */
249 public ConvexHull(String host1, String host2) {
250 if (host1.compareTo(host2) > 0) {
251 fReferenceHost = host2;
252 fOtherHost = host1;
253 } else {
254 fReferenceHost = host1;
255 fOtherHost = host2;
256 }
257 fAlpha = BigDecimal.ONE;
258 fAlphamax = BigDecimal.ONE;
259 fAlphamin = BigDecimal.ONE;
260 fBeta = BigDecimal.ZERO;
261 fBetamax = BigDecimal.ZERO;
262 fBetamin = BigDecimal.ZERO;
263 fNbMatches = 0;
264 fNbAccurateMatches = 0;
265 fQuality = SyncQuality.ABSENT; // default quality
266 }
267
268 protected void processMatch(TmfEventDependency match) {
269
270 LinkedList<SyncPoint> boundList, otherBoundList;
271
272 SyncPoint[] line, otherLine;
273 SyncPoint p;
274 int inversionFactor = 1;
275 boolean qualify = false;
276 fNbMatches++;
277
278 /* Initialize data depending on the which hull the match is part of */
279 if (match.getSourceEvent().getTrace().getHostId().compareTo(match.getDestinationEvent().getTrace().getHostId()) > 0) {
280 boundList = fUpperBoundList;
281 otherBoundList = fLowerBoundList;
282 line = fLmin;
283 otherLine = fLmax;
284 p = new SyncPoint(match.getDestinationEvent(), match.getSourceEvent());
285 inversionFactor = 1;
286 } else {
287 boundList = fLowerBoundList;
288 otherBoundList = fUpperBoundList;
289 line = fLmax;
290 otherLine = fLmin;
291 p = new SyncPoint(match.getSourceEvent(), match.getDestinationEvent());
292 inversionFactor = -1;
293 }
294
295 /*
296 * Does the message qualify for the hull, or is in on the wrong side
297 * of the reference line
298 */
299 if ((line[0] == null) || (line[1] == null) || (p.crossProduct(line[0], line[1]) * inversionFactor > 0)) {
300 /*
301 * If message qualifies, verify if points need to be removed
302 * from the hull and add the new point as the maximum reference
303 * point for the line. Also clear the stats that are not good
304 * anymore
305 */
306 fNbAccurateMatches++;
307 qualify = true;
308 removeUselessPoints(p, boundList, inversionFactor);
309 line[1] = p;
310 fStats.clear();
311 }
312
313 /*
314 * Adjust the boundary of the reference line and if one of the
315 * reference point of the other line was removed from the hull, also
316 * adjust the other line
317 */
318 adjustBound(line, otherBoundList, inversionFactor);
319 if ((otherLine[1] != null) && !boundList.contains(otherLine[0])) {
320 adjustBound(otherLine, boundList, inversionFactor * -1);
321 }
322
323 if (qualify) {
324 approximateSync();
325 }
326
327 }
328
329 /**
330 * Calculates slopes and ordinate at origin of fLmax and fLmin to obtain
331 * and approximation of the synchronization at this time
332 */
333 private void approximateSync() {
334
335 /**
336 * Line slopes functions
337 *
338 * Lmax = alpha_max T + beta_min
339 *
340 * Lmin = alpha_min T + beta_max
341 */
342 if ((fLmax[0] != null) || (fLmin[0] != null)) {
343 /**
344 * Do not recalculate synchronization after it is failed. We
345 * keep the last not failed result.
346 */
347 if (getQuality() != SyncQuality.FAIL) {
348 BigDecimal alphamax = fLmax[1].getAlpha(fLmax[0]);
349 BigDecimal alphamin = fLmin[1].getAlpha(fLmin[0]);
350 SyncQuality quality = null;
351
352 if ((fLmax[0] == null) || (fLmin[0] == null)) {
353 quality = SyncQuality.APPROXIMATE;
354 }
355 else if (alphamax.compareTo(alphamin) > 0) {
356 quality = SyncQuality.ACCURATE;
357 } else {
358 /* Lines intersect, not good */
359 quality = SyncQuality.FAIL;
360 }
361 /*
362 * Only calculate sync if this match does not cause failure
363 * of synchronization
364 */
365 if (quality != SyncQuality.FAIL) {
366 fAlphamax = alphamax;
367 fBetamin = fLmax[1].getBeta(fAlphamax);
368 fAlphamin = alphamin;
369 fBetamax = fLmin[1].getBeta(fAlphamin);
370 fAlpha = fAlphamax.add(fAlphamin).divide(BigDecimal.valueOf(2), fMc);
371 fBeta = fBetamin.add(fBetamax).divide(BigDecimal.valueOf(2), fMc);
372 }
373 setQuality(quality);
374 }
375 } else if (((fLmax[0] == null) && (fLmin[1] == null))
376 || ((fLmax[1] == null) && (fLmin[0] == null))) {
377 /* Either there is no upper hull point or no lower hull */
378 setQuality(SyncQuality.INCOMPLETE);
379 }
380 }
381
382 /*
383 * Verify if the line should be adjusted to be more accurate give the
384 * hull
385 */
386 private void adjustBound(SyncPoint[] line, LinkedList<SyncPoint> otherBoundList, int inversionFactor) {
387 SyncPoint minPoint = null, nextPoint;
388 boolean finishedSearch = false;
389
390 /*
391 * Find in the other bound, the origin point of the line, start from
392 * the beginning if the point was lost
393 */
394 int i = Math.max(0, otherBoundList.indexOf(line[0]));
395
396 while ((i < otherBoundList.size() - 1) && !finishedSearch) {
397 minPoint = otherBoundList.get(i);
398 nextPoint = otherBoundList.get(i + 1);
399
400 /*
401 * If the rotation (cross-product) is not optimal, move to next
402 * point as reference for the line (if available)
403 *
404 * Otherwise, the current minPoint is the minPoint of the line
405 */
406 if (minPoint.crossProduct(nextPoint, line[1]) * inversionFactor > 0) {
407 if (nextPoint.getTimeX() < line[1].getTimeX()) {
408 i++;
409 } else {
410 line[0] = null;
411 finishedSearch = true;
412 }
413 } else {
414 line[0] = minPoint;
415 finishedSearch = true;
416 }
417 }
418
419 if (line[0] == null) {
420 line[0] = minPoint;
421 }
422
423 /* Make sure point 0 is before point 1 */
424 if ((line[0] != null) && (line[0].getTimeX() > line[1].getTimeX())) {
425 line[0] = null;
426 }
427 }
428
429 /*
430 * When a point qualifies to be in a hull, we verify if any of the
431 * existing points need to be removed from the hull
432 */
433 private void removeUselessPoints(final SyncPoint p, final LinkedList<SyncPoint> boundList, final int inversionFactor) {
434
435 boolean checkRemove = true;
436
437 while (checkRemove && boundList.size() >= 2) {
438 if (p.crossProduct(boundList.get(boundList.size() - 2), boundList.getLast()) * inversionFactor > 0) {
439 boundList.removeLast();
440 } else {
441 checkRemove = false;
442 }
443 }
444 boundList.addLast(p);
445 }
446
447 public ITmfTimestampTransform getTimestampTransform(String hostId) {
448 if (hostId.equals(fOtherHost) && (getQuality() == SyncQuality.ACCURATE || getQuality() == SyncQuality.APPROXIMATE || getQuality() == SyncQuality.FAIL)) {
449 /* alpha: beta => 1 / fAlpha, -1 * fBeta / fAlpha); */
450 return TimestampTransformFactory.createLinear(NonNullUtils.checkNotNull(BigDecimal.ONE.divide(fAlpha, fMc)), NonNullUtils.checkNotNull(BigDecimal.valueOf(-1).multiply(fBeta).divide(fAlpha, fMc)));
451 }
452 return TimestampTransformFactory.getDefaultTransform();
453 }
454
455 public SyncQuality getQuality() {
456 return fQuality;
457 }
458
459 public BigDecimal getAccuracy() {
460 return fAlphamax.subtract(fAlphamin);
461 }
462
463 public Map<String, Object> getStats() {
464 if (fStats.size() == 0) {
465 String syncQuality;
466 switch (getQuality()) {
467 case ABSENT:
468 syncQuality = Messages.SyncAlgorithmFullyIncremental_absent;
469 break;
470 case ACCURATE:
471 syncQuality = Messages.SyncAlgorithmFullyIncremental_accurate;
472 break;
473 case APPROXIMATE:
474 syncQuality = Messages.SyncAlgorithmFullyIncremental_approx;
475 break;
476 case INCOMPLETE:
477 syncQuality = Messages.SyncAlgorithmFullyIncremental_incomplete;
478 break;
479 case FAIL:
480 default:
481 syncQuality = Messages.SyncAlgorithmFullyIncremental_fail;
482 break;
483 }
484
485 fStats.put(Messages.SyncAlgorithmFullyIncremental_refhost, fReferenceHost);
486 fStats.put(Messages.SyncAlgorithmFullyIncremental_otherhost, fOtherHost);
487 fStats.put(Messages.SyncAlgorithmFullyIncremental_quality, syncQuality);
488 fStats.put(Messages.SyncAlgorithmFullyIncremental_alpha, fAlpha);
489 fStats.put(Messages.SyncAlgorithmFullyIncremental_beta, fBeta);
490 fStats.put(Messages.SyncAlgorithmFullyIncremental_ub, (fUpperBoundList.size() == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fUpperBoundList.size());
491 fStats.put(Messages.SyncAlgorithmFullyIncremental_lb, (fLowerBoundList.size() == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fLowerBoundList.size());
492 fStats.put(Messages.SyncAlgorithmFullyIncremental_accuracy, getAccuracy().doubleValue());
493 fStats.put(Messages.SyncAlgorithmFullyIncremental_nbmatch, (fNbMatches == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fNbMatches);
494 fStats.put(Messages.SyncAlgorithmFullyIncremental_nbacc, (fNbAccurateMatches == 0) ? Messages.SyncAlgorithmFullyIncremental_NA : fNbAccurateMatches);
495 fStats.put(Messages.SyncAlgorithmFullyIncremental_refformula, Messages.SyncAlgorithmFullyIncremental_T_ + fReferenceHost);
496 fStats.put(Messages.SyncAlgorithmFullyIncremental_otherformula, fAlpha + Messages.SyncAlgorithmFullyIncremental_mult + Messages.SyncAlgorithmFullyIncremental_T_ + fReferenceHost + Messages.SyncAlgorithmFullyIncremental_add + fBeta);
497 }
498 return fStats;
499
500 }
501
502 public String getReferenceHost() {
503 return fReferenceHost;
504 }
505
506 public String getOtherHost() {
507 return fOtherHost;
508 }
509
510 public boolean isForHosts(String hostId1, String hostId2) {
511 return ((fReferenceHost.equals(hostId1) && fOtherHost.equals(hostId2)) || (fReferenceHost.equals(hostId2) && fOtherHost.equals(hostId1)));
512 }
513
514 private void readObject(ObjectInputStream stream)
515 throws IOException, ClassNotFoundException {
516 stream.defaultReadObject();
517
518 /* Initialize transient fields */
519 fUpperBoundList = new LinkedList<>();
520 fLowerBoundList = new LinkedList<>();
521 fLmax = new SyncPoint[2];
522 fLmin = new SyncPoint[2];
523 fStats = new LinkedHashMap<>();
524 }
525
526 @SuppressWarnings("nls")
527 @Override
528 public String toString() {
529 StringBuilder b = new StringBuilder();
530 b.append("Between " + fReferenceHost + " and " + fOtherHost + " [");
531 b.append(" alpha " + fAlpha + " beta " + fBeta + " ]");
532 return b.toString();
533 }
534
535 private void setQuality(SyncQuality fQuality) {
536 this.fQuality = fQuality;
537 }
538
539 }
540
541 /**
542 * Private class representing a point to synchronize on a graph. The x axis
543 * is the timestamp of the event from the reference trace while the y axis
544 * is the timestamp of the event on the other trace
545 */
546 private class SyncPoint {
547 private final ITmfTimestamp x, y;
548
549 public SyncPoint(ITmfEvent ex, ITmfEvent ey) {
550 x = ex.getTimestamp();
551 y = ey.getTimestamp();
552 }
553
554 public long getTimeX() {
555 return x.getValue();
556 }
557
558 /**
559 * Calculate a cross product of 3 points:
560 *
561 * If the cross-product < 0, then p, pa, pb are clockwise
562 *
563 * If the cross-product > 0, then p, pa, pb are counter-clockwise
564 *
565 * If cross-product == 0, then they are in a line
566 *
567 * @param pa
568 * First point
569 * @param pb
570 * Second point
571 * @return The cross product
572 */
573 public long crossProduct(SyncPoint pa, SyncPoint pb) {
574 long cp = ((pa.x.getValue() - x.getValue()) * (pb.y.getValue() - y.getValue()) - (pa.y.getValue() - y.getValue()) * (pb.x.getValue() - x.getValue()));
575 return cp;
576 }
577
578 /*
579 * Gets the alpha (slope) between two points
580 */
581 public BigDecimal getAlpha(SyncPoint p1) {
582 if (p1 == null) {
583 return BigDecimal.ONE;
584 }
585 BigDecimal deltay = BigDecimal.valueOf(y.getValue() - p1.y.getValue());
586 BigDecimal deltax = BigDecimal.valueOf(x.getValue() - p1.x.getValue());
587 if (deltax.equals(BigDecimal.ZERO)) {
588 return BigDecimal.ONE;
589 }
590 return deltay.divide(deltax, fMc);
591 }
592
593 /*
594 * Get the beta value (when x = 0) of the line given alpha
595 */
596 public BigDecimal getBeta(BigDecimal alpha) {
597 return BigDecimal.valueOf(y.getValue()).subtract(alpha.multiply(BigDecimal.valueOf(x.getValue()), fMc));
598 }
599
600 @Override
601 public String toString() {
602 return String.format("%s (%s, %s)", this.getClass().getCanonicalName(), x, y); //$NON-NLS-1$
603 }
604 }
605
606 }
This page took 0.046274 seconds and 4 git commands to generate.