001package org.tquadrat.foundation.perflog.internal; 002 003import static java.time.ZoneOffset.UTC; 004import static org.apiguardian.api.API.Status.INTERNAL; 005import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING; 006import static org.tquadrat.foundation.lang.Objects.hash; 007import static org.tquadrat.foundation.lang.Objects.isNull; 008import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 009import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_ABORTED; 010 011import java.time.Instant; 012import java.time.ZonedDateTime; 013import java.util.Optional; 014import java.util.OptionalInt; 015import java.util.OptionalLong; 016import java.util.StringJoiner; 017import java.util.concurrent.locks.ReentrantReadWriteLock; 018 019import org.apiguardian.api.API; 020import org.tquadrat.foundation.annotation.ClassVersion; 021import org.tquadrat.foundation.lang.AutoLock; 022import org.tquadrat.foundation.perflog.PerformanceSection; 023import org.tquadrat.foundation.perflog.PerformanceSectionName; 024import org.tquadrat.foundation.value.Time; 025import org.tquadrat.foundation.value.TimeValue; 026 027/** 028 * <p>{@summary Instances of this class holds the execution status for a 029 * "Performance Section".}</p> 030 * 031 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 032 * @version $Id: PerformanceSectionInfo.java 1258 2026-06-04 18:33:06Z tquadrat $ 033 * @since 0.25.0 034 * 035 * @UMLGraph.link 036 */ 037@ClassVersion( sourceVersion = "$Id: PerformanceSectionInfo.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 038@API( status = INTERNAL, since = "0.25.0" ) 039public class PerformanceSectionInfo 040{ 041 /*------------*\ 042 ====** Attributes **======================================================= 043 \*------------*/ 044 /** 045 * The accumulated execution time of the performance section since the 046 * last restart of the program, in milliseconds. 047 * 048 * @see #m_FirstStart 049 */ 050 private long m_CumulatedExecutionTime = 0L; 051 052 /** 053 * <p>{@summary The time when the performance section was first started 054 * after the last restart of the program.}</p> 055 */ 056 private Instant m_FirstStart; 057 058 /** 059 * <p>{@summary The time when this performance section info was last 060 * updated.} This attribute is used to determine when the report entries 061 * for the performance section should be discarded.</p> 062 */ 063 private Instant m_LastUpdated; 064 065 /** 066 * <p>{@summary The number of aborted runs that timed out since the last 067 * restart of the program.} These are <i>not</i> included in 068 * {@link #m_NumberOfCompletedRuns}.</p> 069 * 070 * @see #m_FirstStart 071 */ 072 private int m_NumberOfAbortedRuns = 0; 073 074 /** 075 * The number of runs with an elapsed time since the last restart of the 076 * program. 077 * 078 * @see #m_FirstStart 079 */ 080 private int m_NumberOfCompletedRuns = 0; 081 082 /** 083 * <p>{@summary The number of runs that exceeded the threshold since the 084 * last restart of the program.} These are included in 085 * {@link #m_NumberOfCompletedRuns}.</p> 086 * 087 * @see #m_FirstStart 088 */ 089 @SuppressWarnings( "FieldNamingConvention" ) 090 private int m_NumberOfRunsThatExceededThreshold = 0; 091 092 /** 093 * <p>{@summary The number of runs that timed out since the last restart 094 * of the program.} These are <i>not</i> included in 095 * {@link #m_NumberOfCompletedRuns} 096 * nor in 097 * {@link #m_NumberOfAbortedRuns}.</p> 098 * 099 * @see #m_FirstStart 100 */ 101 private int m_NumberOfTimedOutRuns = 0; 102 103 /** 104 * The performance section. 105 */ 106 private final PerformanceSection m_PerformanceSection; 107 108 /** 109 * <p>{@summary The read guard for the statistics attributes.} These 110 * are:</p> 111 * <ul> 112 * <li>{@link #m_FirstStart}</li> 113 * <li>{@link #m_LastUpdated}</li> 114 * <li>{@link #m_NumberOfAbortedRuns}</li> 115 * <li>{@link #m_NumberOfCompletedRuns}</li> 116 * <li>{@link #m_NumberOfRunsThatExceededThreshold}</li> 117 * <li>{@link #m_NumberOfTimedOutRuns}</li> 118 * </ul> 119 */ 120 private final AutoLock m_ReadGuard; 121 122 /** 123 * <p>{@summary The write guard for the statistics attributes.} These 124 * are:</p> 125 * <ul> 126 * <li>{@link #m_FirstStart}</li> 127 * <li>{@link #m_LastUpdated}</li> 128 * <li>{@link #m_NumberOfAbortedRuns}</li> 129 * <li>{@link #m_NumberOfCompletedRuns}</li> 130 * <li>{@link #m_NumberOfRunsThatExceededThreshold}</li> 131 * <li>{@link #m_NumberOfTimedOutRuns}</li> 132 * </ul> 133 */ 134 private final AutoLock m_WriteGuard; 135 136 /*--------------*\ 137 ====** Constructors **===================================================== 138 \*--------------*/ 139 /** 140 * Creates a new instance of {@code PerformanceSectionInfo}. 141 * 142 * @param performanceSection The performance section. 143 */ 144 public PerformanceSectionInfo( final PerformanceSection performanceSection ) 145 { 146 m_PerformanceSection = requireNonNullArgument( performanceSection, "performanceSection" ); 147 148 final var lock = new ReentrantReadWriteLock(); 149 m_ReadGuard = AutoLock.of( lock.readLock() ); 150 m_WriteGuard = AutoLock.of( lock.writeLock() ); 151 } // PerformanceSectionInfo() 152 153 /** 154 * <p>{@summary Creates a new instance of 155 * {@code PerformanceSectionInfo}.}</p> 156 * <p>The wrapped 157 * {@link PerformanceSection} 158 * instance will be created on the fly with default values:</p> 159 * <ul> 160 * <li>{@linkplain PerformanceSection#getDescription() Description}: empty</li> 161 * <li>{@linkplain PerformanceSection#getThreshold() Threshold}: disabled</li> 162 * <li>{@linkplain PerformanceSection#getTimeout() Timeout}: disabled</li> 163 * <li>{@linkplain PerformanceSection#isSendingReportForAbort() Sending Report for Abort}: true</li> 164 * <li>{@linkplain PerformanceSection#isIgnored() Active}: true</li> 165 * </ul> 166 * 167 * @param performanceSectionName The name for a new performance section 168 * with default settings. 169 */ 170 @SuppressWarnings( "MethodParameterNamingConvention" ) 171 public PerformanceSectionInfo( final PerformanceSectionName performanceSectionName ) 172 { 173 this( new PerformanceSection( requireNonNullArgument( performanceSectionName, "performanceSectionName" ), EMPTY_STRING, null, null ) ); 174 } // PerformanceSectionInfo() 175 176 /** 177 * <p>{@summary Creates a new instance of 178 * {@code PerformanceSectionInfo}.}</p> 179 * <p>The wrapped 180 * {@link PerformanceSection} 181 * instance will be created on the fly with default values:</p> 182 * <ul> 183 * <li>{@linkplain PerformanceSection#getDescription() Description}: empty</li> 184 * <li>{@linkplain PerformanceSection#getThreshold() Threshold}: disabled</li> 185 * <li>{@linkplain PerformanceSection#getTimeout() Timeout}: disabled</li> 186 * <li>{@linkplain PerformanceSection#isSendingReportForAbort() Sending Report for Abort}: true</li> 187 * <li>{@linkplain PerformanceSection#isIgnored() Active}: true</li> 188 * </ul> 189 * 190 * @param performanceSectionName The name for a new performance section 191 * with default settings. 192 */ 193 @SuppressWarnings( "MethodParameterNamingConvention" ) 194 public PerformanceSectionInfo( final String performanceSectionName ) 195 { 196 this( new PerformanceSectionNameImpl( performanceSectionName ) ); 197 } // PerformanceSectionInfo() 198 199 /*---------*\ 200 ====** Methods **========================================================== 201 \*---------*/ 202 /** 203 * {@inheritDoc} 204 * <p>Two instances of {@code PerformanceSectionInfo} are equal if both 205 * refer to equal 206 * {@link PerformanceSection} 207 * instances.</p> 208 */ 209 @Override 210 public final boolean equals( final Object o ) 211 { 212 var retValue = this == o; 213 if( !retValue && o instanceof final PerformanceSectionInfo other ) 214 { 215 retValue = m_PerformanceSection.equals( other.m_PerformanceSection ); 216 } 217 218 //---* Done *---------------------------------------------------------- 219 return retValue; 220 } // equals() 221 222 /** 223 * Returns the average execution time of the performance section. 224 * 225 * @return An instance of 226 * {@link OptionalLong} 227 * holding the average execution time in milliseconds. Will be empty 228 * if no successful execution was recorded so far. 229 */ 230 public final OptionalLong getAverageExecutionTime() 231 { 232 final OptionalLong retValue; 233 try( final var _ = m_ReadGuard.lock() ) 234 { 235 retValue = m_NumberOfCompletedRuns == 0 236 ? OptionalLong.empty() 237 : OptionalLong.of( m_CumulatedExecutionTime / (long) m_NumberOfCompletedRuns ); 238 } 239 240 //---* Done *---------------------------------------------------------- 241 return retValue; 242 } // getAverageExecutionTime() 243 244 /** 245 * Returns the description for the performance section. 246 * 247 * @return The description for the performance section. 248 */ 249 public final String getDescription() { return m_PerformanceSection.getDescription(); } 250 251 /** 252 * Returns the start time for the first execution of the performance 253 * section after the last restart of the program. 254 * 255 * @return An instance of 256 * {@link Optional} 257 * that holds the first start time. 258 */ 259 public final Optional<ZonedDateTime> getFirstStart() 260 { 261 final var retValue = m_ReadGuard.execute( () -> m_FirstStart ).map( v -> v.atZone( UTC ) ); 262 263 //---* Done *---------------------------------------------------------- 264 return retValue; 265 } // getFirstStart() 266 267 /** 268 * Returns the time for the last update of this performance section info 269 * instance after the last restart of the program. 270 * 271 * @return An instance of 272 * {@link Optional} 273 * that holds the last update time. 274 */ 275 public final Optional<ZonedDateTime> getLastUpdated() 276 { 277 final var retValue = m_ReadGuard.execute( () -> m_LastUpdated ).map( v -> v.atZone( UTC ) ); 278 279 //---* Done *---------------------------------------------------------- 280 return retValue; 281 } // getLastUpdated() 282 283 /** 284 * Returns the name of the performance section. 285 * 286 * @return The name of the performance section. 287 */ 288 public final PerformanceSectionName getName() { return m_PerformanceSection.getName(); } 289 290 /** 291 * <p>{@summary Returns the number of aborted runs.} Will be empty if no 292 * run was recorded yet.</p> 293 * <p>Runs that timed out are not included in this number.</p> 294 * 295 * @return An instance of 296 * {@link OptionalInt} 297 * that holds the number of aborted runs. 298 */ 299 public final OptionalInt getNumberOfAbortedRuns() 300 { 301 final OptionalInt retValue; 302 try( final var _ = m_ReadGuard.lock() ) 303 { 304 retValue = isNull( m_LastUpdated ) 305 ? OptionalInt.empty() 306 : OptionalInt.of( m_NumberOfAbortedRuns ); 307 } 308 309 //---* Done *---------------------------------------------------------- 310 return retValue; 311 } // getNumberOfAbortedRuns() 312 313 /** 314 * <p>{@summary Returns the number of completed runs.} These are the runs 315 * that provided an 316 * {@linkplain PerformanceTrackerImpl#getElapsedTime() elapsed time}. 317 * Will be empty if no run was recorded yet.</p> 318 * 319 * @return An instance of 320 * {@link OptionalInt} 321 * that holds the number of completed runs. 322 */ 323 public final OptionalInt getNumberOfCompletedRuns() 324 { 325 final OptionalInt retValue; 326 try( final var _ = m_ReadGuard.lock() ) 327 { 328 retValue = isNull( m_LastUpdated ) 329 ? OptionalInt.empty() 330 : OptionalInt.of( m_NumberOfCompletedRuns ); 331 } 332 333 //---* Done *---------------------------------------------------------- 334 return retValue; 335 } // getNumberOfCompletedRuns() 336 337 /** 338 * <p>{@summary Returns the number of completed runs that exceeded the 339 * {@linkplain #getThreshold() threshold}.} These runs are also included 340 * into the number of 341 * {@linkplain #getNumberOfCompletedRuns() completed runs}. 342 * Will be empty if no run was recorded yet.</p> 343 * 344 * @return An instance of 345 * {@link OptionalInt} 346 * that holds the number of completed runs exceeding the threshold. 347 */ 348 @SuppressWarnings( "NewMethodNamingConvention" ) 349 public final OptionalInt getNumberOfRunsThatExceededThreshold() 350 { 351 final OptionalInt retValue; 352 try( final var _ = m_ReadGuard.lock() ) 353 { 354 retValue = isNull( m_LastUpdated ) 355 ? OptionalInt.empty() 356 : OptionalInt.of( m_NumberOfRunsThatExceededThreshold ); 357 } 358 359 //---* Done *---------------------------------------------------------- 360 return retValue; 361 } // getNumberOfRunsThatExceededThreshold() 362 363 /** 364 * <p>{@summary Returns the number of runs that timed ot.} Will be empty 365 * if no run was recorded yet.</p> 366 * <p>Runs that were aborted due to other reasons than a timeout are not 367 * included in this number.</p> 368 * 369 * @return An instance of 370 * {@link OptionalInt} 371 * that holds the number of timed out runs. 372 */ 373 public final OptionalInt getNumberOfTimedOutRuns() 374 { 375 final OptionalInt retValue; 376 try( final var _ = m_ReadGuard.lock() ) 377 { 378 retValue = isNull( m_LastUpdated ) 379 ? OptionalInt.empty() 380 : OptionalInt.of( m_NumberOfTimedOutRuns ); 381 } 382 383 //---* Done *---------------------------------------------------------- 384 return retValue; 385 } // getNumberOfTimedOutRuns() 386 387 /** 388 * Returns the 389 * {@link PerformanceSection} 390 * hold by this {@code PerformanceSectionInfo} instance. 391 * 392 * @return The performance section. 393 */ 394 public final PerformanceSection getPerformanceSection() { return m_PerformanceSection; } 395 396 /** 397 * Returns the threshold for the performance section. 398 * 399 * @return An instance of 400 * {@link OptionalLong} 401 * that holds the threshold in milliseconds. 402 */ 403 public final Optional<TimeValue> getThreshold() { return m_PerformanceSection.getThreshold(); } 404 405 /** 406 * Returns the timeout for this performance section. 407 * 408 * @return An instance of 409 * {@link OptionalLong} 410 * that holds the timeout in milliseconds. 411 */ 412 public final Optional<TimeValue> getTimeout() { return m_PerformanceSection.getTimeout(); } 413 414 /** 415 * {@inheritDoc} 416 * <p>Two instances of {@code PerformanceSectionInfo} are equal if both 417 * refer to equal 418 * {@link PerformanceSection} 419 * instances.</p> 420 */ 421 @Override 422 public final int hashCode() { return hash( m_PerformanceSection ); } 423 424 /** 425 * Returns the flag that controls whether the performance section is 426 * currently ignored. 427 * 428 * @return {@true} if the performance section is currently ignored, 429 * {@false} if the performance section is currently observed. 430 */ 431 public final boolean isIgnored() { return m_PerformanceSection.isIgnored(); } 432 433 /** 434 * <p>{@summary Checks whether the average execution time is above the 435 * threshold for the performance section.} If the threshold is disabled, 436 * the method returns {@false}.</p> 437 * 438 * @return {@true} if the average execution time is above the 439 * threshold, {@false} otherwise. 440 * 441 * @see PerformanceSection#getThreshold() 442 */ 443 public final boolean isAverageAboveThreshold() 444 { 445 final var averageTime = getAverageExecutionTime(); 446 final var threshold = getThreshold().map( v -> v.convert( Time.MILLISECOND ).longValue() ); 447 448 final var retValue = averageTime.isPresent() && threshold.isPresent() && averageTime.getAsLong() > threshold.get().longValue(); 449 450 //---* Done *---------------------------------------------------------- 451 return retValue; 452 } // isAverageAboveThreshold() 453 454 /** 455 * Returns the flag that indicates whether reports should be sent only if 456 * the threshold was exceeded. 457 * 458 * @return {@true} if a report should be sent only when the threshold 459 * was exceeded, {@false} if a report should be sent always. 460 */ 461 @SuppressWarnings( "NewMethodNamingConvention" ) 462 public final boolean isSendingReportOnlyForExceededThreshold() { return m_PerformanceSection.isSendingReportOnlyForExceededThreshold(); } 463 464 /** 465 * Processes the given performance tracker. 466 * 467 * @param tracker The tracker to process. 468 */ 469 public final void processTracker( final PerformanceTrackerImpl tracker ) 470 { 471 try( final var _ = m_WriteGuard.lock() ) 472 { 473 if( m_PerformanceSection.equals( requireNonNullArgument( tracker, "tracker" ).getPerformanceSection() ) ) 474 { 475 m_LastUpdated = Instant.now(); 476 if( isNull( m_FirstStart ) || tracker.getTimestamp().isBefore( m_FirstStart ) ) m_FirstStart = tracker.getTimestamp(); 477 } 478 479 tracker.getElapsedTime().ifPresent( v -> 480 { 481 m_CumulatedExecutionTime += v.convert( Time.MILLISECOND ).longValue(); 482 ++m_NumberOfCompletedRuns; 483 484 if( tracker.isThresholdExceeded() ) ++m_NumberOfRunsThatExceededThreshold; 485 }); 486 487 if( tracker.isTimedOut() ) 488 { 489 ++m_NumberOfTimedOutRuns; 490 } 491 else if( tracker.getStatus() == STATUS_ABORTED ) 492 { 493 ++m_NumberOfAbortedRuns; 494 } 495 } 496 } // processTracker() 497 498 /** 499 * {@inheritDoc} 500 */ 501 @Override 502 public final String toString() 503 { 504 final var buffer = new StringJoiner( ",", "PerformanceSectionInfo{", "}" ) 505 .add( "m_PerformanceSection=".concat( m_PerformanceSection.toString() ) ) 506 .add( "m_CumulatedExecutionTime=%dms".formatted( m_CumulatedExecutionTime ) ) 507 .add( "m_FirstStart=".concat( m_FirstStart.toString() ) ) 508 .add( "m_LastUpdated=".concat( m_LastUpdated.toString() ) ) 509 .add( "m_NumberOfAbortedRuns=%d".formatted( m_NumberOfAbortedRuns ) ) 510 .add( "m_NumberOfCompletedRuns=%d".formatted( m_NumberOfCompletedRuns ) ) 511 .add( "m_NumberOfRunsThatExceededThreshold=%d".formatted( m_NumberOfRunsThatExceededThreshold ) ) 512 .add( "m_NumberOfTimedOutRuns=%d".formatted( m_NumberOfTimedOutRuns ) ); 513 final var retValue = buffer.toString(); 514 515 //---* Done *---------------------------------------------------------- 516 return retValue; 517 } // toString() 518} 519// class PerformanceSectionInfo 520 521/* 522 * End of File 523 */