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 *  &quot;Performance Section&quot;.}</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 */