1 /*******************************************************************************
2 * Copyright (c) 2009, 2010 Ericsson
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
10 * Alvaro Sanchez-Leon (alvsan09@gmail.com) - Initial API and implementation
11 *******************************************************************************/
12 package org
.eclipse
.linuxtools
.lttng
.state
.trace
;
14 import java
.util
.Collections
;
15 import java
.util
.HashMap
;
16 import java
.util
.Vector
;
18 import org
.eclipse
.linuxtools
.lttng
.TraceDebug
;
19 import org
.eclipse
.linuxtools
.lttng
.event
.LttngEvent
;
20 import org
.eclipse
.linuxtools
.lttng
.event
.LttngSyntheticEvent
;
21 import org
.eclipse
.linuxtools
.lttng
.event
.LttngSyntheticEvent
.SequenceInd
;
22 import org
.eclipse
.linuxtools
.lttng
.event
.LttngTimestamp
;
23 import org
.eclipse
.linuxtools
.lttng
.model
.LTTngTreeNode
;
24 import org
.eclipse
.linuxtools
.lttng
.request
.ILttngSyntEventRequest
;
25 import org
.eclipse
.linuxtools
.lttng
.request
.IRequestStatusListener
;
26 import org
.eclipse
.linuxtools
.lttng
.request
.LttngSyntEventRequest
;
27 import org
.eclipse
.linuxtools
.lttng
.state
.LttngStateException
;
28 import org
.eclipse
.linuxtools
.lttng
.state
.evProcessor
.ITransEventProcessor
;
29 import org
.eclipse
.linuxtools
.lttng
.state
.evProcessor
.state
.StateEventToHandlerFactory
;
30 import org
.eclipse
.linuxtools
.lttng
.state
.model
.LttngTraceState
;
31 import org
.eclipse
.linuxtools
.lttng
.state
.model
.StateModelFactory
;
32 import org
.eclipse
.linuxtools
.lttng
.state
.resource
.ILttngStateContext
;
33 import org
.eclipse
.linuxtools
.lttng
.trace
.LTTngTextTrace
;
34 import org
.eclipse
.linuxtools
.lttng
.trace
.LTTngTrace
;
35 import org
.eclipse
.linuxtools
.tmf
.component
.TmfEventProvider
;
36 import org
.eclipse
.linuxtools
.tmf
.event
.TmfEvent
;
37 import org
.eclipse
.linuxtools
.tmf
.event
.TmfTimeRange
;
38 import org
.eclipse
.linuxtools
.tmf
.event
.TmfTimestamp
;
39 import org
.eclipse
.linuxtools
.tmf
.experiment
.TmfExperiment
;
40 import org
.eclipse
.linuxtools
.tmf
.request
.TmfDataRequest
;
41 import org
.eclipse
.linuxtools
.tmf
.signal
.TmfExperimentUpdatedSignal
;
42 import org
.eclipse
.linuxtools
.tmf
.trace
.ITmfTrace
;
43 import org
.eclipse
.linuxtools
.tmf
.trace
.TmfCheckpoint
;
44 import org
.eclipse
.linuxtools
.tmf
.trace
.TmfLocation
;
46 public class StateTraceManager
extends LTTngTreeNode
implements
47 IStateTraceManager
, ILttngStateContext
{
50 private static final long DEFAULT_OFFSET
= 0L;
51 private static final int DEFAULT_CHUNK
= 1;
53 // configurable check point interval
54 private static final long LTTNG_CHECK_POINT_INTERVAL
= 15000L;
55 private long fcheckPointInterval
= LTTNG_CHECK_POINT_INTERVAL
;
57 private TmfExperiment
<LttngEvent
> fExperiment
= null;
60 private final ITmfTrace fTrace
;
61 private final TmfTimestamp fCheckPointsStartTime
;
62 private int fcpuNumber
= -1;
63 private final ITransEventProcessor fStateUpdateProcessor
;
65 // potentially thread shared
66 private final HashMap
<Long
, LttngTraceState
> stateCheckpointsList
= new HashMap
<Long
, LttngTraceState
>();
67 private final Vector
<TmfCheckpoint
> timestampCheckpointsList
= new Vector
<TmfCheckpoint
>();
68 private LttngTraceState fStateModel
;
69 private LttngTraceState fCheckPointStateModel
;
70 private int selectionCount
= 0;
73 private Object checkPointsLock
= new Object();
76 * Could be fixed to trace level from received trace, however preparation
77 * for multiple threads is necessary, a common synthetic event provider at
78 * experiment level is a good start although can be adjusted externally
80 private TmfEventProvider
<LttngSyntheticEvent
> fSynEventProvider
;
83 // =======================================================================
85 // =======================================================================
92 * @param eventProvider
93 * @throws LttngStateException
95 public StateTraceManager(Long id
, LTTngTreeNode parent
, String name
,
97 TmfEventProvider
<LttngSyntheticEvent
> eventProvider
)
98 throws LttngStateException
{
99 super(id
, parent
, name
, trace
);
102 throw new LttngStateException("No TmfTrace object available!");
106 fCheckPointsStartTime
= trace
.getStartTime().clone();
107 fStateUpdateProcessor
= StateEventToHandlerFactory
.getInstance();
108 fSynEventProvider
= eventProvider
;
112 fStateModel
= StateModelFactory
.getStateEntryInstance(this);
113 fStateModel
.init(this);
115 fCheckPointStateModel
= StateModelFactory
.getStateEntryInstance(this);
116 fCheckPointStateModel
.init(this);
119 // =======================================================================
121 // =======================================================================
122 @SuppressWarnings("unchecked")
123 private void init() {
124 // resolve the experiment
125 Object obj
= getParent().getValue();
126 if (obj
!= null && obj
instanceof TmfExperiment
<?
>) {
127 fExperiment
= (TmfExperiment
<LttngEvent
>) obj
;
130 // initialize the number of cpus
131 if (fTrace
instanceof LTTngTrace
) {
132 fcpuNumber
= ((LTTngTrace
) fTrace
).getCpuNumber();
133 } else if (fTrace
instanceof LTTngTextTrace
) {
134 fcpuNumber
= ((LTTngTextTrace
) fTrace
).getCpuNumber();
141 * @see org.eclipse.linuxtools.lttng.signal.ILttExperimentSelectedListener#
142 * experimentSelected(java.lang.Object,
143 * org.eclipse.linuxtools.tmf.experiment.TmfExperiment)
145 public void experimentSelected(Object source
,
146 TmfExperiment
<LttngEvent
> experiment
) {
147 // Only update experiment is needed for the time being
153 * @see org.eclipse.linuxtools.lttng.signal.ILttExperimentSelectedListener#
155 * (org.eclipse.linuxtools.tmf.signal.TmfExperimentUpdatedSignal)
157 public void experimentUpdated(TmfExperimentUpdatedSignal signal
, boolean wait
) {
158 // initialise check points once per new experiment selection
160 synchronized (checkPointsLock
) {
161 stateCheckpointsList
.clear();
162 timestampCheckpointsList
.clear();
165 // requested time window from trace start to end, this to
166 // make sure one thread is used so the events arrive in order for proper
167 // building of the checkpoints
168 TmfTimeRange adjustedRange
= new TmfTimeRange(fCheckPointsStartTime
, fTrace
.getTimeRange().getEndTime());
170 // Obtain a dataRequest to pass to the processRequest function
171 ILttngSyntEventRequest request
= getDataRequestStateSave(adjustedRange
,
172 null, fStateUpdateProcessor
);
173 request
.setclearDataInd(true);
174 request
.startRequestInd(fSynEventProvider
);
177 request
.waitForCompletion();
178 } catch (InterruptedException e
) {
187 // * @see org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#
188 // * executeDataRequest(org.eclipse.linuxtools.tmf.event.TmfTimeRange,
189 // * java.lang.Object,
190 // * org.eclipse.linuxtools.lttng.request.IRequestStatusListener,
191 // * org.eclipse.linuxtools.lttng.state.evProcessor.ITransEventProcessor)
193 // public ILttngSyntEventRequest executeDataRequest(TmfTimeRange trange,
195 // IRequestStatusListener listener, ITransEventProcessor processor) {
196 // TmfTimestamp restoredStartTime = restoreCheckPointByTimestamp(trange
198 // // Adjust the time range to consider rewinding to the start time
199 // trange = new TmfTimeRange(restoredStartTime, trange.getEndTime());
200 // // Get a data request for the time range we want (nearest checkpoint
201 // // to timestamp wanted)
203 // // Process request to that point
204 // ILttngSyntEventRequest request = getDataRequestByTimeRange(trange,
205 // listener, processor);
206 // request.setSource(source);
208 // // don't wait for completion i.e. allow cancellations
209 // request.startRequestInd(fSynEventProvider);
210 // // fSynEventProvider
211 // // .sendRequest((TmfDataRequest<LttngSyntheticEvent>) request);
213 // if (TraceDebug.isDEBUG()) {
215 // .debug(" Time Window requested, (start adjusted to checkpoint): "
216 // + trange.getStartTime()
218 // + trange.getEndTime()
219 // + " Total number of processes in the State provider: "
220 // + fStateModel.getProcesses().length + " Completed");
229 * @see org.eclipse.linuxtools.lttng.state.IStateManager#getEventLog()
231 public ITmfTrace
getTrace() {
236 * Save a checkpoint if it is needed at that point
238 * The function will use "eventCount" internally to determine if a save was
241 * @param eventCounter
242 * The event "count" or event "id" so far
244 * The timestamp of this event
246 * @return boolean True if a checkpoint was saved, false otherwise
248 private void saveCheckPointIfNeeded(Long eventCounter
,
249 TmfTimestamp eventTime
) {
250 // Save a checkpoint every LTTNG_STATE_SAVE_INTERVAL event
251 if ((eventCounter
.longValue() % fcheckPointInterval
) == 0) {
252 LttngTraceState stateCheckPoint
;
253 synchronized (fCheckPointStateModel
) {
254 stateCheckPoint
= fCheckPointStateModel
.clone();
257 TraceDebug
.debug("Check point created here: " + eventCounter
258 + " -> " + eventTime
.toString() + "************"
259 + getTrace().getName() + " >>>>> Thread: "
260 + Thread
.currentThread().getId());
262 synchronized (checkPointsLock
) {
263 // Save the checkpoint
264 stateCheckpointsList
.put(eventCounter
, stateCheckPoint
);
265 // Save correlation between timestamp and checkpoint index
267 timestampCheckpointsList
.add(new TmfCheckpoint(
268 new TmfTimestamp(eventTime
), new TmfLocation
<Long
>(
275 * @return the lttng_check_point_interval
277 public long getCheckPointInterval() {
278 return fcheckPointInterval
;
282 * @param check_point_interval
283 * , the lttng_check_point_interval to set
285 public void setCheckPointInterval(long check_point_interval
) {
286 this.fcheckPointInterval
= check_point_interval
;
290 * @see org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#restoreCheckPointByTimestamp(org.eclipse.linuxtools.tmf.event.TmfTimestamp)
292 @SuppressWarnings("unchecked")
293 public TmfTimestamp
restoreCheckPointByTimestamp(TmfTimestamp eventTime
) { TmfTimeRange experimentRange
= fExperiment
.getTimeRange();
294 TmfTimestamp nearestTimeStamp
= fTrace
.getStartTime();
296 // The GUI can have time limits higher than this log, since GUI can
297 // handle multiple logs
298 if ((eventTime
.getValue() < 0)
299 || (eventTime
.getValue() > experimentRange
.getEndTime().getValue())) {
303 // // The GUI can have time limits lower than this trace, since experiment
304 // // can handle multiple traces
305 // if ((eventTime.getValue() < fTrace.getStartTime().getValue())) {
306 // eventTime = fTrace.getStartTime();
309 // // Sort the checkpoints, required before the binary search
310 // Collections.sort(timestampCheckpointsList);
311 // // Initiate the compare with a checkpoint containing the target time
313 // int index = Collections.binarySearch(timestampCheckpointsList,
314 // new TmfCheckpoint(eventTime, new TmfLocation<Long>(0L)));
315 // // adjust index to round down to earlier checkpoint when exact match not
317 // index = getPrevIndex(index);
319 LttngTraceState traceState
;
321 // // No checkpoint restore is needed, start with a brand new
323 // traceState = StateModelFactory.getStateEntryInstance(this);
325 // The reference to fCheckPointsStartTime is used as the indicator to
326 // identify a state check point building request
327 if (eventTime
== fCheckPointsStartTime
) {
328 // indicates state checkpoint building request
329 nearestTimeStamp
= fCheckPointsStartTime
;
330 // starting from the trace start time with an empty state model
331 traceState
= StateModelFactory
.getStateEntryInstance(this);
332 // update the instance field variable pointing the the check point
333 // building state model
334 synchronized (fCheckPointStateModel
) {
335 fCheckPointStateModel
= traceState
;
339 // synchronized (checkPointsLock) {
340 // // Useful CheckPoint found
341 // TmfCheckpoint checkpoint = timestampCheckpointsList.get(index);
342 // nearestTimeStamp = checkpoint.getTimestamp();
343 // // get the location associated with the checkpoint
344 // TmfLocation<Long> location = (TmfLocation<Long>) checkpoint
346 // // reference a new copy of the checkpoint template
347 // traceState = stateCheckpointsList.get(location.getLocation())
349 // The GUI requests can have time limits lower than this trace,
350 // since experiment can handle multiple traces
351 if ((eventTime
.getValue() < fTrace
.getStartTime().getValue())) {
352 eventTime
= fTrace
.getStartTime();
356 // // Restore the stored traceState
357 // synchronized (fStateModel) {
358 // fStateModel = traceState;
359 // Sort the checkpoints, required before the binary search
360 Collections
.sort(timestampCheckpointsList
);
361 // Initiate the compare with a checkpoint containing the target time
363 int index
= Collections
.binarySearch(timestampCheckpointsList
, new TmfCheckpoint(eventTime
,
364 new TmfLocation
<Long
>(0L)));
365 // adjust index to round down to earlier checkpoint when exact match
368 index
= getPrevIndex(index
);
371 // No checkpoint restore is needed, start with a brand new
373 traceState
= StateModelFactory
.getStateEntryInstance(this);
375 synchronized (checkPointsLock
) {
376 // Useful CheckPoint found
377 TmfCheckpoint checkpoint
= timestampCheckpointsList
.get(index
);
378 nearestTimeStamp
= checkpoint
.getTimestamp();
379 // get the location associated with the checkpoint
380 TmfLocation
<Long
> location
= (TmfLocation
<Long
>) checkpoint
.getLocation();
381 // reference a new copy of the checkpoint template
382 traceState
= stateCheckpointsList
.get(location
.getLocation()).clone();
386 // Restore the stored traceState
387 synchronized (fStateModel
) {
388 fStateModel
= traceState
;
392 return nearestTimeStamp
;
396 * Adjust the result from a binary search to the round down position
399 * if Negative is: (-(insertion point) -1)
400 * @return position or if no match found, earlier than insertion point
402 private int getPrevIndex(int position
) {
403 int roundDownPosition
= position
;
405 roundDownPosition
= -(position
+ 2);
408 roundDownPosition
= roundDownPosition
< 0 ?
0 : roundDownPosition
;
409 return roundDownPosition
;
413 // TODO: Remove this request type when the UI handle their own requests
415 * Request Event data of a specified time range
420 * @return ILttngEventRequest The request made
422 ILttngSyntEventRequest
getDataRequestByTimeRange(TmfTimeRange timeWindow
,
423 IRequestStatusListener listener
,
424 final ITransEventProcessor processor
) {
426 ILttngSyntEventRequest request
= new StateTraceManagerRequest(timeWindow
,
427 DEFAULT_OFFSET
, TmfDataRequest
.ALL_DATA
, DEFAULT_CHUNK
,
428 listener
, getExperimentTimeWindow(), processor
) {
434 private ILttngSyntEventRequest
getDataRequestStateSave(TmfTimeRange timeWindow
,
435 IRequestStatusListener requestListener
,
436 final ITransEventProcessor processor
) {
438 // Create the new request and override the handlePartialResult function
439 ILttngSyntEventRequest request
= new StateTraceManagerRequest(timeWindow
,
440 DEFAULT_OFFSET
, TmfDataRequest
.ALL_DATA
, DEFAULT_CHUNK
,
441 requestListener
, getExperimentTimeWindow(), processor
) {
444 * @see org.eclipse.linuxtools.lttng.request.LttngSyntEventRequest#handleCompleted()
447 public void handleCompleted() {
448 if (isCancelled() || isFailed()) {
449 // No notification to end request handlers
451 // Indicates that trace selection has successfully completed
452 // i.e. check points have been already build if the number
453 // of events exceeded the interval
454 setTraceSelectCount(getTraceSelectCount() + 1);
457 super.handleCompleted();
459 if (TraceDebug
.isDEBUG()) {
460 StringBuilder sb
= new StringBuilder(
461 "Check Point build Request completed"
463 + fprocessor
.getStateUpdateCount()
464 + "\tEvents for a different trace state received: "
465 + fprocessor
.getFilteredOutEventCount()
467 + fStateModel
.getProcesses().length
469 + "Total number of processes in the Check point State model: "
470 + fCheckPointStateModel
.getProcesses().length
);
472 TmfTimeRange logTimes
= fTrace
.getTimeRange();
473 sb
.append("\n\tTrace time interval for trace "
476 + new LttngTimestamp(logTimes
.getStartTime()));
477 sb
.append(" - " + new LttngTimestamp(logTimes
.getEndTime()));
478 sb
.append("\n\tRequested Time: "
479 + getRange().getStartTime().toString() + " - "
480 + getRange().getEndTime().toString());
481 sb
.append("\n\tCheckPoints available at: ");
482 for (TmfCheckpoint cpoint
: timestampCheckpointsList
) {
483 sb
.append("\n\t" + "Location: " + cpoint
.getLocation()
484 + " - " + cpoint
.getTimestamp());
487 TraceDebug
.debug(sb
.toString());
492 * @see org.eclipse.linuxtools.lttng.state.trace.StateTraceManager.StateTraceManagerRequest#saveCheckPoint(java.lang.Long, org.eclipse.linuxtools.tmf.event.TmfTimestamp)
495 public void saveCheckPoint(Long count
, TmfTimestamp time
) {
496 saveCheckPointIfNeeded(count
, time
);
508 private synchronized int getTraceSelectCount() {
509 return selectionCount
;
515 private synchronized void setTraceSelectCount(int value
) {
516 selectionCount
= value
;
523 * org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#getStateModel
526 public LttngTraceState
getStateModel() {
527 synchronized (fStateModel
) {
536 * org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#getStateModel
537 * (org.eclipse.linuxtools.tmf.event.TmfTimestamp)
539 public LttngTraceState
getStateModel(TmfTimestamp checkPointReference
) {
541 if (checkPointReference
== null) {
545 // Only two state models supported at this time e.g. one for checkpoint
546 // building and the second one is shared for UI requests which are
547 // either coalesced or sequential (i.e. within the same trace data
549 if (checkPointReference
== fCheckPointsStartTime
) {
550 synchronized (fCheckPointStateModel
) {
551 return fCheckPointStateModel
;
554 synchronized (fStateModel
) {
561 * @return the stateCheckpointsList
563 HashMap
<Long
, LttngTraceState
> getStateCheckpointsList() {
564 return stateCheckpointsList
;
568 * @return the timestampCheckpointsList
570 Vector
<TmfCheckpoint
> getTimestampCheckpointsList() {
571 return timestampCheckpointsList
;
573 // =======================================================================
575 // =======================================================================
576 class StateTraceManagerRequest
extends LttngSyntEventRequest
{
577 // =======================================================================
579 // =======================================================================
580 final TmfEvent
[] evt
= new TmfEvent
[1];
581 final ITransEventProcessor fprocessor
;
582 LttngSyntheticEvent synEvent
;
583 Long fCount
= getSynEventCount();
585 // =======================================================================
587 // =======================================================================
588 public StateTraceManagerRequest(TmfTimeRange range
, long offset
,
589 int nbEvents
, int maxBlockSize
,
590 IRequestStatusListener listener
,
591 TmfTimeRange experimentTimeRange
, ITransEventProcessor processor
) {
593 super(range
, offset
, nbEvents
, maxBlockSize
, listener
,
594 experimentTimeRange
, processor
);
595 fprocessor
= processor
;
596 TraceDebug
.debug("Instance created for range: " + range
.toString());
600 // =======================================================================
602 // =======================================================================
607 * org.eclipse.linuxtools.lttng.request.LttngSyntEventRequest#handleData
611 public void handleData() {
612 TmfEvent
[] result
= getData();
614 evt
[0] = (result
.length
> 0) ? result
[0] : null;
615 if (evt
[0] != null) {
616 synEvent
= (LttngSyntheticEvent
) evt
[0];
617 if (synEvent
.getSynType() == SequenceInd
.AFTER
) {
618 // Note : We call this function before incrementing
619 // eventCount to save a default check point at the "0th"
621 saveCheckPoint(fCount
, synEvent
.getTimestamp());
624 if (TraceDebug
.isDEBUG()) {
625 if (fCount
% 1000 == 0) {
626 TraceDebug
.debug("handled: " + fCount
627 + " sequence: " + synEvent
.getSynType());
635 * To be overridden by active save e.g. check points, this no action
636 * default is used for requests which do not require rebuilding of
637 * checkpoints e.g. requiring data of a new time range selection
642 public void saveCheckPoint(Long count
, TmfTimestamp time
) {
650 * @see org.eclipse.linuxtools.lttng.state.resource.ILttngStateContext#
653 public int getNumberOfCpus() {
660 * @see org.eclipse.linuxtools.lttng.state.resource.ILttngStateContext#
661 * getTraceTimeWindow()
663 public TmfTimeRange
getTraceTimeWindow() {
664 if (fTrace
!= null) {
665 return fTrace
.getTimeRange();
675 * org.eclipse.linuxtools.lttng.state.resource.ILttngStateContext#getTraceId
678 public String
getTraceId() {
679 if (fTrace
!= null) {
680 return fTrace
.getName();
688 * @see org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#
689 * getExperimentTimeWindow()
691 public TmfTimeRange
getExperimentTimeWindow() {
692 if (fExperiment
!= null) {
693 return fExperiment
.getTimeRange();
702 * org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#getExperimentName
705 public String
getExperimentName() {
706 return fExperiment
.getName();
713 * org.eclipse.linuxtools.lttng.state.resource.ILttngStateContext#getTraceIdRef
716 public ITmfTrace
getTraceIdRef() {
724 * org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#clearCheckPoints
727 public void clearCheckPoints() {
728 synchronized (checkPointsLock
) {
729 stateCheckpointsList
.clear();
730 timestampCheckpointsList
.clear();
732 fCheckPointStateModel
= StateModelFactory
733 .getStateEntryInstance(this);
735 fCheckPointStateModel
.init(this);
736 } catch (LttngStateException e
) {
746 * org.eclipse.linuxtools.lttng.state.trace.IStateTraceManager#handleEvent
747 * (org.eclipse.linuxtools.lttng.event.LttngSyntheticEvent, java.lang.Long)
749 public void handleEvent(LttngSyntheticEvent synEvent
, Long eventCount
) {
750 fStateUpdateProcessor
.process(synEvent
, fCheckPointStateModel
);
752 // Save checkpoint as needed
753 saveCheckPointIfNeeded(eventCount
- 1, synEvent
.getTimestamp());
759 * @see java.lang.Object#toString()
761 public String
toString() {
762 StringBuilder sb
= new StringBuilder(super.toString());
763 sb
.append("\n\tTotal number of processes in the Shared State model: " + fStateModel
.getProcesses().length
764 + "\n\t" + "Total number of processes in the Check point State model: "
765 + fCheckPointStateModel
.getProcesses().length
);
767 TmfTimeRange traceTRange
= fTrace
.getTimeRange();
768 sb
.append("\n\tTrace time interval for trace " + fTrace
.getName() + "\n\t"
769 + new LttngTimestamp(traceTRange
.getStartTime()));
770 sb
.append(" - " + new LttngTimestamp(traceTRange
.getEndTime()));
771 sb
.append("\n\tCheckPoints available at: ");
772 for (TmfCheckpoint cpoint
: timestampCheckpointsList
) {
773 sb
.append("\n\t" + "Location: " + cpoint
.getLocation() + " - " + cpoint
.getTimestamp());
776 return sb
.toString();