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.Boolean.TRUE; 022import static java.lang.System.currentTimeMillis; 023import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME; 024import static java.util.concurrent.Executors.newThreadPerTaskExecutor; 025import static java.util.concurrent.TimeUnit.MINUTES; 026import static org.apiguardian.api.API.Status.INTERNAL; 027import static org.tquadrat.foundation.lang.Objects.isNull; 028import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 029import static org.tquadrat.foundation.lang.Objects.requireNotBlankArgument; 030import static org.tquadrat.foundation.perflog.PerfLogUtils.createPerformanceSectionName; 031import static org.tquadrat.foundation.perflog.PerfLogUtils.getPerfLogMBeanObjectName; 032import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_AbortedRuns; 033import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_CompletedRuns; 034import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_Error; 035import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_FirstStart; 036import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_LastUpdated; 037import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_Message; 038import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_Section; 039import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_SectionDescription; 040import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_SectionIgnored; 041import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_SectionName; 042import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_SectionStatistics; 043import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_SectionThreshold; 044import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_SectionThresholdOnlyReport; 045import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_SectionTimeout; 046import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_Success; 047import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_ThresholdExceededRuns; 048import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.JSONField_TimedOutRuns; 049import static org.tquadrat.foundation.perflog.remote.PerfLogRemote.NOTIFICATION_Type; 050import static org.tquadrat.foundation.util.StringUtils.isNotEmptyOrBlank; 051import static org.tquadrat.foundation.value.Time.MILLISECOND; 052 053import javax.management.Descriptor; 054import javax.management.ImmutableDescriptor; 055import javax.management.ListenerNotFoundException; 056import javax.management.MBeanInfo; 057import javax.management.MBeanNotificationInfo; 058import javax.management.MBeanOperationInfo; 059import javax.management.Notification; 060import javax.management.NotificationBroadcasterSupport; 061import javax.management.NotificationFilter; 062import javax.management.NotificationListener; 063import javax.management.StandardMBean; 064import javax.management.openmbean.ArrayType; 065import javax.management.openmbean.OpenDataException; 066import javax.management.openmbean.OpenMBeanAttributeInfo; 067import javax.management.openmbean.OpenMBeanAttributeInfoSupport; 068import javax.management.openmbean.OpenMBeanConstructorInfo; 069import javax.management.openmbean.OpenMBeanInfoSupport; 070import javax.management.openmbean.OpenMBeanOperationInfo; 071import javax.management.openmbean.OpenMBeanOperationInfoSupport; 072import javax.management.openmbean.OpenMBeanParameterInfo; 073import javax.management.openmbean.OpenMBeanParameterInfoSupport; 074import javax.management.openmbean.SimpleType; 075import java.time.Instant; 076import java.util.ArrayList; 077import java.util.Collection; 078import java.util.HashMap; 079import java.util.LinkedList; 080import java.util.List; 081import java.util.Map; 082import java.util.Optional; 083import java.util.concurrent.ExecutorService; 084import java.util.concurrent.atomic.AtomicLong; 085import java.util.concurrent.locks.ReentrantReadWriteLock; 086 087import org.apiguardian.api.API; 088import org.tquadrat.foundation.annotation.ClassVersion; 089import org.tquadrat.foundation.exception.ImpossibleExceptionError; 090import org.tquadrat.foundation.jsonbuilder.JSONBuilder; 091import org.tquadrat.foundation.jsonbuilder.JSONObject; 092import org.tquadrat.foundation.lang.AutoLock; 093import org.tquadrat.foundation.lang.AutoLock.ExecutionFailedException; 094import org.tquadrat.foundation.perflog.PerfLogMBean; 095import org.tquadrat.foundation.perflog.PerformanceReport; 096import org.tquadrat.foundation.perflog.PerformanceSection; 097import org.tquadrat.foundation.perflog.PerformanceSectionName; 098 099/** 100 * <p>{@summary The implementation of the interface 101 * {@link PerfLogMBean}.}</p> 102 * 103 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 104 * @version $Id: PerfLogMBeanImpl.java 1258 2026-06-04 18:33:06Z tquadrat $ 105 * @since 0.25.0 106 * 107 * @UMLGraph.link 108 */ 109@ClassVersion( sourceVersion = "$Id: PerfLogMBeanImpl.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 110@API( status = INTERNAL, since = "0.25.0" ) 111public class PerfLogMBeanImpl extends StandardMBean implements PerfLogMBean 112{ 113 /*------------*\ 114 ====** Attributes **======================================================= 115 \*------------*/ 116 /** 117 * The last 10 exceptions that caused notification threads to abort. 118 */ 119 private final List<String> m_Exceptions = new LinkedList<>(); 120 121 /** 122 * The instance of 123 * {@link JSONBuilder} 124 * used by this MBean. 125 */ 126 private final JSONBuilder m_JSONBuilder; 127 128 /** 129 * <p>{@summary The sequence number for the notifications.} The initial 130 * value will be the current time in seconds since the beginning of the 131 * epoche, rounded down to ten seconds and multiplied with 10k.</p> 132 */ 133 private final AtomicLong m_NotificationSequenceNumber; 134 135 /** 136 * The implementation of 137 * {@link javax.management.NotificationEmitter} 138 * that is utilised by this MBean instance. 139 */ 140 private final NotificationBroadcasterSupport m_NotificationBroadcasterSupport; 141 142 /** 143 * <p>{@summary The registry for the performance section info 144 * instances.}</p> 145 * <p>The key for the map is the name of the performance section.</p> 146 */ 147 private final Map<PerformanceSectionName,PerformanceSectionInfo> m_PerfSectionRegistry = new HashMap<>(); 148 149 /** 150 * The guard for read operations on the performance section registry. 151 */ 152 private final AutoLock m_PerfRegistryReadGuard; 153 154 /** 155 * The guard for write operations on the performance section registry. 156 */ 157 private final AutoLock m_PerfRegistryWriteGuard; 158 159 /** 160 * The thread pool that is used to send the notifications. 161 * 162 * @see #m_NotificationBroadcasterSupport 163 */ 164 private final ExecutorService m_ThreadPool; 165 166 /*--------------*\ 167 ====** Constructors **===================================================== 168 \*--------------*/ 169 /** 170 * Creates a new instance of {@code PerfLogMBeanImpl}. 171 */ 172 public PerfLogMBeanImpl() 173 { 174 super( PerfLogMBean.class, false ); 175 176 m_JSONBuilder = JSONBuilder.getInstance(); 177 178 final var lock = new ReentrantReadWriteLock(); 179 m_PerfRegistryReadGuard = AutoLock.of( lock.readLock() ); 180 m_PerfRegistryWriteGuard = AutoLock.of( lock.writeLock() ); 181 182 //---* Create the ExecutorService for the notifications *-------------- 183 final var threadName = "NotificationThread"; 184 final var factory = Thread.ofVirtual() 185 .name( threadName, 0L ) 186 .uncaughtExceptionHandler( this::uncaughtExceptionHandler ) 187 .factory(); 188 m_ThreadPool = newThreadPerTaskExecutor( factory ); 189 190 //---* Create the NotificationBroadcasterSupport *--------------------- 191 final var notificationInfo = getMBeanInfo().getNotifications(); 192 m_NotificationSequenceNumber = new AtomicLong( currentTimeMillis() / 10_000 * 10_000 ); 193 m_NotificationBroadcasterSupport = new NotificationBroadcasterSupport( m_ThreadPool, notificationInfo ); 194 } // PerfLogMBeanImpl() 195 196 /*---------*\ 197 ====** Methods **========================================================== 198 \*---------*/ 199 /** 200 * Adds an error message to the given 201 * {@link JSONObject}. 202 * 203 * @param object The JSON object. 204 * @param message The error message. 205 */ 206 private final void addErrorToJSON( final JSONObject object, final String message ) 207 { 208 final var error = requireNonNullArgument( object, "object" ).setObject( JSONField_Error ); 209 error.set( JSONField_Message, requireNotBlankArgument( message, "message" ) ); 210 } // addErrorToJSON() 211 212 /** 213 * Adds a success message to the given 214 * {@link JSONObject}. 215 * 216 * @param object The JSON object. 217 * @param message The error message. 218 */ 219 private final void addSuccessToJSON( final JSONObject object, final String message ) 220 { 221 final var error = requireNonNullArgument( object, "object" ).setObject( JSONField_Success ); 222 error.set( JSONField_Message, requireNotBlankArgument( message, "message" ) ); 223 } // addErrorToJSON() 224 225 /** 226 * {@inheritDoc} 227 */ 228 @Override 229 public final void addNotificationListener( final NotificationListener listener, final NotificationFilter filter, final Object handback ) 230 { 231 m_NotificationBroadcasterSupport.addNotificationListener( listener, filter, handback ); 232 } // addNotificationListener() 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override 238 public final void addPerformanceSection( final PerformanceSection definition ) 239 { 240 try 241 { 242 m_PerfRegistryWriteGuard.execute( () -> m_PerfSectionRegistry.put( requireNonNullArgument( definition, "definition" ).getName(), new PerformanceSectionInfo( definition ) ) ); 243 } 244 catch( final ExecutionFailedException e ) 245 { 246 final var cause = e.getCause(); 247 if( cause instanceof final IllegalArgumentException iae ) throw iae; 248 throw e; 249 } 250 } // addPerformanceSection() 251 252 /** 253 * Builds the attribute info list for 254 * {@link #getMBeanInfo()}. 255 * 256 * @return The attribute info list. 257 */ 258 private final OpenMBeanAttributeInfo [] buildAttributeInfoList() 259 { 260 final Collection<OpenMBeanAttributeInfo> buffer = new ArrayList<>(); 261 262 try 263 { 264 //---* The list of notification exceptions *----------------------- 265 buffer.add( new OpenMBeanAttributeInfoSupport( 266 "NotificationExceptions", 267 "The last 10 exceptions thrown by a notification thread", 268 new ArrayType<String []>( SimpleType.STRING, false ), 269 true, // readable 270 false, // not writable 271 false // standard get naming 272 ) ); 273 274 //---* The notification sequence number *-------------------------- 275 buffer.add( new OpenMBeanAttributeInfoSupport( 276 "NotificationSequenceNumber", 277 "The last sequence number used for a notification", 278 SimpleType.LONG, 279 true, // readable 280 false, // not writable 281 false // standard get naming 282 ) ); 283 284 //---* The list of currently defined performance sections *-------- 285 buffer.add( new OpenMBeanAttributeInfoSupport( 286 "PerformanceSections", 287 "The currently known Performance Sections", 288 new ArrayType<String []>( SimpleType.STRING, false ), 289 true, // readable 290 false, // not writable 291 false // standard get naming 292 ) ); 293 } 294 catch( final OpenDataException e ) 295 { 296 throw new ImpossibleExceptionError( e ); 297 } 298 299 final var retValue = buffer.toArray( OpenMBeanAttributeInfo []::new ); 300 301 //---* Done *---------------------------------------------------------- 302 return retValue; 303 } // buildAttributeInfoList() 304 305 /** 306 * Builds the notifications info list for 307 * {@link #getMBeanInfo()}. 308 * 309 * @return The notifications info list. 310 */ 311 private final MBeanNotificationInfo [] buildNotificationInfoList() 312 { 313 final Collection<MBeanNotificationInfo> buffer = new ArrayList<>(); 314 315 buffer.add( new MBeanNotificationInfo( new String[]{NOTIFICATION_Type}, Notification.class.getName(), NOTIFICATION_Description ) ); 316 317 final var retValue = buffer.toArray( MBeanNotificationInfo []::new ); 318 319 //---* Done *---------------------------------------------------------- 320 return retValue; 321 } // buildNotificationInfoList() 322 323 /** 324 * Builds the operation info list for 325 * {@link #getMBeanInfo()}. 326 * 327 * @return The operation info list. 328 */ 329 private final OpenMBeanOperationInfo [] buildOperationInfoList() 330 { 331 final Collection<OpenMBeanOperationInfo> buffer = new ArrayList<>(); 332 333// try 334// { 335 //---* The status for a performance sections *--------------------- 336 var params = new OpenMBeanParameterInfo[] 337 { new OpenMBeanParameterInfoSupport( "name", "The Name of the Performance Section to inspect", SimpleType.STRING ) }; 338 buffer.add( new OpenMBeanOperationInfoSupport( 339 "showPerformanceSection", 340 "Shows the current status of the given Performance Section", 341 params, 342 SimpleType.STRING, 343 MBeanOperationInfo.INFO 344 ) ); 345 346 //---* Enable a performance sections *----------------------------- 347 params = new OpenMBeanParameterInfo[] 348 { new OpenMBeanParameterInfoSupport( "name", "The Name of the Performance Section to enable", SimpleType.STRING ) }; 349 buffer.add( new OpenMBeanOperationInfoSupport( 350 "enablePerformanceSection", 351 "Enable the given Performance Section", 352 params, 353 SimpleType.STRING, 354 MBeanOperationInfo.ACTION 355 ) ); 356 357 //---* Disable a performance sections *---------------------------- 358 params = new OpenMBeanParameterInfo[] 359 { new OpenMBeanParameterInfoSupport( "name", "The Name of the Performance Section to disable", SimpleType.STRING ) }; 360 buffer.add( new OpenMBeanOperationInfoSupport( 361 "disablePerformanceSection", 362 "Disable the given Performance Section", 363 params, 364 SimpleType.STRING, 365 MBeanOperationInfo.ACTION 366 ) ); 367// } 368// catch( final OpenDataException e ) 369// { 370// throw new ImpossibleExceptionError( e ); 371// } 372 373 final var retValue = buffer.toArray( OpenMBeanOperationInfo []::new ); 374 375 //---* Done *---------------------------------------------------------- 376 return retValue; 377 } // buildOperationInfoList() 378 379 /** 380 * {@inheritDoc} 381 */ 382 @Override 383 public String disablePerformanceSection( final String name ) 384 { 385 final var json = m_JSONBuilder.createObject(); 386 if( isNotEmptyOrBlank( name ) ) 387 { 388 try 389 { 390 final var performanceSectionName = createPerformanceSectionName( name ); 391 final var performanceSection = getPerformanceSection( performanceSectionName ); 392 if( performanceSection.isPresent() ) 393 { 394 final var ps = performanceSection.get(); 395 if( ps.isIgnored() ) 396 { 397 addSuccessToJSON( json, "Performance Section '%s' is already disabled".formatted( name ) ); 398 } 399 else 400 { 401 ps.setIgnoreFlag( true ); 402 addSuccessToJSON( json, "Performance Section '%s' successfully disabled".formatted( name ) ); 403 } 404 } 405 else 406 { 407 addErrorToJSON( json, "No entry for this name: %s".formatted( name ) ); 408 } 409 } 410 catch( final IllegalArgumentException e ) 411 { 412 addErrorToJSON( json, "The given name '%s' is invalid: %s".formatted( name, e.getMessage() ) ); 413 } 414 } 415 else 416 { 417 addErrorToJSON( json, "The given name is null, empty or blank" ); 418 } 419 420 final var retValue = json.toString(); 421 422 //---* Done *---------------------------------------------------------- 423 return retValue; 424 } // disablePerformanceSection() 425 426 /** 427 * {@inheritDoc} 428 */ 429 @Override 430 public String enablePerformanceSection( final String name ) 431 { 432 final var json = m_JSONBuilder.createObject(); 433 if( isNotEmptyOrBlank( name ) ) 434 { 435 try 436 { 437 final var performanceSectionName = createPerformanceSectionName( name ); 438 final var performanceSection = getPerformanceSection( performanceSectionName ); 439 if( performanceSection.isPresent() ) 440 { 441 final var ps = performanceSection.get(); 442 if( !ps.isIgnored() ) 443 { 444 addSuccessToJSON( json, "Performance Section '%s' is already enabled".formatted( name ) ); 445 } 446 else 447 { 448 ps.setIgnoreFlag( false ); 449 addSuccessToJSON( json, "Performance Section '%s' successfully enabled".formatted( name ) ); 450 } 451 } 452 else 453 { 454 addErrorToJSON( json, "No entry for this name: %s".formatted( name ) ); 455 } 456 } 457 catch( final IllegalArgumentException e ) 458 { 459 addErrorToJSON( json, "The given name '%s' is invalid: %s".formatted( name, e.getMessage() ) ); 460 } 461 } 462 else 463 { 464 addErrorToJSON( json, "The given name is null, empty or blank" ); 465 } 466 467 final var retValue = json.toString(); 468 469 //---* Done *---------------------------------------------------------- 470 return retValue; 471 } // enablePerformanceSection() 472 473 /** 474 * {@inheritDoc} 475 */ 476 @Override 477 public final String[] getNotificationExceptions() 478 { 479 final var retValue = m_Exceptions.toArray( String []::new ); 480 481 //---* Done *---------------------------------------------------------- 482 return retValue; 483 } // getNotificationExceptions() 484 485 /** 486 * <p>{@summary Get the 487 * {@link MBeanInfo} 488 * for this MBean.} That is in fact an instance of 489 * {@link OpenMBeanInfoSupport} 490 * as an implementation of 491 * {@link javax.management.openmbean.OpenMBeanInfo}. 492 * This identifies this MBean as an Open MBean.</p> 493 * <p>This method implements 494 * {@link javax.management.DynamicMBean#getMBeanInfo() DynamicMBean.getMBeanInfo()} 495 * and overwrites the 496 * {@linkplain StandardMBean#getMBeanInfo() implementation} 497 * from 498 * {@link StandardMBean}.</p> 499 * <p>This method first calls 500 * {@link #getCachedMBeanInfo()} 501 * in order to retrieve the cached {@code MBeanInfo} for this MBean, if 502 * any. If the result from this call is not {@null}, it will be 503 * returned.</p> 504 * <p>Otherwise, this method builds a {@code MBeanInfo} for this MBean 505 * from scratch.</p> 506 * <p>Finally, it calls 507 * {@link #cacheMBeanInfo(javax.management.MBeanInfo) cacheMBeanInfo()} 508 * in order to cache the new MBeanInfo for subsequent calls. 509 * 510 * @return The cached {@code MBeanInfo} for this MBean, if not 511 * {@null}, or a newly built {@code MBeanInfo} if none was 512 * cached. 513 */ 514 @SuppressWarnings( "UnnecessaryJavaDocLink" ) 515 @Override 516 public final MBeanInfo getMBeanInfo() 517 { 518 var retValue = getCachedMBeanInfo(); 519 if( isNull( retValue ) ) 520 { 521 final var className = getClass().getName(); 522 523 //---* Set the attributes *---------------------------------------- 524 final var attributes = buildAttributeInfoList(); 525 526 //---* Set the constructors *-------------------------------------- 527 /* 528 * We do not publish the constructors for this MBean. 529 */ 530 final OpenMBeanConstructorInfo[] constructors = null; 531 532 //---* Set the operations *---------------------------------------- 533 final var operations = buildOperationInfoList(); 534 535 //---* Set the notification infos *-------------------------------- 536 final var notificationInfos = buildNotificationInfoList(); 537 538 //---* Set the descriptor *---------------------------------------- 539 final Map<String,?> descriptorFields = Map.of( "immutableInfo", TRUE.toString() ); 540 final Descriptor descriptor = new ImmutableDescriptor( descriptorFields ); 541 542 //---* Create the return value *----------------------------------- 543 //noinspection ConstantValue 544 retValue = new OpenMBeanInfoSupport( className, DESCRIPTION, attributes, constructors, operations, notificationInfos, descriptor ); 545 546 //---* Keep the new MBeanInfo *------------------------------------ 547 cacheMBeanInfo( retValue ); 548 } 549 550 //---* Done *---------------------------------------------------------- 551 return retValue; 552 } // getMBeanInfo() 553 554 /** 555 * {@inheritDoc} 556 */ 557 @Override 558 public final MBeanNotificationInfo[] getNotificationInfo() 559 { 560 return getMBeanInfo().getNotifications(); 561 } // getNotificationInfo() 562 563 /** 564 * {@inheritDoc} 565 */ 566 @Override 567 public final long getNotificationSequenceNumber() { return m_NotificationSequenceNumber.get(); } 568 569 /** 570 * {@inheritDoc} 571 */ 572 @Override 573 public final Optional<PerformanceSection> getPerformanceSection( final PerformanceSectionName name ) 574 { 575 final Optional<PerformanceSection> retValue; 576 try 577 { 578 retValue = m_PerfRegistryReadGuard.execute( () -> m_PerfSectionRegistry.get( requireNonNullArgument( name, "name" ) ) ) 579 .map( PerformanceSectionInfo::getPerformanceSection ); 580 } 581 catch( final ExecutionFailedException e ) 582 { 583 final var cause = e.getCause(); 584 if( cause instanceof final IllegalArgumentException iae ) throw iae; 585 throw e; 586 } 587 588 //---* Done *---------------------------------------------------------- 589 return retValue; 590 } // getPerformanceSection() 591 592 /** 593 * {@inheritDoc} 594 */ 595 @Override 596 public final String [] getPerformanceSections() 597 { 598 final String [] retValue; 599 try( final var _ = m_PerfRegistryReadGuard.lock() ) 600 { 601 retValue = m_PerfSectionRegistry.keySet() 602 .stream() 603 .sorted() 604 .map( PerformanceSectionName::toString ) 605 .toArray( String []::new ); 606 } 607 608 //---* Done *---------------------------------------------------------- 609 return retValue; 610 } // getPerformanceSections() 611 612 /** 613 * {@inheritDoc} 614 */ 615 @Override 616 public final void postDeregister() 617 { 618 super.postDeregister(); 619 620 //---* Shutdown the thread pool *-------------------------------------- 621 try 622 { 623 m_ThreadPool.shutdown(); 624 //noinspection ResultOfMethodCallIgnored 625 m_ThreadPool.awaitTermination( 1L, MINUTES ); 626 } 627 catch( final InterruptedException _ ) 628 { 629 /* 630 * Deliberately ignored! 631 */ 632 } 633 } // postDeregister() 634 635 /** 636 * {@inheritDoc} 637 * <p>Finally, this method sends a notification.</p> 638 * <p>If an 639 * {@link java.util.concurrent.Executor} 640 * was specified in the constructor for the 641 * {@link NotificationBroadcasterSupport} 642 * instance, it will be given one task per selected listener to deliver 643 * the notification to that listener.</p> 644 */ 645 @Override 646 public final void receivePerformanceReport( final PerformanceReport report ) 647 { 648 //---* Update info *--------------------------------------------------- 649 final var sectionName = requireNonNullArgument( report, "report" ).getPerformanceSection().getName(); 650 final var sectionInfo = m_PerfRegistryReadGuard.execute( () -> m_PerfSectionRegistry.get( sectionName ) ); 651 sectionInfo.ifPresent( perfSectionInfo -> perfSectionInfo.processTracker( (PerformanceTrackerImpl) report.getPerformanceTracker() ) ); 652 653 //---* Compose the notification and send it *-------------------------- 654 final var notification = new Notification( NOTIFICATION_Type, getPerfLogMBeanObjectName(), m_NotificationSequenceNumber.getAndIncrement(), currentTimeMillis(), ((PerformanceReportImpl) report).toJSON() ); 655 656 m_NotificationBroadcasterSupport.sendNotification( notification ); 657 } // receivePerformanceReport() 658 659 /** 660 * {@inheritDoc} 661 */ 662 @Override 663 public final void removeNotificationListener( final NotificationListener listener ) throws ListenerNotFoundException 664 { 665 m_NotificationBroadcasterSupport.removeNotificationListener( listener ); 666 } // removeNotificationListener() 667 668 /** 669 * {@inheritDoc} 670 */ 671 @Override 672 public final void removeNotificationListener( final NotificationListener listener, final NotificationFilter filter, final Object handback ) throws ListenerNotFoundException 673 { 674 m_NotificationBroadcasterSupport.removeNotificationListener( listener, filter, handback ); 675 } // removeNotificationListener() 676 677 /** 678 * {@inheritDoc} 679 */ 680 @Override 681 public final PerformanceSection retrievePerformanceSection( final PerformanceSectionName name ) 682 { 683 final PerformanceSection retValue; 684 try( final var _ = m_PerfRegistryWriteGuard.lock() ) 685 { 686 final var sectionInfo = m_PerfSectionRegistry.computeIfAbsent( requireNonNullArgument( name, "name" ), PerformanceSectionInfo::new ); 687 retValue = sectionInfo.getPerformanceSection(); 688 } 689 690 //---* Done *---------------------------------------------------------- 691 return retValue; 692 } // retrievePerformanceSection() 693 694 /** 695 * {@inheritDoc} 696 */ 697 @Override 698 public final String showPerformanceSection( final String name ) 699 { 700 final var json = m_JSONBuilder.createObject(); 701 if( isNotEmptyOrBlank( name ) ) 702 { 703 try 704 { 705 final var performanceSectionName = createPerformanceSectionName( name ); 706 final var performanceSectionInfo = m_PerfRegistryReadGuard.execute( () -> m_PerfSectionRegistry.get( performanceSectionName ) ); 707 if( performanceSectionInfo.isPresent() ) 708 { 709 final var sectionInfo = performanceSectionInfo.get(); 710 final var section = json.setObject( JSONField_Section ); 711 section.set( JSONField_SectionName, name ) 712 .set( JSONField_SectionDescription, sectionInfo.getDescription() ) 713 .set( JSONField_SectionIgnored, sectionInfo.isIgnored() ) 714 .set( JSONField_SectionThresholdOnlyReport, sectionInfo.isSendingReportOnlyForExceededThreshold() ); 715 sectionInfo.getThreshold().ifPresentOrElse( v -> section.set( JSONField_SectionThreshold, v, MILLISECOND ), () -> section.set( JSONField_SectionThreshold, "disabled" ) ); 716 sectionInfo.getTimeout().ifPresentOrElse( v -> section.set( JSONField_SectionTimeout, v, MILLISECOND ), () -> section.set( JSONField_SectionTimeout, "disabled" ) ); 717 final var statistics = m_JSONBuilder.createObject(); 718 sectionInfo.getFirstStart().ifPresent( v -> statistics.set( JSONField_FirstStart, v.format( ISO_ZONED_DATE_TIME ) ) ); 719 sectionInfo.getLastUpdated().ifPresent( v -> statistics.set( JSONField_LastUpdated, v.format( ISO_ZONED_DATE_TIME ) ) ); 720 sectionInfo.getNumberOfCompletedRuns().ifPresent( v -> statistics.set( JSONField_CompletedRuns, v ) ); 721 sectionInfo.getNumberOfRunsThatExceededThreshold().ifPresent( v -> json.set( JSONField_ThresholdExceededRuns, v ) ); 722 sectionInfo.getNumberOfAbortedRuns().ifPresent( v -> statistics.set( JSONField_AbortedRuns, v ) ); 723 sectionInfo.getNumberOfTimedOutRuns().ifPresent( v -> statistics.set( JSONField_TimedOutRuns, v ) ); 724 if( !statistics.isEmpty() ) section.set( JSONField_SectionStatistics, statistics ); 725 } 726 else 727 { 728 addErrorToJSON( json, "No entry for this name: %s".formatted( name ) ); 729 } 730 } 731 catch( final IllegalArgumentException e ) 732 { 733 addErrorToJSON( json, "The given name '%s' is invalid: %s".formatted( name, e.getMessage() ) ); 734 } 735 } 736 else 737 { 738 addErrorToJSON( json, "The given name is null, empty or blank" ); 739 } 740 741 final var retValue = "%s".formatted( json ); 742 743 //---* Done *---------------------------------------------------------- 744 return retValue; 745 } // showPerformanceSection() 746 747 /** 748 * <p>{@summary This method is invoked when the given thread terminates 749 * due to the given uncaught exception.}</p> 750 * <p>Any exception thrown by this method will be ignored by the Java 751 * Virtual Machine.</p> 752 * <p>This 753 * {@link Thread.UncaughtExceptionHandler} 754 * will be assigned to the 755 * {@link java.util.concurrent.ThreadFactory} 756 * used by the 757 * {@linkplain #m_ThreadPool thread pool} 758 * for the 759 * {@link NotificationBroadcasterSupport} 760 * instance.</p> 761 * 762 * @param t The thread. 763 * @param e The exception. 764 * 765 * @see Thread.UncaughtExceptionHandler#uncaughtException(Thread,Throwable) 766 */ 767 private final void uncaughtExceptionHandler( final Thread t, final Throwable e ) 768 { 769 m_Exceptions.addFirst( "%s - Thread %s aborted: %s".formatted( Instant.now(), t.getName(), e.toString() ) ); 770 while( m_Exceptions.size() > 10 ) m_Exceptions.removeLast(); 771 } // uncaughtExceptionHandler() 772} 773// class PerfLogMBeanImpl 774 775/* 776 * End of File 777 */