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 */