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; 020 021import static java.util.Arrays.asList; 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.lang.Objects.requireValidArgument; 027import static org.tquadrat.foundation.perflog.PerfLogUtils.createPerformanceSectionName; 028import static org.tquadrat.foundation.perflog.PerformanceSection.PerformanceSectionFlags.IGNORED; 029import static org.tquadrat.foundation.perflog.PerformanceSection.PerformanceSectionFlags.SEND_REPORT_FOR_ABORT; 030import static org.tquadrat.foundation.perflog.PerformanceSection.PerformanceSectionFlags.SEND_REPORT_ONLY_FOR_EXCEEDED_THRESHOLD; 031import static org.tquadrat.foundation.util.StringUtils.mapFromEmpty; 032import static org.tquadrat.foundation.value.Time.MILLISECOND; 033 034import java.util.EnumSet; 035import java.util.Optional; 036import java.util.OptionalLong; 037import java.util.Set; 038import java.util.concurrent.locks.ReentrantReadWriteLock; 039 040import org.apiguardian.api.API; 041import org.tquadrat.foundation.annotation.ClassVersion; 042import org.tquadrat.foundation.exception.ValidationException; 043import org.tquadrat.foundation.lang.AutoLock; 044import org.tquadrat.foundation.lang.AutoLock.ExecutionFailedException; 045import org.tquadrat.foundation.value.TimeValue; 046 047/** 048 * <p>{@summary This class describes a "Performance Section".}</p> 049 * 050 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 051 * @version $Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $ 052 * @since 0.25.0 053 * 054 * @UMLGraph.link 055 */ 056@ClassVersion( sourceVersion = "$Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 057@API( status = STABLE, since = "0.25.0" ) 058public final class PerformanceSection 059{ 060 /*---------------*\ 061 ====** Inner Classes **==================================================== 062 \*---------------*/ 063 /** 064 * <p>{@summary The ignore status for a performance section.}</p> 065 * 066 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 067 * @version $Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $ 068 * @since 0.25.0 069 * 070 * @UMLGraph.link 071 */ 072 @ClassVersion( sourceVersion = "$Id: PerformanceSection.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 073 @API( status = STABLE, since = "0.25.0" ) 074 public static enum PerformanceSectionFlags 075 { 076 /** 077 * The performance section will be ignored. 078 */ 079 IGNORED, 080 081 /** 082 * Indicates that a report should be sent also for aborted performance 083 * trackers. 084 */ 085 SEND_REPORT_FOR_ABORT, 086 087 /** 088 * Indicates that reports should be sent only when the threshold was 089 * exceeded. 090 */ 091 @SuppressWarnings( "FieldNamingConvention" ) SEND_REPORT_ONLY_FOR_EXCEEDED_THRESHOLD 092 } 093 // enum PerformanceSectionFlags 094 095 /*------------*\ 096 ====** Attributes **======================================================= 097 \*------------*/ 098 /** 099 * The description for the performance section. 100 */ 101 private final String m_Description; 102 103 /** 104 * The flog that controls whether this performance section is ignored. 105 */ 106 private boolean m_Ignore; 107 108 /** 109 * The flag that controls whether reports for this performance section are 110 * sent in case of an abort. 111 */ 112 private boolean m_SendReportForAbort; 113 114 /** 115 * The flag that controls whether reports for this performance section are 116 * sent only when the threshold was exceeded. 117 */ 118 @SuppressWarnings( "FieldNamingConvention" ) 119 private boolean m_SendReportOnlyForExceededThreshold; 120 121 /** 122 * The unique name of the performance section. 123 */ 124 private final PerformanceSectionName m_Name; 125 126 /** 127 * The guard for read operations. 128 */ 129 private final AutoLock m_ReadGuard; 130 131 /** 132 * <p>{@summary The threshold time.} A value of {@null} means that 133 * there is no threshold for this performance section.</p> 134 */ 135 @SuppressWarnings( "UnusedAssignment" ) 136 private TimeValue m_Threshold = null; 137 138 /** 139 * <p>{@summary The timeout time.} A value of {@null} means that 140 * there is no timeout for this performance section.</p> 141 */ 142 @SuppressWarnings( "UnusedAssignment" ) 143 private TimeValue m_Timeout = null; 144 145 /** 146 * The guard for write operations. 147 */ 148 private final AutoLock m_WriteGuard; 149 150 /*--------------*\ 151 ====** Constructors **===================================================== 152 \*--------------*/ 153 /** 154 * Creates a new instance of {@code PerformanceSection}. 155 * 156 * @param name The unique name of the performance section. 157 * @param description The description for the performance section; can be 158 * {@null}. 159 * @param threshold The threshold time in milliseconds; a value of 160 * {@null} means that no threshold was defined for this 161 * performance section. A value less than 1 is invalid and causes a 162 * {@link org.tquadrat.foundation.exception.ValidationException} 163 * to be thrown. 164 * @param timeout The timeout time in milliseconds; a value of 165 * {@null} means that no timeout was defined for this performance 166 * section. A value less than 1 is invalid and causes a 167 * {@link org.tquadrat.foundation.exception.ValidationException} 168 * to be thrown. The timeout value must be greater than the threshold 169 * value, if provided. 170 * @param flags Provides the configuration for this performance 171 * section. 172 */ 173 public PerformanceSection( final String name, final String description, final Long threshold, final Long timeout, final PerformanceSectionFlags... flags ) 174 { 175 this( 176 createPerformanceSectionName( name ), 177 description, 178 isNull( threshold ) ? null : new TimeValue( MILLISECOND, requireValidArgument( threshold, "threshold", t -> t > 0L ) ), 179 isNull( timeout ) ? null : new TimeValue( MILLISECOND, requireValidArgument( timeout, "timeout", t -> t > 0L ) ), 180 flags 181 ); 182 } // PerformanceSection() 183 184 /** 185 * Creates a new instance of {@code PerformanceSection}. 186 * 187 * @param name The unique name of the performance section. 188 * @param description The description for the performance section; can be 189 * {@null}. 190 * @param threshold The threshold time; a value of {@null} means 191 * that no threshold was defined for this performance section. A value 192 * of 0 is invalid and causes a 193 * {@link org.tquadrat.foundation.exception.ValidationException} 194 * to be thrown. 195 * @param timeout The timeout time; a value of {@null} means that 196 * no timeout was defined for this performance section. A value of 0 197 * is invalid and causes a 198 * {@link org.tquadrat.foundation.exception.ValidationException} 199 * to be thrown. The timeout value must be greater than the threshold 200 * value, if provided. 201 * @param flags Provides the configuration for this performance 202 * section. 203 */ 204 public PerformanceSection( final PerformanceSectionName name, final String description, final TimeValue threshold, final TimeValue timeout, final PerformanceSectionFlags... flags ) 205 { 206 m_Description = mapFromEmpty( description, "?" ); 207 final Set<PerformanceSectionFlags> flagsSet = EnumSet.noneOf( PerformanceSectionFlags.class ); 208 flagsSet.addAll( asList( requireNonNullArgument( flags, "flags" ) ) ); 209 m_Ignore = flagsSet.contains( IGNORED ); 210 m_Name = requireNonNullArgument( name, "name" ); 211 m_SendReportForAbort = flagsSet.contains( SEND_REPORT_FOR_ABORT ); 212 m_SendReportOnlyForExceededThreshold = flagsSet.contains( SEND_REPORT_ONLY_FOR_EXCEEDED_THRESHOLD ); 213 m_Threshold = validateThresholdAndTimeout( threshold, threshold, null ); 214 m_Timeout = validateThresholdAndTimeout( timeout, threshold, timeout ); 215 216 final var lock = new ReentrantReadWriteLock(); 217 m_ReadGuard = AutoLock.of( lock.readLock() ); 218 m_WriteGuard = AutoLock.of( lock.writeLock() ); 219 } // PerformanceSection() 220 221 /*---------*\ 222 ====** Methods **========================================================== 223 \*---------*/ 224 /** 225 * {@inheritDoc} 226 * <p>Two instances of {@code PerformanceSection} are equal if their names 227 * are equal.</p> 228 */ 229 @Override 230 public final boolean equals( final Object o ) 231 { 232 var retValue = this == o; 233 if( !retValue && o instanceof final PerformanceSection other ) 234 { 235 retValue = getName().equals( other.getName() ); 236 } 237 238 //---* Done *---------------------------------------------------------- 239 return retValue; 240 } // equals() 241 242 /** 243 * Returns the description for the performance section. 244 * 245 * @return The description for the performance section. 246 */ 247 public final String getDescription() { return m_Description; } 248 249 /** 250 * Returns the name of the performance section. 251 * 252 * @return The name of the performance section. 253 */ 254 public final PerformanceSectionName getName() { return m_Name; } 255 256 /** 257 * Returns the threshold for this performance section. 258 * 259 * @return An instance of 260 * {@link OptionalLong} 261 * that holds the threshold in milliseconds. 262 */ 263 public final Optional<TimeValue> getThreshold() 264 { 265 final var retValue = m_ReadGuard.execute( () -> m_Threshold ); 266 267 //---* Done *---------------------------------------------------------- 268 return retValue; 269 } // getThreshold() 270 271 /** 272 * Returns the timeout for this performance section. 273 * 274 * @return An instance of 275 * {@link OptionalLong} 276 * that holds the timeout in milliseconds. 277 */ 278 public final Optional<TimeValue> getTimeout() 279 { 280 final var retValue = m_ReadGuard.execute( () -> m_Timeout ); 281 282 //---* Done *---------------------------------------------------------- 283 return retValue; 284 } // getTimeout() 285 286 /** 287 * {@inheritDoc} 288 * <p>Two instances of {@code PerformanceSection} are equal if their names 289 * are equal.</p> 290 */ 291 @Override 292 public final int hashCode() { return getName().hashCode(); } 293 294 /** 295 * Returns the flag that controls whether this performance section is 296 * ignored. 297 * 298 * @return {@true} if the performance section is currently ignored, 299 * {@false} if the performance section is currently observed. 300 */ 301 public final boolean isIgnored() { return m_ReadGuard.evaluate( () -> m_Ignore ); } 302 303 /** 304 * Returns the flag that indicates whether reports should be sent if a 305 * {@link PerformanceTracker} 306 * is aborted. 307 * 308 * @return {@true} if a report should be sent, {@false} if not. 309 * 310 * @see PerformanceTracker#abort(String) 311 * @see PerformanceTracker#abort(String, Throwable) 312 */ 313 public final boolean isSendingReportForAbort() { return m_ReadGuard.evaluate( () -> m_SendReportForAbort ); } 314 315 /** 316 * Returns the flag that indicates whether reports should be sent only if 317 * the threshold was exceeded. 318 * 319 * @return {@true} if a report should be sent only when the threshold 320 * was exceeded, {@false} if a report should be sent always. 321 */ 322 @SuppressWarnings( "NewMethodNamingConvention" ) 323 public final boolean isSendingReportOnlyForExceededThreshold() { return m_ReadGuard.evaluate( () -> m_SendReportOnlyForExceededThreshold ); } 324 325 /** 326 * Sets the flag that controls whether this performance section is 327 * ignored. 328 * 329 * @param flag {@true} if the performance section should be 330 * ignored from now on, {@false} if it has to be observed in 331 * future. 332 */ 333 public final void setIgnoreFlag( final boolean flag ) { m_WriteGuard.perform( () -> m_Ignore = flag ); } 334 335 /** 336 * Sets the flag that controls whether reports are sent also for aborted 337 * {@linkplain PerformanceTracker performance trackers}. 338 * 339 * @param flag {@true} if reports should be sent, {@false} if 340 * not. 341 */ 342 public final void setSendReportForAbortFlag( final boolean flag ) 343 { 344 m_WriteGuard.perform( () -> m_SendReportForAbort = flag ); 345 } // setSendReportForAbortFlag() 346 347 /** 348 * Sets the flag that controls whether reports are sent only when the 349 * threshold was exceeded. 350 * 351 * @param flag {@true} if reports should be sent only when the 352 * threshold was exceeded, {@false} if reports should be sent 353 * always. 354 */ 355 @SuppressWarnings( "NewMethodNamingConvention" ) 356 public final void setSendReportOnlyForExceededThresholdFlag( final boolean flag ) 357 { 358 m_WriteGuard.perform( () -> m_SendReportOnlyForExceededThreshold = flag ); 359 } // setSendReportOnlyForExceededThresholdFlag() 360 361 /** 362 * Sets the threshold and enables it. 363 * 364 * @param value The new threshold value; must be greater than 0. 365 */ 366 public final void setThreshold( final TimeValue value ) 367 { 368 try 369 { 370 m_WriteGuard.perform( () -> m_Threshold = validateThreshold( value ) ); 371 } 372 catch( final ExecutionFailedException e ) 373 { 374 final var cause = e.getCause(); 375 if( cause instanceof final IllegalArgumentException iae ) throw iae; 376 throw e; 377 } 378 } // setThreshold() 379 380 /** 381 * Sets the timeout and enables it. 382 * 383 * @param value The new timeout value; must be greater than 0. 384 */ 385 public final void setTimeout( final TimeValue value ) 386 { 387 try 388 { 389 m_WriteGuard.perform( () -> m_Timeout = validateTimeout( value ) ); 390 } 391 catch( final ExecutionFailedException e ) 392 { 393 final var cause = e.getCause(); 394 if( cause instanceof final IllegalArgumentException iae ) throw iae; 395 throw e; 396 } 397 } // setTimeout() 398 399 /** 400 * Disables the threshold for this performance section. 401 */ 402 public final void switchOffThreshold() { m_WriteGuard.perform( () -> m_Threshold = null ); } 403 404 /** 405 * Disables the timeout for this performance section. 406 */ 407 public final void switchOffTimeout() { m_WriteGuard.perform( () -> m_Timeout = null ); } 408 409 /** 410 * {@inheritDoc} 411 */ 412 @Override 413 public final String toString() 414 { 415 final var buffer = new StringBuilder( "PerformanceSection{" ) 416 .append( "m_Name='" ).append( m_Name ).append( "', " ) 417 .append( "m_Description='" ).append( m_Description ).append( "', " ) 418 .append( "m_Threshold=" ); 419 try( final var _ = m_ReadGuard.lock() ) 420 { 421 if( nonNull( m_Threshold ) ) 422 { 423 buffer.append( "%s".formatted( m_Threshold ) ); 424 } 425 else 426 { 427 buffer.append( "none" ); 428 } 429 buffer.append( ", m_Timeout=" ); 430 if( nonNull( m_Timeout ) ) 431 { 432 buffer.append( "%s".formatted( m_Timeout ) ); 433 } 434 else 435 { 436 buffer.append( "none" ); 437 } 438 buffer.append( ", m_Ignore=" ).append( m_Ignore ) 439 .append( ", m_SendReportForAbort=" ).append( m_SendReportForAbort ) 440 .append( ", m_SendReportOnlyForExceededThreshold=" ).append( m_SendReportOnlyForExceededThreshold ) 441 .append( '}' ); 442 } 443 final var retValue = buffer.toString(); 444 445 //---* Done *---------------------------------------------------------- 446 return retValue; 447 } // toString() 448 449 /** 450 * <p>{@summary Validates the given threshold.}</p> 451 * <p>The value must be greater than 0, and, if the 452 * timeout is not {@null}, it must be less than the timeout.</p> 453 * 454 * @note The method is called from inside a guarded area only! 455 * 456 * @param value The value for the threshold. 457 * @return The given value. 458 * @throws IllegalArgumentException The value is invalid. 459 */ 460 private final TimeValue validateThreshold( final TimeValue value ) throws IllegalArgumentException 461 { 462 final var retValue = validateThresholdAndTimeout( requireNonNullArgument( value, "threshold" ), value, m_Timeout ); 463 464 //---* Done *---------------------------------------------------------- 465 return retValue; 466 } // validateThreshold() 467 468 /** 469 * <p>{@summary Validates threshold and timeout.}</p> 470 * <p>Both values must be either -1 or greater than 0, and, if the 471 * threshold is not -1, the timeout must be greater than the 472 * threshold.</p> 473 * 474 * @param retValue The return value. 475 * @param threshold The value for the threshold. 476 * @param timeout The value for the timeout. 477 * @return The return value. 478 * @throws IllegalArgumentException A value is invalid. 479 */ 480 @SuppressWarnings( "OverlyComplexMethod" ) 481 private static final TimeValue validateThresholdAndTimeout( final TimeValue retValue, final TimeValue threshold, final TimeValue timeout ) throws IllegalArgumentException 482 { 483 if( nonNull( retValue ) && !retValue.equals( threshold ) && !retValue.equals( timeout ) ) throw new IllegalArgumentException( "return value is neither threshold nor timeout" ); 484 if( nonNull( threshold ) && threshold.value().longValue() < 1L ) throw new ValidationException( "threshold value '%s' is invalid".formatted( threshold ) ); 485 if( nonNull( timeout ) && timeout.value().longValue() < 1L ) throw new ValidationException( "timeout value '%s' is invalid".formatted( timeout ) ); 486 if( nonNull( timeout ) && nonNull( threshold ) && timeout.baseValue().compareTo( threshold.baseValue() ) <= 0 ) throw new ValidationException( "timeout value '%s' is not greater than threshold value '%s'".formatted( timeout, threshold ) ); 487 488 //---* Done *---------------------------------------------------------- 489 return retValue; 490 } // validateThresholdAndTimeout() 491 492 /** 493 * <p>{@summary Validates the given timeout.}</p> 494 * <p>The value must be greater than 0, and, if the threshold is not 495 * {@null}, the timeout must be greater than the threshold.</p> 496 * 497 * @note The method is called from inside a guarded area only! 498 * 499 * @param value The value for the timeout. 500 * @return The given value. 501 * @throws IllegalArgumentException The value is invalid. 502 */ 503 private final TimeValue validateTimeout( final TimeValue value ) throws IllegalArgumentException 504 { 505 final var retValue = validateThresholdAndTimeout( requireNonNullArgument( value, "timeout" ), m_Threshold, value ); 506 507 //---* Done *---------------------------------------------------------- 508 return retValue; 509 } // validateThresholdAndTimeout() 510} 511// class PerformanceSection 512 513/* 514 * End of File 515 */