001/* 002 * ============================================================================ 003 * Copyright © 2002-2026 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * 007 * Licensed to the public under the agreements of the GNU Lesser General Public 008 * License, version 3.0 (the "License"). You may obtain a copy of the License at 009 * 010 * http://www.gnu.org/licenses/lgpl.html 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 014 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 015 * License for the specific language governing permissions and limitations 016 * under the License. 017 */ 018 019package org.tquadrat.foundation.perflog.internal; 020 021import static java.lang.System.nanoTime; 022import static org.apiguardian.api.API.Status.STABLE; 023import static org.tquadrat.foundation.lang.Objects.isNull; 024import static org.tquadrat.foundation.lang.Objects.nonNull; 025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 026import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_ABORTED; 027import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_READY; 028import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_STARTED; 029import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_STOPPED; 030import static org.tquadrat.foundation.value.Time.NANOSECOND; 031 032import java.math.BigDecimal; 033import java.time.Instant; 034import java.util.HashMap; 035import java.util.Map; 036import java.util.Optional; 037import java.util.concurrent.ScheduledFuture; 038 039import org.apiguardian.api.API; 040import org.tquadrat.foundation.annotation.ClassVersion; 041import org.tquadrat.foundation.perflog.PerfLogManager; 042import org.tquadrat.foundation.perflog.PerformanceSection; 043import org.tquadrat.foundation.perflog.PerformanceTracker; 044import org.tquadrat.foundation.value.TimeValue; 045 046/** 047 * <p>{@summary The implementation for the interface 048 * {@link PerformanceTracker}}.</p> 049 * 050 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 051 * @version $Id: PerformanceTrackerImpl.java 1258 2026-06-04 18:33:06Z tquadrat $ 052 * @since 0.25.0 053 * 054 * @UMLGraph.link 055 */ 056@ClassVersion( sourceVersion = "$Id: PerformanceTrackerImpl.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 057@API( status = STABLE, since = "0.25.0" ) 058@SuppressWarnings( "ClassWithTooManyFields" ) 059public final class PerformanceTrackerImpl implements PerformanceTracker 060{ 061 /*------------*\ 062 ====** Attributes **======================================================= 063 \*------------*/ 064 /** 065 * The flag that indicates whether the tracker was aborted. 066 */ 067 private boolean m_Aborted = false; 068 069 /** 070 * The context data. 071 */ 072 private final Map<String,String> m_Context = new HashMap<>(); 073 074 /** 075 * <p>{@summary The end time for this performance tracker.}</p> 076 * <p>This is not an absolute time, but a relative value.</p> 077 * 078 * @see System#nanoTime() 079 */ 080 private long m_EndTime = 0L; 081 082 /** 083 * The reference to the performance manager that created this tracker. 084 */ 085 private final PerfLogManagerImpl m_PerfLogManager; 086 087 /** 088 * The performance section for this tracker. 089 */ 090 private final PerformanceSection m_PerformanceSection; 091 092 /** 093 * The flag that indicates whether the tracker was started. 094 */ 095 private boolean m_Started = false; 096 097 /** 098 * <p>{@summary The start time for this performance tracker.}</p> 099 * <p>This is not an absolute time, but a relative value.</p> 100 * 101 * @see System#nanoTime() 102 */ 103 private long m_StartTime = 0L; 104 105 /** 106 * The flag that indicates whether the tracker was stopped. 107 */ 108 private boolean m_Stopped = false; 109 110 /** 111 * <p>{@summary The flag that indicates whether this tracker was aborted 112 * because of a timeout.}</p> 113 */ 114 private boolean m_TimedOut = false; 115 116 /** 117 * <p>{@summary The future that is used for the timeout handling.}</p> 118 * <p>Will be {@null} when the timeout is disabled.</p> 119 */ 120 private ScheduledFuture<?> m_TimeOutFuture = null; 121 122 /** 123 * <p>{@summary The absolute time when this performance tracker was 124 * started.} This is only used for reporting purposes.</p> 125 */ 126 private Instant m_Timestamp = null; 127 128 /*--------------*\ 129 ====** Constructors **===================================================== 130 \*--------------*/ 131 /** 132 * Creates a new instance of {@code PerformanceTrackerImpl}. 133 * 134 * @param perfLogManager The reference to the performance manager that 135 * created this tracker instance. 136 * @param performanceSection The reference to the definition for the 137 * performance section. 138 */ 139 public PerformanceTrackerImpl( final PerfLogManager perfLogManager, final PerformanceSection performanceSection ) 140 { 141 m_PerfLogManager = (PerfLogManagerImpl) requireNonNullArgument( perfLogManager,"perfLogManager" ); 142 m_PerformanceSection = requireNonNullArgument( performanceSection, "performanceSection" ); 143 } // PerformanceTrackerImpl() 144 145 /*---------*\ 146 ====** Methods **========================================================== 147 \*---------*/ 148 /** 149 * {@inheritDoc} 150 */ 151 @Override 152 public final void abort() { abort( null, null, false ); } 153 154 /** 155 * {@inheritDoc} 156 */ 157 @Override 158 public final void abort( final String message ) { abort( message, null ); } 159 160 /** 161 * {@inheritDoc} 162 */ 163 @Override 164 public final void abort( final String message, final Throwable cause ) { abort( message, cause, false ); } 165 166 /** 167 * <p>{@summary Stops the performance timer and sets the 168 * {@linkplain #isTimedOut() timeout flag}.}</p> 169 * <p>If the tracker has been aborted or stopped already, nothing happens. 170 * Same if it was never started.</p> 171 * 172 * @param flag {@true} if the tracker timed out, {@false} 173 * if this is a "regular" abort. 174 */ 175 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 176 public final void abort( final boolean flag ) { abort( null, null, flag ); } 177 178 /** 179 * <p>{@summary Stops the performance timer, sets the 180 * {@linkplain #isTimedOut() timeout flag} 181 * and sends a report if required.}</p> 182 * <p>If the tracker has been aborted or stopped already, nothing happens. 183 * Same if it was never started.</p> 184 * 185 * @param message The message describing the reason for the abort. 186 * @param cause The exception that caused the abort. 187 * @param flag {@true} if the tracker timed out, {@false} 188 * if this is a "regular" abort. 189 */ 190 private final void abort( final String message, final Throwable cause, final boolean flag ) 191 { 192 if( m_Started && !m_Stopped && !m_Aborted ) 193 { 194 terminateTimeoutMonitoring(); 195 m_Aborted = true; 196 m_TimedOut = m_PerformanceSection.getTimeout().isPresent() && flag; 197 if( m_TimedOut || m_PerformanceSection.isSendingReportForAbort() ) m_PerfLogManager.sendReport( this, message, cause ); 198 } 199 } // abort() 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override 205 public final PerformanceTracker addContext( final String name, final String value ) 206 { 207 requireNonNullArgument( name, "name" ); 208 if( isNull( value ) ) 209 { 210 m_Context.remove( name ); 211 } 212 else 213 { 214 m_Context.put( name, value ); 215 } 216 217 //---* Done *---------------------------------------------------------- 218 return this; 219 } // addContext 220 221 /** 222 * Returns the context information. 223 * 224 * @return The context information. 225 */ 226 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 227 public final Map<String,String> getContext() { return Map.copyOf( m_Context ); } 228 229 /** 230 * Returns the elapsed time in nanoseconds. 231 * 232 * @return An instance of 233 * {@link java.util.Optional} 234 * holding the elapsed time. 235 */ 236 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 237 public final Optional<TimeValue> getElapsedTime() 238 { 239 final Optional<TimeValue> retValue = m_Started && m_Stopped 240 ? Optional.of( new TimeValue( NANOSECOND, BigDecimal.valueOf( m_EndTime - m_StartTime ) ) ) 241 : Optional.empty(); 242 243 //---* Done *---------------------------------------------------------- 244 return retValue; 245 } // getElapsedTime() 246 247 /** 248 * Returns the performance section. 249 * 250 * @return The performance section. 251 */ 252 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 253 public final PerformanceSection getPerformanceSection() { return m_PerformanceSection; } 254 255 /** 256 * {@inheritDoc} 257 */ 258 @Override 259 public final TrackerStatus getStatus() 260 { 261 var retValue = STATUS_READY; 262 263 if( m_Aborted ) 264 { 265 retValue = STATUS_ABORTED; 266 } 267 else if( m_Stopped ) 268 { 269 retValue = STATUS_STOPPED; 270 } 271 else if( m_Started ) 272 { 273 retValue = STATUS_STARTED; 274 } 275 276 //---* Done *---------------------------------------------------------- 277 return retValue; 278 } // getStatus() 279 280 /** 281 * Returns the timestamp. 282 * 283 * @return The timestamp. 284 */ 285 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 286 public final Instant getTimestamp() { return m_Timestamp; } 287 288 /** 289 * <p>{@summary Checks whether the threshold was exceeded.} This means the 290 * operation covered by the performance section for this tracker took 291 * longer than the estimated time. 292 * 293 * @return {@true} if the operation took longer than the provided 294 * threshold, {@false} otherwise, or no threshold was provided. 295 */ 296 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 297 public boolean isThresholdExceeded() 298 { 299 final var threshold = m_PerformanceSection.getThreshold(); 300 final var elapsedTime = getElapsedTime(); 301 302 final var retValue = threshold.isPresent() && elapsedTime.stream().anyMatch( v -> threshold.get().compareTo( v ) < 0 ); 303 304 //---* Done *---------------------------------------------------------- 305 return retValue; 306 } // isThresholdExceeded() 307 308 /** 309 * <p>{@summary Checks whether the tracker was aborted due to a 310 * timeout.}</p> 311 * 312 * @return {@true} if the tracker was aborted due to a timeout, 313 * {@false} if it is still running, if it was regularly stopped, 314 * if aborted due to other reasons, or if the timeout was disabled for 315 * the respective performance section. 316 */ 317 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 318 public final boolean isTimedOut() { return m_TimedOut; } 319 320 /** 321 * Registers the timeout monitor for this performance tracker. 322 */ 323 private final void registerTimeoutMonitor() 324 { 325 m_TimeOutFuture = m_PerfLogManager.registerTimeoutMonitor( this ); 326 } // registerTimeoutMonitor() 327 328 /** 329 * {@inheritDoc} 330 */ 331 @Override 332 public final PerformanceTracker reset( final boolean resetContext ) throws IllegalStateException 333 { 334 if( m_Started && !(m_Aborted || m_Stopped) ) throw new IllegalStateException( "Tracker is active" ); 335 336 m_Aborted = false; 337 m_Stopped = false; 338 m_Started = false; 339 m_TimedOut = false; 340 341 m_Timestamp = null; 342 m_StartTime = 0L; 343 m_EndTime = 0L; 344 345 if( resetContext ) m_Context.clear(); 346 347 m_TimeOutFuture = null; 348 349 //---* Done *---------------------------------------------------------- 350 return this; 351 } // reset() 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override 357 public final void start() throws IllegalStateException 358 { 359 reset( false ); 360 361 m_Timestamp = Instant.now(); 362 m_Started = true; 363 m_PerformanceSection.getTimeout().ifPresent( _ -> registerTimeoutMonitor() ); 364 m_StartTime = nanoTime(); 365 } // start() 366 367 /** 368 * {@inheritDoc} 369 */ 370 @Override 371 public final void stop() throws IllegalStateException 372 { 373 m_EndTime = nanoTime(); 374 if( m_Stopped ) throw new IllegalStateException( "Tracker was already stopped" ); 375 m_Stopped = true; 376 terminateTimeoutMonitoring(); 377 if( m_Started && !m_Aborted ) 378 { 379 m_PerfLogManager.sendReport( this, null, null ); 380 } 381 } // stop() 382 383 /** 384 * Terminates the timeout monitor. 385 */ 386 private final void terminateTimeoutMonitoring() 387 { 388 if( nonNull( m_TimeOutFuture ) ) 389 { 390 m_TimeOutFuture.cancel( true ); 391 m_TimeOutFuture = null; 392 } 393 } // terminateTimeoutMonitoring() 394} 395// class PerformanceTrackerImpl 396 397/* 398 * End of File 399 */