1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.model.constraint;
7   
8   import java.time.Instant;
9   import java.util.concurrent.atomic.AtomicLong;
10  import java.util.concurrent.atomic.AtomicReference;
11  import java.util.concurrent.atomic.LongAdder;
12  
13  import edu.umd.cs.findbugs.annotations.NonNull;
14  import edu.umd.cs.findbugs.annotations.Nullable;
15  
16  /**
17   * Thread-safe accumulator for timing measurements of a single timed entity
18   * (constraint, let-statement, phase, or overall validation).
19   * <p>
20   * Records total elapsed time, invocation count, and min/max per-invocation
21   * durations. Also tracks the wall-clock start and end timestamps in UTC.
22   * <p>
23   * All methods are safe to call from multiple threads concurrently.
24   */
25  public class TimingRecord {
26    @NonNull
27    private final LongAdder totalTimeNs = new LongAdder();
28    @NonNull
29    private final LongAdder count = new LongAdder();
30    @NonNull
31    private final AtomicLong minTimeNs = new AtomicLong(Long.MAX_VALUE);
32    @NonNull
33    private final AtomicLong maxTimeNs = new AtomicLong(Long.MIN_VALUE);
34    @NonNull
35    private final AtomicReference<Instant> startTimestampUtc = new AtomicReference<>();
36    @NonNull
37    private final AtomicReference<Instant> endTimestampUtc = new AtomicReference<>();
38  
39    /**
40     * Record the start of a timed event. Atomically keeps the earliest start
41     * timestamp across all invocations.
42     *
43     * @param startTimestamp
44     *          the wall-clock time when the event began
45     */
46    void recordStart(@NonNull Instant startTimestamp) {
47      startTimestampUtc.accumulateAndGet(
48          startTimestamp,
49          (prev, next) -> prev == null || next.isBefore(prev) ? next : prev);
50    }
51  
52    /**
53     * Record the completion of a timed event. Atomically keeps the latest end
54     * timestamp across all invocations.
55     *
56     * @param durationNs
57     *          the elapsed time of this invocation in nanoseconds
58     * @param endTimestamp
59     *          the wall-clock time when the event completed
60     */
61    void recordEnd(long durationNs, @NonNull Instant endTimestamp) {
62      totalTimeNs.add(durationNs);
63      count.increment();
64      endTimestampUtc.accumulateAndGet(
65          endTimestamp,
66          (prev, next) -> prev == null || next.isAfter(prev) ? next : prev);
67  
68      // update min atomically
69      long currentMin;
70      do {
71        currentMin = minTimeNs.get();
72        if (durationNs >= currentMin) {
73          break;
74        }
75      } while (!minTimeNs.compareAndSet(currentMin, durationNs));
76  
77      // update max atomically
78      long currentMax;
79      do {
80        currentMax = maxTimeNs.get();
81        if (durationNs <= currentMax) {
82          break;
83        }
84      } while (!maxTimeNs.compareAndSet(currentMax, durationNs));
85    }
86  
87    /**
88     * Get the total accumulated time across all invocations.
89     *
90     * @return total time in nanoseconds
91     */
92    public long getTotalTimeNs() {
93      return totalTimeNs.sum();
94    }
95  
96    /**
97     * Get the number of times this entity has been timed.
98     *
99     * @return the invocation count
100    */
101   public long getCount() {
102     return count.sum();
103   }
104 
105   /**
106    * Get the minimum single-invocation duration recorded.
107    *
108    * @return minimum time in nanoseconds, or {@link Long#MAX_VALUE} if no
109    *         invocations have been recorded
110    */
111   public long getMinTimeNs() {
112     return minTimeNs.get();
113   }
114 
115   /**
116    * Get the maximum single-invocation duration recorded.
117    *
118    * @return maximum time in nanoseconds, or {@link Long#MIN_VALUE} if no
119    *         invocations have been recorded
120    */
121   public long getMaxTimeNs() {
122     return maxTimeNs.get();
123   }
124 
125   /**
126    * Get the wall-clock timestamp of the earliest recorded start event.
127    *
128    * @return the start timestamp, or {@code null} if no events have been recorded
129    */
130   @Nullable
131   public Instant getStartTimestampUtc() {
132     return startTimestampUtc.get();
133   }
134 
135   /**
136    * Get the wall-clock timestamp of the latest recorded end event.
137    *
138    * @return the end timestamp, or {@code null} if no events have completed
139    */
140   @Nullable
141   public Instant getEndTimestampUtc() {
142     return endTimestampUtc.get();
143   }
144 }