1 /*******************************************************************************
2 * Copyright (c) 2013 École Polytechnique de Montréal
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
10 * Geneviève Bastien - Initial implementation and API
11 * Francis Giraldeau - Transform computation using synchronization graph
12 *******************************************************************************/
14 package org
.eclipse
.linuxtools
.tmf
.core
.synchronization
;
16 import java
.io
.IOException
;
17 import java
.io
.ObjectOutputStream
;
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
;
27 import org
.eclipse
.linuxtools
.internal
.tmf
.core
.synchronization
.graph
.SyncSpanningTree
;
28 import org
.eclipse
.linuxtools
.tmf
.core
.event
.ITmfEvent
;
29 import org
.eclipse
.linuxtools
.tmf
.core
.event
.matching
.TmfEventDependency
;
30 import org
.eclipse
.linuxtools
.tmf
.core
.synchronization
.ITmfTimestampTransform
;
31 import org
.eclipse
.linuxtools
.tmf
.core
.synchronization
.Messages
;
32 import org
.eclipse
.linuxtools
.tmf
.core
.synchronization
.SynchronizationAlgorithm
;
33 import org
.eclipse
.linuxtools
.tmf
.core
.synchronization
.TimestampTransformFactory
;
34 import org
.eclipse
.linuxtools
.tmf
.core
.timestamp
.ITmfTimestamp
;
35 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.ITmfTrace
;
38 * Class implementing fully incremental trace synchronization approach as
41 * Masoume Jabbarifar, Michel Dagenais and Alireza Shameli-Sendi,
42 * "Streaming Mode Incremental Clock Synchronization"
44 * Since the algorithm itself applies to two traces, it is implemented in a
45 * private class, while this public class manages the synchronization between
48 * @author Geneviève Bastien
50 * @deprecated This class has been moved to internal. Use one of
51 * {@link SynchronizationAlgorithmFactory#getFullyIncrementalAlgorithm()}
52 * method to get this algorithm.
55 public class SyncAlgorithmFullyIncremental
extends SynchronizationAlgorithm
{
58 * Auto-generated serial UID
60 private static final long serialVersionUID
= -1782788842774838830L;
62 private static final MathContext fMc
= MathContext
.DECIMAL128
;
65 private final List
<ConvexHull
> fSyncs
;
67 private SyncSpanningTree fTree
= null;
70 * Initialization of the attributes
72 public SyncAlgorithmFullyIncremental() {
73 fSyncs
= new LinkedList
<>();
77 * Function called after all matching has been done, to do any post-match
78 * treatment. For this class, it calculates stats, while the data is
82 public void matchingEnded() {
87 public void init(Collection
<ITmfTrace
> traces
) {
88 ITmfTrace
[] traceArr
= traces
.toArray(new ITmfTrace
[traces
.size()]);
90 /* Create a convex hull for all trace pairs */
91 // FIXME: is it necessary to make ConvexHull for every pairs up-front?
92 // The ConvexHull seems to be created on the fly in processMatch().
93 for (int i
= 0; i
< traceArr
.length
; i
++) {
94 for (int j
= i
+ 1; j
< traceArr
.length
; j
++) {
95 ConvexHull algo
= new ConvexHull(traceArr
[i
].getHostId(), traceArr
[j
].getHostId());
102 protected void processMatch(TmfEventDependency match
) {
103 String host1
= match
.getSourceEvent().getTrace().getHostId();
104 String host2
= match
.getDestinationEvent().getTrace().getHostId();
106 /* Process only if source and destination are different */
107 if (host1
.equals(host2
)) {
111 /* Check if a convex hull algorithm already exists for these 2 hosts */
112 ConvexHull algo
= null;
113 for (ConvexHull traceSync
: fSyncs
) {
114 if (traceSync
.isForHosts(host1
, host2
)) {
119 algo
= new ConvexHull(host1
, host2
);
122 algo
.processMatch(match
);
123 invalidateSyncGraph();
126 private void invalidateSyncGraph() {
131 public ITmfTimestampTransform
getTimestampTransform(ITmfTrace trace
) {
132 return getTimestampTransform(trace
.getHostId());
136 public ITmfTimestampTransform
getTimestampTransform(String hostId
) {
137 SyncSpanningTree tree
= getSyncTree();
138 return tree
.getTimestampTransform(hostId
);
142 * Each convex hull computes the synchronization between 2 given hosts. A
143 * synchronization can be done on multiple hosts that may not all
144 * communicate with each other. We must use another algorithm to determine
145 * which host will be the reference node and what synchronization formula
146 * will be used between each host and this reference node.
148 * For example, take traces a, b and c where a and c talk to b but do not
149 * know each other ({@literal a <-> b <-> c}). The convex hulls will contain
150 * the formulae between their 2 traces, but if a is the reference node, then
151 * the resulting formula of c would be the composition of {@literal a <-> b}
152 * and {@literal b <-> c}
154 * @return The synchronization spanning tree for this synchronization
156 private SyncSpanningTree
getSyncTree() {
158 fTree
= new SyncSpanningTree();
159 for (ConvexHull traceSync
: fSyncs
) {
160 SyncQuality q
= traceSync
.getQuality();
161 if (q
== SyncQuality
.ACCURATE
|| q
== SyncQuality
.APPROXIMATE
) {
162 String from
= traceSync
.getReferenceHost();
163 String to
= traceSync
.getOtherHost();
164 fTree
.addSynchronization(from
, to
, traceSync
.getTimestampTransform(to
), traceSync
.getAccuracy());
172 public SyncQuality
getSynchronizationQuality(ITmfTrace trace1
, ITmfTrace trace2
) {
173 for (ConvexHull traceSync
: fSyncs
) {
174 if (traceSync
.isForHosts(trace1
.getHostId(), trace2
.getHostId())) {
175 return traceSync
.getQuality();
178 return SyncQuality
.ABSENT
;
182 public boolean isTraceSynced(String hostId
) {
183 ITmfTimestampTransform t
= getTimestampTransform(hostId
);
184 return !t
.equals(TimestampTransformFactory
.getDefaultTransform());
188 public Map
<String
, Map
<String
, Object
>> getStats() {
190 * TODO: Stats, while still accurate, may be misleading now that the
191 * sync tree changes synchronization formula. The stats should use the
194 Map
<String
, Map
<String
, Object
>> statmap
= new LinkedHashMap
<>();
195 for (ConvexHull traceSync
: fSyncs
) {
196 statmap
.put(traceSync
.getReferenceHost() + " <==> " + traceSync
.getOtherHost(), traceSync
.getStats()); //$NON-NLS-1$
202 public String
toString() {
203 StringBuilder b
= new StringBuilder();
204 b
.append(getClass().getSimpleName() + " "); //$NON-NLS-1$
210 * This is the actual synchronization algorithm between two traces using
213 private class ConvexHull
implements Serializable
{
215 private static final long serialVersionUID
= 8309351175030935291L;
218 * The list of meaningful points on the upper hull (received by the
219 * reference trace, below in a graph)
221 private final LinkedList
<SyncPoint
> fUpperBoundList
= new LinkedList
<>();
224 * The list of meaninful points on the lower hull (sent by the reference
225 * trace, above in a graph)
227 private final LinkedList
<SyncPoint
> fLowerBoundList
= new LinkedList
<>();
229 /** Points forming the line with maximum slope */
230 private final SyncPoint
[] fLmax
;
232 /** Points forming the line with minimum slope */
233 private final SyncPoint
[] fLmin
;
236 * Slopes and ordinate at origin of respectively fLmin, fLmax and the
239 private BigDecimal fAlphamin
, fBetamax
, fAlphamax
, fBetamin
, fAlpha
, fBeta
;
241 private int fNbMatches
, fNbAccurateMatches
;
242 private String fReferenceHost
= "", fOtherHost
= ""; //$NON-NLS-1$//$NON-NLS-2$
243 private SyncQuality fQuality
;
245 private Map
<String
, Object
> fStats
= new LinkedHashMap
<>();
248 * Initialization of the attributes
251 * ID of the first host
253 * ID of the second host
255 public ConvexHull(String host1
, String host2
) {
256 if (host1
.compareTo(host2
) > 0) {
257 fReferenceHost
= host2
;
260 fReferenceHost
= host1
;
263 fLmax
= new SyncPoint
[2];
264 fLmin
= new SyncPoint
[2];
265 fAlpha
= BigDecimal
.ONE
;
266 fAlphamax
= BigDecimal
.ONE
;
267 fAlphamin
= BigDecimal
.ONE
;
268 fBeta
= BigDecimal
.ZERO
;
269 fBetamax
= BigDecimal
.ZERO
;
270 fBetamin
= BigDecimal
.ZERO
;
272 fNbAccurateMatches
= 0;
273 fQuality
= SyncQuality
.ABSENT
; // default quality
276 protected void processMatch(TmfEventDependency match
) {
278 LinkedList
<SyncPoint
> boundList
, otherBoundList
;
280 SyncPoint
[] line
, otherLine
;
282 int inversionFactor
= 1;
283 boolean qualify
= false;
286 /* Initialize data depending on the which hull the match is part of */
287 if (match
.getSourceEvent().getTrace().getHostId().compareTo(match
.getDestinationEvent().getTrace().getHostId()) > 0) {
288 boundList
= fUpperBoundList
;
289 otherBoundList
= fLowerBoundList
;
292 p
= new SyncPoint(match
.getDestinationEvent(), match
.getSourceEvent());
295 boundList
= fLowerBoundList
;
296 otherBoundList
= fUpperBoundList
;
299 p
= new SyncPoint(match
.getSourceEvent(), match
.getDestinationEvent());
300 inversionFactor
= -1;
304 * Does the message qualify for the hull, or is in on the wrong side
305 * of the reference line
307 if ((line
[0] == null) || (line
[1] == null) || (p
.crossProduct(line
[0], line
[1]) * inversionFactor
> 0)) {
309 * If message qualifies, verify if points need to be removed
310 * from the hull and add the new point as the maximum reference
311 * point for the line. Also clear the stats that are not good
314 fNbAccurateMatches
++;
316 removeUselessPoints(p
, boundList
, inversionFactor
);
322 * Adjust the boundary of the reference line and if one of the
323 * reference point of the other line was removed from the hull, also
324 * adjust the other line
326 adjustBound(line
, otherBoundList
, inversionFactor
);
327 if ((otherLine
[1] != null) && !boundList
.contains(otherLine
[0])) {
328 adjustBound(otherLine
, boundList
, inversionFactor
* -1);
338 * Calculates slopes and ordinate at origin of fLmax and fLmin to obtain
339 * and approximation of the synchronization at this time
341 private void approximateSync() {
344 * Line slopes functions
346 * Lmax = alpha_max T + beta_min
348 * Lmin = alpha_min T + beta_max
350 if ((fLmax
[0] != null) || (fLmin
[0] != null)) {
352 * Do not recalculate synchronization after it is failed. We
353 * keep the last not failed result.
355 if (getQuality() != SyncQuality
.FAIL
) {
356 BigDecimal alphamax
= fLmax
[1].getAlpha(fLmax
[0]);
357 BigDecimal alphamin
= fLmin
[1].getAlpha(fLmin
[0]);
358 SyncQuality quality
= null;
360 if ((fLmax
[0] == null) || (fLmin
[0] == null)) {
361 quality
= SyncQuality
.APPROXIMATE
;
363 else if (alphamax
.compareTo(alphamin
) > 0) {
364 quality
= SyncQuality
.ACCURATE
;
366 /* Lines intersect, not good */
367 quality
= SyncQuality
.FAIL
;
370 * Only calculate sync if this match does not cause failure
373 if (quality
!= SyncQuality
.FAIL
) {
374 fAlphamax
= alphamax
;
375 fBetamin
= fLmax
[1].getBeta(fAlphamax
);
376 fAlphamin
= alphamin
;
377 fBetamax
= fLmin
[1].getBeta(fAlphamin
);
378 fAlpha
= fAlphamax
.add(fAlphamin
).divide(BigDecimal
.valueOf(2), fMc
);
379 fBeta
= fBetamin
.add(fBetamax
).divide(BigDecimal
.valueOf(2), fMc
);
383 } else if (((fLmax
[0] == null) && (fLmin
[1] == null))
384 || ((fLmax
[1] == null) && (fLmin
[0] == null))) {
385 /* Either there is no upper hull point or no lower hull */
386 setQuality(SyncQuality
.INCOMPLETE
);
391 * Verify if the line should be adjusted to be more accurate give the
394 private void adjustBound(SyncPoint
[] line
, LinkedList
<SyncPoint
> otherBoundList
, int inversionFactor
) {
395 SyncPoint minPoint
= null, nextPoint
;
396 boolean finishedSearch
= false;
399 * Find in the other bound, the origin point of the line, start from
400 * the beginning if the point was lost
402 int i
= Math
.max(0, otherBoundList
.indexOf(line
[0]));
404 while ((i
< otherBoundList
.size() - 1) && !finishedSearch
) {
405 minPoint
= otherBoundList
.get(i
);
406 nextPoint
= otherBoundList
.get(i
+ 1);
409 * If the rotation (cross-product) is not optimal, move to next
410 * point as reference for the line (if available)
412 * Otherwise, the current minPoint is the minPoint of the line
414 if (minPoint
.crossProduct(nextPoint
, line
[1]) * inversionFactor
> 0) {
415 if (nextPoint
.getTimeX() < line
[1].getTimeX()) {
419 finishedSearch
= true;
423 finishedSearch
= true;
427 if (line
[0] == null) {
431 /* Make sure point 0 is before point 1 */
432 if ((line
[0] != null) && (line
[0].getTimeX() > line
[1].getTimeX())) {
438 * When a point qualifies to be in a hull, we verify if any of the
439 * existing points need to be removed from the hull
441 private void removeUselessPoints(final SyncPoint p
, final LinkedList
<SyncPoint
> boundList
, final int inversionFactor
) {
443 boolean checkRemove
= true;
445 while (checkRemove
&& boundList
.size() >= 2) {
446 if (p
.crossProduct(boundList
.get(boundList
.size() - 2), boundList
.getLast()) * inversionFactor
> 0) {
447 boundList
.removeLast();
452 boundList
.addLast(p
);
455 public ITmfTimestampTransform
getTimestampTransform(String hostId
) {
456 if (hostId
.equals(fOtherHost
) && (getQuality() == SyncQuality
.ACCURATE
|| getQuality() == SyncQuality
.APPROXIMATE
|| getQuality() == SyncQuality
.FAIL
)) {
457 /* alpha: beta => 1 / fAlpha, -1 * fBeta / fAlpha); */
458 return TimestampTransformFactory
.createLinear(BigDecimal
.ONE
.divide(fAlpha
, fMc
), BigDecimal
.valueOf(-1).multiply(fBeta
).divide(fAlpha
, fMc
));
460 return TimestampTransformFactory
.getDefaultTransform();
463 public SyncQuality
getQuality() {
467 public BigDecimal
getAccuracy() {
468 return fAlphamax
.subtract(fAlphamin
);
471 public Map
<String
, Object
> getStats() {
472 if (fStats
.size() == 0) {
474 switch (getQuality()) {
476 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_absent
;
479 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_accurate
;
482 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_approx
;
485 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_incomplete
;
489 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_fail
;
493 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_refhost
, fReferenceHost
);
494 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_otherhost
, fOtherHost
);
495 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_quality
, syncQuality
);
496 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_alpha
, fAlpha
);
497 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_beta
, fBeta
);
498 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_ub
, (fUpperBoundList
.size() == 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fUpperBoundList
.size());
499 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_lb
, (fLowerBoundList
.size() == 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fLowerBoundList
.size());
500 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_accuracy
, getAccuracy().doubleValue());
501 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_nbmatch
, (fNbMatches
== 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fNbMatches
);
502 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_nbacc
, (fNbAccurateMatches
== 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fNbAccurateMatches
);
503 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_refformula
, Messages
.SyncAlgorithmFullyIncremental_T_
+ fReferenceHost
);
504 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_otherformula
, fAlpha
+ Messages
.SyncAlgorithmFullyIncremental_mult
+ Messages
.SyncAlgorithmFullyIncremental_T_
+ fReferenceHost
+ Messages
.SyncAlgorithmFullyIncremental_add
+ fBeta
);
510 public String
getReferenceHost() {
511 return fReferenceHost
;
514 public String
getOtherHost() {
518 public boolean isForHosts(String hostId1
, String hostId2
) {
519 return ((fReferenceHost
.equals(hostId1
) && fOtherHost
.equals(hostId2
)) || (fReferenceHost
.equals(hostId2
) && fOtherHost
.equals(hostId1
)));
522 private void writeObject(ObjectOutputStream s
)
525 * Remove calculation data because most of it is not serializable.
526 * We have the statistics anyway
528 fUpperBoundList
.clear();
529 fLowerBoundList
.clear();
534 s
.defaultWriteObject();
537 @SuppressWarnings("nls")
539 public String
toString() {
540 StringBuilder b
= new StringBuilder();
541 b
.append("Between " + fReferenceHost
+ " and " + fOtherHost
+ " [");
542 b
.append(" alpha " + fAlpha
+ " beta " + fBeta
+ " ]");
546 private void setQuality(SyncQuality fQuality
) {
547 this.fQuality
= fQuality
;
553 * Private class representing a point to synchronize on a graph. The x axis
554 * is the timestamp of the event from the reference trace while the y axis
555 * is the timestamp of the event on the other trace
557 private class SyncPoint
{
558 private final ITmfTimestamp x
, y
;
560 public SyncPoint(ITmfEvent ex
, ITmfEvent ey
) {
561 x
= ex
.getTimestamp();
562 y
= ey
.getTimestamp();
565 public long getTimeX() {
570 * Calculate a cross product of 3 points:
572 * If the cross-product < 0, then p, pa, pb are clockwise
574 * If the cross-product > 0, then p, pa, pb are counter-clockwise
576 * If cross-product == 0, then they are in a line
582 * @return The cross product
584 public long crossProduct(SyncPoint pa
, SyncPoint pb
) {
585 long cp
= ((pa
.x
.getValue() - x
.getValue()) * (pb
.y
.getValue() - y
.getValue()) - (pa
.y
.getValue() - y
.getValue()) * (pb
.x
.getValue() - x
.getValue()));
590 * Gets the alpha (slope) between two points
592 public BigDecimal
getAlpha(SyncPoint p1
) {
594 return BigDecimal
.ONE
;
596 BigDecimal deltay
= BigDecimal
.valueOf(y
.getValue() - p1
.y
.getValue());
597 BigDecimal deltax
= BigDecimal
.valueOf(x
.getValue() - p1
.x
.getValue());
598 if (deltax
.equals(BigDecimal
.ZERO
)) {
599 return BigDecimal
.ONE
;
601 return deltay
.divide(deltax
, fMc
);
605 * Get the beta value (when x = 0) of the line given alpha
607 public BigDecimal
getBeta(BigDecimal alpha
) {
608 return BigDecimal
.valueOf(y
.getValue()).subtract(alpha
.multiply(BigDecimal
.valueOf(x
.getValue()), fMc
));
612 public String
toString() {
613 return String
.format("%s (%s, %s)", this.getClass().getCanonicalName(), x
, y
); //$NON-NLS-1$
617 private void writeObject(ObjectOutputStream s
)
620 * Remove the tree because it is not serializable
623 s
.defaultWriteObject();