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.System.nanoTime;
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.perflog.PerformanceTracker.TrackerStatus.STATUS_ABORTED;
027import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_READY;
028import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_STARTED;
029import static org.tquadrat.foundation.perflog.PerformanceTracker.TrackerStatus.STATUS_STOPPED;
030import static org.tquadrat.foundation.value.Time.NANOSECOND;
031
032import java.math.BigDecimal;
033import java.time.Instant;
034import java.util.HashMap;
035import java.util.Map;
036import java.util.Optional;
037import java.util.concurrent.ScheduledFuture;
038
039import org.apiguardian.api.API;
040import org.tquadrat.foundation.annotation.ClassVersion;
041import org.tquadrat.foundation.perflog.PerfLogManager;
042import org.tquadrat.foundation.perflog.PerformanceSection;
043import org.tquadrat.foundation.perflog.PerformanceTracker;
044import org.tquadrat.foundation.value.TimeValue;
045
046/**
047 *  <p>{@summary The implementation for the interface
048 *  {@link PerformanceTracker}}.</p>
049 *
050 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
051 *  @version $Id: PerformanceTrackerImpl.java 1258 2026-06-04 18:33:06Z tquadrat $
052 *  @since 0.25.0
053 *
054 *  @UMLGraph.link
055 */
056@ClassVersion( sourceVersion = "$Id: PerformanceTrackerImpl.java 1258 2026-06-04 18:33:06Z tquadrat $" )
057@API( status = STABLE, since = "0.25.0" )
058@SuppressWarnings( "ClassWithTooManyFields" )
059public final class PerformanceTrackerImpl implements PerformanceTracker
060{
061        /*------------*\
062    ====** Attributes **=======================================================
063        \*------------*/
064    /**
065     *  The flag that indicates whether the tracker was aborted.
066     */
067    private boolean m_Aborted = false;
068
069    /**
070     *  The context data.
071     */
072    private final Map<String,String> m_Context = new HashMap<>();
073
074    /**
075     *  <p>{@summary The end time for this performance tracker.}</p>
076     *  <p>This is not an absolute time, but a relative value.</p>
077     *
078     *  @see System#nanoTime()
079     */
080    private long m_EndTime = 0L;
081
082    /**
083     *  The reference to the performance manager that created this tracker.
084     */
085    private final PerfLogManagerImpl m_PerfLogManager;
086
087    /**
088     *  The performance section for this tracker.
089     */
090    private final PerformanceSection m_PerformanceSection;
091
092    /**
093     *  The flag that indicates whether the tracker was started.
094     */
095    private boolean m_Started = false;
096
097    /**
098     *  <p>{@summary The start time for this performance tracker.}</p>
099     *  <p>This is not an absolute time, but a relative value.</p>
100     *
101     *  @see System#nanoTime()
102     */
103    private long m_StartTime = 0L;
104
105    /**
106     *  The flag that indicates whether the tracker was stopped.
107     */
108    private boolean m_Stopped = false;
109
110    /**
111     *  <p>{@summary The flag that indicates whether this tracker was aborted
112     *  because of a timeout.}</p>
113     */
114    private boolean m_TimedOut = false;
115
116    /**
117     *  <p>{@summary The future that is used for the timeout handling.}</p>
118     *  <p>Will be {@null} when the timeout is disabled.</p>
119     */
120    private ScheduledFuture<?> m_TimeOutFuture = null;
121
122    /**
123     *  <p>{@summary The absolute time when this performance tracker was
124     *  started.} This is only used for reporting purposes.</p>
125     */
126    private Instant m_Timestamp = null;
127
128        /*--------------*\
129    ====** Constructors **=====================================================
130        \*--------------*/
131    /**
132     *  Creates a new instance of {@code PerformanceTrackerImpl}.
133     *
134     *  @param  perfLogManager  The reference to the performance manager that
135     *      created this tracker instance.
136     *  @param  performanceSection  The reference to the definition for the
137     *      performance section.
138     */
139    public PerformanceTrackerImpl( final PerfLogManager perfLogManager, final PerformanceSection performanceSection )
140    {
141        m_PerfLogManager = (PerfLogManagerImpl) requireNonNullArgument( perfLogManager,"perfLogManager" );
142        m_PerformanceSection = requireNonNullArgument( performanceSection, "performanceSection" );
143    }   //  PerformanceTrackerImpl()
144
145        /*---------*\
146    ====** Methods **==========================================================
147        \*---------*/
148    /**
149     *  {@inheritDoc}
150     */
151    @Override
152    public final void abort() { abort( null, null, false ); }
153
154    /**
155     *  {@inheritDoc}
156     */
157    @Override
158    public final void abort( final String message ) { abort( message, null ); }
159
160    /**
161     *  {@inheritDoc}
162     */
163    @Override
164    public final void abort( final String message, final Throwable cause ) { abort( message, cause, false ); }
165
166    /**
167     *  <p>{@summary Stops the performance timer and sets the
168     *  {@linkplain #isTimedOut() timeout flag}.}</p>
169     *  <p>If the tracker has been aborted or stopped already, nothing happens.
170     *  Same if it was never started.</p>
171     *
172     *  @param  flag    {@true} if the tracker timed out, {@false}
173     *      if this is a &quot;regular&quot; abort.
174     */
175    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
176    public final void abort( final boolean flag ) { abort( null, null, flag ); }
177
178    /**
179     *  <p>{@summary Stops the performance timer, sets the
180     *  {@linkplain #isTimedOut() timeout flag}
181     *  and sends a report if required.}</p>
182     *  <p>If the tracker has been aborted or stopped already, nothing happens.
183     *  Same if it was never started.</p>
184     *
185     *  @param  message The message describing the reason for the abort.
186     *  @param  cause   The exception that caused the abort.
187     *  @param  flag    {@true} if the tracker timed out, {@false}
188     *      if this is a &quot;regular&quot; abort.
189     */
190    private final void abort( final String message, final Throwable cause, final boolean flag )
191    {
192        if( m_Started && !m_Stopped && !m_Aborted )
193        {
194            terminateTimeoutMonitoring();
195            m_Aborted = true;
196            m_TimedOut = m_PerformanceSection.getTimeout().isPresent() && flag;
197            if( m_TimedOut || m_PerformanceSection.isSendingReportForAbort() ) m_PerfLogManager.sendReport( this, message, cause );
198        }
199    }   //  abort()
200
201    /**
202     *  {@inheritDoc}
203     */
204    @Override
205    public final PerformanceTracker addContext( final String name, final String value )
206    {
207        requireNonNullArgument( name, "name" );
208        if( isNull( value ) )
209        {
210            m_Context.remove( name );
211        }
212        else
213        {
214            m_Context.put( name, value );
215        }
216
217        //---* Done *----------------------------------------------------------
218        return this;
219    }   //  addContext
220
221    /**
222     *  Returns the context information.
223     *
224     *  @return The context information.
225     */
226    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
227    public final Map<String,String> getContext() { return Map.copyOf( m_Context ); }
228
229    /**
230     *  Returns the elapsed time in nanoseconds.
231     *
232     *  @return An instance of
233     *      {@link java.util.Optional}
234     *      holding the elapsed time.
235     */
236    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
237    public final Optional<TimeValue> getElapsedTime()
238    {
239        final Optional<TimeValue> retValue = m_Started && m_Stopped
240            ? Optional.of( new TimeValue( NANOSECOND, BigDecimal.valueOf( m_EndTime - m_StartTime ) ) )
241            : Optional.empty();
242
243        //---* Done *----------------------------------------------------------
244        return retValue;
245    }   //  getElapsedTime()
246
247    /**
248     *  Returns the performance section.
249     *
250     *  @return The performance section.
251     */
252    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
253    public final PerformanceSection getPerformanceSection() { return m_PerformanceSection; }
254
255    /**
256     *  {@inheritDoc}
257     */
258    @Override
259    public final TrackerStatus getStatus()
260    {
261        var retValue = STATUS_READY;
262
263        if( m_Aborted )
264        {
265            retValue = STATUS_ABORTED;
266        }
267        else if( m_Stopped )
268        {
269            retValue = STATUS_STOPPED;
270        }
271        else if( m_Started )
272        {
273            retValue = STATUS_STARTED;
274        }
275
276        //---* Done *----------------------------------------------------------
277        return retValue;
278    }   //  getStatus()
279
280    /**
281     *  Returns the timestamp.
282     *
283     *  @return The timestamp.
284     */
285    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
286    public final Instant getTimestamp() { return m_Timestamp; }
287
288    /**
289     *  <p>{@summary Checks whether the threshold was exceeded.} This means the
290     *  operation covered by the performance section for this tracker took
291     *  longer than the estimated time.
292     *
293     *  @return {@true} if the operation took longer than the provided
294     *      threshold, {@false} otherwise, or no threshold was provided.
295     */
296    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
297    public boolean isThresholdExceeded()
298    {
299        final var threshold = m_PerformanceSection.getThreshold();
300        final var elapsedTime = getElapsedTime();
301
302        final var retValue = threshold.isPresent() && elapsedTime.stream().anyMatch( v -> threshold.get().compareTo( v ) < 0 );
303
304        //---* Done *----------------------------------------------------------
305        return retValue;
306    }   //  isThresholdExceeded()
307
308    /**
309     *  <p>{@summary Checks whether the tracker was aborted due to a
310     *  timeout.}</p>
311     *
312     *  @return {@true} if the tracker was aborted due to a timeout,
313     *      {@false} if it is still running, if it was regularly stopped,
314     *      if aborted due to other reasons, or if the timeout was disabled for
315     *      the respective performance section.
316     */
317    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
318    public final boolean isTimedOut() { return m_TimedOut; }
319
320    /**
321     *  Registers the timeout monitor for this performance tracker.
322     */
323    private final void registerTimeoutMonitor()
324    {
325        m_TimeOutFuture = m_PerfLogManager.registerTimeoutMonitor( this );
326    }   //  registerTimeoutMonitor()
327
328    /**
329     *  {@inheritDoc}
330     */
331    @Override
332    public final PerformanceTracker reset( final boolean resetContext ) throws IllegalStateException
333    {
334        if( m_Started && !(m_Aborted || m_Stopped) ) throw new IllegalStateException( "Tracker is active" );
335
336        m_Aborted = false;
337        m_Stopped = false;
338        m_Started = false;
339        m_TimedOut = false;
340
341        m_Timestamp = null;
342        m_StartTime = 0L;
343        m_EndTime = 0L;
344
345        if( resetContext ) m_Context.clear();
346
347        m_TimeOutFuture = null;
348
349        //---* Done *----------------------------------------------------------
350        return this;
351    }   //  reset()
352
353    /**
354     *  {@inheritDoc}
355     */
356    @Override
357    public final void start() throws IllegalStateException
358    {
359        reset( false );
360
361        m_Timestamp = Instant.now();
362        m_Started = true;
363        m_PerformanceSection.getTimeout().ifPresent( _ -> registerTimeoutMonitor() );
364        m_StartTime = nanoTime();
365    }   //  start()
366
367    /**
368     *  {@inheritDoc}
369     */
370    @Override
371    public final void stop() throws IllegalStateException
372    {
373        m_EndTime = nanoTime();
374        if( m_Stopped ) throw new IllegalStateException( "Tracker was already stopped" );
375        m_Stopped = true;
376        terminateTimeoutMonitoring();
377        if( m_Started && !m_Aborted )
378        {
379            m_PerfLogManager.sendReport( this, null, null );
380        }
381    }   //  stop()
382
383    /**
384     *  Terminates the timeout monitor.
385     */
386    private final void terminateTimeoutMonitoring()
387    {
388        if( nonNull( m_TimeOutFuture ) )
389        {
390            m_TimeOutFuture.cancel( true );
391            m_TimeOutFuture = null;
392        }
393    }   //  terminateTimeoutMonitoring()
394}
395//  class PerformanceTrackerImpl
396
397/*
398 *  End of File
399 */