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
.timestamp
.ITmfTimestamp
;
31 import org
.eclipse
.linuxtools
.tmf
.core
.trace
.ITmfTrace
;
34 * Class implementing fully incremental trace synchronization approach as
37 * Masoume Jabbarifar, Michel Dagenais and Alireza Shameli-Sendi,
38 * "Streaming Mode Incremental Clock Synchronization"
40 * Since the algorithm itself applies to two traces, it is implemented in a
41 * private class, while this public class manages the synchronization between
44 * @author Geneviève Bastien
47 public class SyncAlgorithmFullyIncremental
extends SynchronizationAlgorithm
{
50 * Auto-generated serial UID
52 private static final long serialVersionUID
= -1782788842774838830L;
54 private static final MathContext fMc
= MathContext
.DECIMAL128
;
57 private final List
<ConvexHull
> fSyncs
;
59 private SyncSpanningTree fTree
= null;
62 * Initialization of the attributes
64 public SyncAlgorithmFullyIncremental() {
65 fSyncs
= new LinkedList
<>();
69 * Function called after all matching has been done, to do any post-match
70 * treatment. For this class, it calculates stats, while the data is
74 public void matchingEnded() {
79 public void init(Collection
<ITmfTrace
> traces
) {
80 ITmfTrace
[] traceArr
= traces
.toArray(new ITmfTrace
[traces
.size()]);
82 /* Create a convex hull for all trace pairs */
83 // FIXME: is it necessary to make ConvexHull for every pairs up-front?
84 // The ConvexHull seems to be created on the fly in processMatch().
85 for (int i
= 0; i
< traceArr
.length
; i
++) {
86 for (int j
= i
+ 1; j
< traceArr
.length
; j
++) {
87 ConvexHull algo
= new ConvexHull(traceArr
[i
].getHostId(), traceArr
[j
].getHostId());
94 protected void processMatch(TmfEventDependency match
) {
95 String host1
= match
.getSourceEvent().getTrace().getHostId();
96 String host2
= match
.getDestinationEvent().getTrace().getHostId();
98 /* Process only if source and destination are different */
99 if (host1
.equals(host2
)) {
103 /* Check if a convex hull algorithm already exists for these 2 hosts */
104 ConvexHull algo
= null;
105 for (ConvexHull traceSync
: fSyncs
) {
106 if (traceSync
.isForHosts(host1
, host2
)) {
111 algo
= new ConvexHull(host1
, host2
);
114 algo
.processMatch(match
);
115 invalidateSyncGraph();
118 private void invalidateSyncGraph() {
123 public ITmfTimestampTransform
getTimestampTransform(ITmfTrace trace
) {
124 return getTimestampTransform(trace
.getHostId());
128 public ITmfTimestampTransform
getTimestampTransform(String hostId
) {
129 SyncSpanningTree tree
= getSyncTree();
130 return tree
.getTimestampTransform(hostId
);
134 * Each convex hull computes the synchronization between 2 given hosts. A
135 * synchronization can be done on multiple hosts that may not all
136 * communicate with each other. We must use another algorithm to determine
137 * which host will be the reference node and what synchronization formula
138 * will be used between each host and this reference node.
140 * For example, take traces a, b and c where a and c talk to b but do not
141 * know each other ({@literal a <-> b <-> c}). The convex hulls will contain
142 * the formulae between their 2 traces, but if a is the reference node, then
143 * the resulting formula of c would be the composition of {@literal a <-> b}
144 * and {@literal b <-> c}
146 * @return The synchronization spanning tree for this synchronization
148 private SyncSpanningTree
getSyncTree() {
150 fTree
= new SyncSpanningTree();
151 for (ConvexHull traceSync
: fSyncs
) {
152 SyncQuality q
= traceSync
.getQuality();
153 if (q
== SyncQuality
.ACCURATE
|| q
== SyncQuality
.APPROXIMATE
) {
154 String from
= traceSync
.getReferenceHost();
155 String to
= traceSync
.getOtherHost();
156 fTree
.addSynchronization(from
, to
, traceSync
.getTimestampTransform(to
), traceSync
.getAccuracy());
164 public SyncQuality
getSynchronizationQuality(ITmfTrace trace1
, ITmfTrace trace2
) {
165 for (ConvexHull traceSync
: fSyncs
) {
166 if (traceSync
.isForHosts(trace1
.getHostId(), trace2
.getHostId())) {
167 return traceSync
.getQuality();
170 return SyncQuality
.ABSENT
;
174 public boolean isTraceSynced(String hostId
) {
175 ITmfTimestampTransform t
= getTimestampTransform(hostId
);
176 return !t
.equals(TimestampTransformFactory
.getDefaultTransform());
180 public Map
<String
, Map
<String
, Object
>> getStats() {
182 * TODO: Stats, while still accurate, may be misleading now that the
183 * sync tree changes synchronization formula. The stats should use the
186 Map
<String
, Map
<String
, Object
>> statmap
= new LinkedHashMap
<>();
187 for (ConvexHull traceSync
: fSyncs
) {
188 statmap
.put(traceSync
.getReferenceHost() + " <==> " + traceSync
.getOtherHost(), traceSync
.getStats()); //$NON-NLS-1$
194 public String
toString() {
195 StringBuilder b
= new StringBuilder();
196 b
.append(getClass().getSimpleName() + " "); //$NON-NLS-1$
202 * This is the actual synchronization algorithm between two traces using
205 private class ConvexHull
implements Serializable
{
207 private static final long serialVersionUID
= 8309351175030935291L;
210 * The list of meaningful points on the upper hull (received by the
211 * reference trace, below in a graph)
213 private final LinkedList
<SyncPoint
> fUpperBoundList
= new LinkedList
<>();
216 * The list of meaninful points on the lower hull (sent by the reference
217 * trace, above in a graph)
219 private final LinkedList
<SyncPoint
> fLowerBoundList
= new LinkedList
<>();
221 /** Points forming the line with maximum slope */
222 private final SyncPoint
[] fLmax
;
224 /** Points forming the line with minimum slope */
225 private final SyncPoint
[] fLmin
;
228 * Slopes and ordinate at origin of respectively fLmin, fLmax and the
231 private BigDecimal fAlphamin
, fBetamax
, fAlphamax
, fBetamin
, fAlpha
, fBeta
;
233 private int fNbMatches
, fNbAccurateMatches
;
234 private String fReferenceHost
= "", fOtherHost
= ""; //$NON-NLS-1$//$NON-NLS-2$
235 private SyncQuality fQuality
;
237 private Map
<String
, Object
> fStats
= new LinkedHashMap
<>();
240 * Initialization of the attributes
243 * ID of the first host
245 * ID of the second host
247 public ConvexHull(String host1
, String host2
) {
248 if (host1
.compareTo(host2
) > 0) {
249 fReferenceHost
= host2
;
252 fReferenceHost
= host1
;
255 fLmax
= new SyncPoint
[2];
256 fLmin
= new SyncPoint
[2];
257 fAlpha
= BigDecimal
.ONE
;
258 fAlphamax
= BigDecimal
.ONE
;
259 fAlphamin
= BigDecimal
.ONE
;
260 fBeta
= BigDecimal
.ZERO
;
261 fBetamax
= BigDecimal
.ZERO
;
262 fBetamin
= BigDecimal
.ZERO
;
264 fNbAccurateMatches
= 0;
265 fQuality
= SyncQuality
.ABSENT
; // default quality
268 protected void processMatch(TmfEventDependency match
) {
270 LinkedList
<SyncPoint
> boundList
, otherBoundList
;
272 SyncPoint
[] line
, otherLine
;
274 int inversionFactor
= 1;
275 boolean qualify
= false;
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
;
284 p
= new SyncPoint(match
.getDestinationEvent(), match
.getSourceEvent());
287 boundList
= fLowerBoundList
;
288 otherBoundList
= fUpperBoundList
;
291 p
= new SyncPoint(match
.getSourceEvent(), match
.getDestinationEvent());
292 inversionFactor
= -1;
296 * Does the message qualify for the hull, or is in on the wrong side
297 * of the reference line
299 if ((line
[0] == null) || (line
[1] == null) || (p
.crossProduct(line
[0], line
[1]) * inversionFactor
> 0)) {
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
306 fNbAccurateMatches
++;
308 removeUselessPoints(p
, boundList
, inversionFactor
);
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
318 adjustBound(line
, otherBoundList
, inversionFactor
);
319 if ((otherLine
[1] != null) && !boundList
.contains(otherLine
[0])) {
320 adjustBound(otherLine
, boundList
, inversionFactor
* -1);
330 * Calculates slopes and ordinate at origin of fLmax and fLmin to obtain
331 * and approximation of the synchronization at this time
333 private void approximateSync() {
336 * Line slopes functions
338 * Lmax = alpha_max T + beta_min
340 * Lmin = alpha_min T + beta_max
342 if ((fLmax
[0] != null) || (fLmin
[0] != null)) {
343 fAlphamax
= fLmax
[1].getAlpha(fLmax
[0]);
344 fBetamin
= fLmax
[1].getBeta(fAlphamax
);
345 fAlphamin
= fLmin
[1].getAlpha(fLmin
[0]);
346 fBetamax
= fLmin
[1].getBeta(fAlphamin
);
347 fAlpha
= fAlphamax
.add(fAlphamin
).divide(BigDecimal
.valueOf(2), fMc
);
348 fBeta
= fBetamin
.add(fBetamax
).divide(BigDecimal
.valueOf(2), fMc
);
349 if ((fLmax
[0] == null) || (fLmin
[0] == null)) {
350 setQuality(SyncQuality
.APPROXIMATE
);
352 else if (fAlphamax
.compareTo(fAlphamin
) > 0) {
353 setQuality(SyncQuality
.ACCURATE
);
355 /* Lines intersect, not good */
356 setQuality(SyncQuality
.FAIL
);
358 } else if (((fLmax
[0] == null) && (fLmin
[1] == null))
359 || ((fLmax
[1] == null) && (fLmin
[0] == null))) {
360 /* Either there is no upper hull point or no lower hull */
361 setQuality(SyncQuality
.INCOMPLETE
);
366 * Verify if the line should be adjusted to be more accurate give the
369 private void adjustBound(SyncPoint
[] line
, LinkedList
<SyncPoint
> otherBoundList
, int inversionFactor
) {
370 SyncPoint minPoint
= null, nextPoint
;
371 boolean finishedSearch
= false;
374 * Find in the other bound, the origin point of the line, start from
375 * the beginning if the point was lost
377 int i
= Math
.max(0, otherBoundList
.indexOf(line
[0]));
379 while ((i
< otherBoundList
.size() - 1) && !finishedSearch
) {
380 minPoint
= otherBoundList
.get(i
);
381 nextPoint
= otherBoundList
.get(i
+ 1);
384 * If the rotation (cross-product) is not optimal, move to next
385 * point as reference for the line (if available)
387 * Otherwise, the current minPoint is the minPoint of the line
389 if (minPoint
.crossProduct(nextPoint
, line
[1]) * inversionFactor
> 0) {
390 if (nextPoint
.getTimeX() < line
[1].getTimeX()) {
394 finishedSearch
= true;
398 finishedSearch
= true;
402 if (line
[0] == null) {
406 /* Make sure point 0 is before point 1 */
407 if ((line
[0] != null) && (line
[0].getTimeX() > line
[1].getTimeX())) {
413 * When a point qualifies to be in a hull, we verify if any of the
414 * existing points need to be removed from the hull
416 private void removeUselessPoints(final SyncPoint p
, final LinkedList
<SyncPoint
> boundList
, final int inversionFactor
) {
418 boolean checkRemove
= true;
420 while (checkRemove
&& boundList
.size() >= 2) {
421 if (p
.crossProduct(boundList
.get(boundList
.size() - 2), boundList
.getLast()) * inversionFactor
> 0) {
422 boundList
.removeLast();
427 boundList
.addLast(p
);
430 public ITmfTimestampTransform
getTimestampTransform(String hostId
) {
431 if (hostId
.equals(fOtherHost
) && (getQuality() == SyncQuality
.ACCURATE
|| getQuality() == SyncQuality
.APPROXIMATE
|| getQuality() == SyncQuality
.FAIL
)) {
432 /* alpha: beta => 1 / fAlpha, -1 * fBeta / fAlpha); */
433 return TimestampTransformFactory
.createLinear(BigDecimal
.ONE
.divide(fAlpha
, fMc
), BigDecimal
.valueOf(-1).multiply(fBeta
).divide(fAlpha
, fMc
));
435 return TimestampTransformFactory
.getDefaultTransform();
438 public SyncQuality
getQuality() {
442 public BigDecimal
getAccuracy() {
443 return fAlphamax
.subtract(fAlphamin
);
446 public Map
<String
, Object
> getStats() {
447 if (fStats
.size() == 0) {
449 switch (getQuality()) {
451 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_absent
;
454 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_accurate
;
457 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_approx
;
460 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_incomplete
;
464 syncQuality
= Messages
.SyncAlgorithmFullyIncremental_fail
;
468 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_refhost
, fReferenceHost
);
469 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_otherhost
, fOtherHost
);
470 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_quality
, syncQuality
);
471 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_alpha
, fAlpha
);
472 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_beta
, fBeta
);
473 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_ub
, (fUpperBoundList
.size() == 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fUpperBoundList
.size());
474 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_lb
, (fLowerBoundList
.size() == 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fLowerBoundList
.size());
475 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_accuracy
, getAccuracy().doubleValue());
476 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_nbmatch
, (fNbMatches
== 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fNbMatches
);
477 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_nbacc
, (fNbAccurateMatches
== 0) ? Messages
.SyncAlgorithmFullyIncremental_NA
: fNbAccurateMatches
);
478 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_refformula
, Messages
.SyncAlgorithmFullyIncremental_T_
+ fReferenceHost
);
479 fStats
.put(Messages
.SyncAlgorithmFullyIncremental_otherformula
, fAlpha
+ Messages
.SyncAlgorithmFullyIncremental_mult
+ Messages
.SyncAlgorithmFullyIncremental_T_
+ fReferenceHost
+ Messages
.SyncAlgorithmFullyIncremental_add
+ fBeta
);
485 public String
getReferenceHost() {
486 return fReferenceHost
;
489 public String
getOtherHost() {
493 public boolean isForHosts(String hostId1
, String hostId2
) {
494 return ((fReferenceHost
.equals(hostId1
) && fOtherHost
.equals(hostId2
)) || (fReferenceHost
.equals(hostId2
) && fOtherHost
.equals(hostId1
)));
497 private void writeObject(ObjectOutputStream s
)
500 * Remove calculation data because most of it is not serializable.
501 * We have the statistics anyway
503 fUpperBoundList
.clear();
504 fLowerBoundList
.clear();
509 s
.defaultWriteObject();
512 @SuppressWarnings("nls")
514 public String
toString() {
515 StringBuilder b
= new StringBuilder();
516 b
.append("Between " + fReferenceHost
+ " and " + fOtherHost
+ " [");
517 b
.append(" alpha " + fAlpha
+ " beta " + fBeta
+ " ]");
521 private void setQuality(SyncQuality fQuality
) {
522 this.fQuality
= fQuality
;
528 * Private class representing a point to synchronize on a graph. The x axis
529 * is the timestamp of the event from the reference trace while the y axis
530 * is the timestamp of the event on the other trace
532 private class SyncPoint
{
533 private final ITmfTimestamp x
, y
;
535 public SyncPoint(ITmfEvent ex
, ITmfEvent ey
) {
536 x
= ex
.getTimestamp();
537 y
= ey
.getTimestamp();
540 public long getTimeX() {
545 * Calculate a cross product of 3 points:
547 * If the cross-product < 0, then p, pa, pb are clockwise
549 * If the cross-product > 0, then p, pa, pb are counter-clockwise
551 * If cross-product == 0, then they are in a line
557 * @return The cross product
559 public long crossProduct(SyncPoint pa
, SyncPoint pb
) {
560 long cp
= ((pa
.x
.getValue() - x
.getValue()) * (pb
.y
.getValue() - y
.getValue()) - (pa
.y
.getValue() - y
.getValue()) * (pb
.x
.getValue() - x
.getValue()));
565 * Gets the alpha (slope) between two points
567 public BigDecimal
getAlpha(SyncPoint p1
) {
569 return BigDecimal
.ONE
;
571 BigDecimal deltay
= BigDecimal
.valueOf(y
.getValue() - p1
.y
.getValue());
572 BigDecimal deltax
= BigDecimal
.valueOf(x
.getValue() - p1
.x
.getValue());
573 if (deltax
.equals(BigDecimal
.ZERO
)) {
574 return BigDecimal
.ONE
;
576 return deltay
.divide(deltax
, fMc
);
580 * Get the beta value (when x = 0) of the line given alpha
582 public BigDecimal
getBeta(BigDecimal alpha
) {
583 return BigDecimal
.valueOf(y
.getValue()).subtract(alpha
.multiply(BigDecimal
.valueOf(x
.getValue()), fMc
));
587 public String
toString() {
588 return String
.format("%s (%s, %s)", this.getClass().getCanonicalName(), x
, y
); //$NON-NLS-1$
592 private void writeObject(ObjectOutputStream s
)
595 * Remove the tree because it is not serializable
598 s
.defaultWriteObject();