• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 196

16 Oct 2025 08:33AM UTC coverage: 78.642% (-0.02%) from 78.661%
196

push

github

web-flow
chore: fix dogfood issues from new rules (#6056)

18180 of 23973 branches covered (75.84%)

Branch coverage included in aggregate %.

2 of 27 new or added lines in 14 files covered. (7.41%)

2 existing lines in 1 file now uncovered.

39693 of 49617 relevant lines covered (80.0%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

13.43
/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.benchmark;
6

7
import java.util.Collections;
8
import java.util.LinkedList;
9
import java.util.Objects;
10
import java.util.Queue;
11
import java.util.concurrent.ConcurrentHashMap;
12
import java.util.concurrent.ConcurrentMap;
13
import java.util.concurrent.atomic.AtomicInteger;
14
import java.util.concurrent.atomic.AtomicLong;
15
import java.util.function.Supplier;
16

17
/**
18
 * A time tracker class to measure time spent on different sections of PMD analysis.
19
 * The class is thread-aware, allowing to differentiate CPU and wall clock time.
20
 *
21
 * @author Juan Martín Sotuyo Dodero
22
 */
23
public final class TimeTracker {
24

25
    private static boolean trackTime = false;
1✔
26
    private static long wallClockStartMillis = -1;
1✔
27
    private static final ThreadLocal<Queue<TimerEntry>> TIMER_ENTRIES;
28
    private static final ConcurrentMap<TimedOperationKey, TimedResult> ACCUMULATED_RESULTS = new ConcurrentHashMap<>();
1✔
29
    private static final TimedOperation NOOP_TIMED_OPERATION = new TimedOperation() {
1✔
30

31
        @Override
32
        public void close() {
33
            // noop
34
        }
1✔
35

36
        @Override
37
        public void close(final int count) {
38
         // noop
39
        }
1✔
40
    };
41

42
    static {
43
        TIMER_ENTRIES = ThreadLocal.withInitial(() -> Collections.asLifoQueue(new LinkedList<>()));
1✔
44
    }
1✔
45

46
    private TimeTracker() {
×
47
        throw new AssertionError("Can't instantiate utility class");
×
48
    }
49

50
    /**
51
     * Starts global tracking. Allows tracking operations to take place and starts the wall clock.
52
     * Must be called once PMD starts if tracking is desired, no tracking will be performed otherwise.
53
     */
54
    public static void startGlobalTracking() {
55
        wallClockStartMillis = System.currentTimeMillis();
×
56
        trackTime = true;
×
57
        ACCUMULATED_RESULTS.clear(); // just in case
×
58
        initThread(); // init main thread
×
59
    }
×
60

61
    /**
62
     * Stops global tracking. Stops the wall clock. All further operations will be treated as NOOP.
63
     * @return The timed data obtained through the run.
64
     */
65
    public static TimingReport stopGlobalTracking() {
66
        if (!trackTime) {
×
67
            return null;
×
68
        }
69

70
        finishThread(); // finish the main thread
×
71
        trackTime = false;
×
72

73
        // Fix UNACCOUNTED metric (total time is meaningless as is call count)
74
        final TimedResult unaccountedResult = ACCUMULATED_RESULTS.get(
×
75
                new TimedOperationKey(TimedOperationCategory.UNACCOUNTED, null));
76
        unaccountedResult.totalTimeNanos.set(unaccountedResult.selfTimeNanos.get());
×
77
        unaccountedResult.callCount.set(0);
×
78

79
        return new TimingReport(System.currentTimeMillis() - wallClockStartMillis, ACCUMULATED_RESULTS);
×
80
    }
81

82
    /**
83
     * Initialize a thread, starting to track its own time.
84
     */
85
    public static void initThread() {
86
        if (!trackTime) {
1!
87
            return;
1✔
88
        }
89

90
        startOperation(TimedOperationCategory.UNACCOUNTED);
×
91
    }
×
92

93
    /**
94
     * Finishes tracking a thread.
95
     */
96
    public static void finishThread() {
97
        if (!trackTime) {
1!
98
            return;
1✔
99
        }
100

101
        finishOperation(0);
×
102

103
        // clean up thread-locals in multithread analysis
104
        if (TIMER_ENTRIES.get().isEmpty()) {
×
105
            TIMER_ENTRIES.remove();
×
106
        }
107
    }
×
108

109
    /**
110
     * Starts tracking an operation.
111
     * @param category The category under which to track the operation.
112
     * @return The current timed operation being tracked.
113
     */
114
    public static TimedOperation startOperation(final TimedOperationCategory category) {
115
        return startOperation(category, null);
1✔
116
    }
117

118
    /**
119
     * Starts tracking an operation.
120
     * @param category The category under which to track the operation.
121
     * @param label A label to be added to the category. Allows to differentiate measures within a single category.
122
     * @return The current timed operation being tracked.
123
     */
124
    public static TimedOperation startOperation(final TimedOperationCategory category, final String label) {
125
        if (!trackTime) {
1!
126
            return NOOP_TIMED_OPERATION;
1✔
127
        }
128

129
        TIMER_ENTRIES.get().add(new TimerEntry(category, label));
×
130
        return new TimedOperationImpl();
×
131
    }
132

133
    /**
134
     * Finishes tracking an operation.
135
     * @param extraDataCounter An optional additional data counter to track along the measurements.
136
     *                         Users are free to track any extra value they want (ie: number of analyzed nodes,
137
     *                         iterations in a loop, etc.)
138
     */
139
    /* default */ static void finishOperation(final long extraDataCounter) {
140
        if (!trackTime) {
×
141
            return;
×
142
        }
143

144
        final Queue<TimerEntry> queue = TIMER_ENTRIES.get();
×
145
        final TimerEntry timerEntry = queue.remove();
×
146

147
        // Compute if absent
148
        TimedResult result = ACCUMULATED_RESULTS.get(timerEntry.operation);
×
149
        if (result == null) {
×
150
            ACCUMULATED_RESULTS.putIfAbsent(timerEntry.operation, new TimedResult());
×
151
            result = ACCUMULATED_RESULTS.get(timerEntry.operation);
×
152
        }
153

154
        // Update counters and let next element on the stack ignore the time we spent
155
        final long delta = result.accumulate(timerEntry, extraDataCounter);
×
156
        if (!queue.isEmpty()) {
×
157
            queue.peek().inNestedOperationsNanos += delta;
×
158
        }
159
    }
×
160

161
    public static void bench(String label, Runnable runnable) {
162
        try (TimedOperation ignored = startOperation(TimedOperationCategory.LANGUAGE_SPECIFIC_PROCESSING, label)) {
×
163
            runnable.run();
×
164
        }
165
    }
×
166

167
    public static <T> T bench(String label, Supplier<T> runnable) {
168
        try (TimedOperation ignored = startOperation(TimedOperationCategory.LANGUAGE_SPECIFIC_PROCESSING, label)) {
×
169
            return runnable.get();
×
170
        }
171
    }
172

173
    /**
174
     * An entry in the open timers queue. Defines an operation that has started and hasn't finished yet.
175
     */
176
    private static class TimerEntry {
177
        /* package */ final TimedOperationKey operation;
178
        /* package */ final long start;
179
        /* package */ long inNestedOperationsNanos = 0;
×
180

181
        /* package */ TimerEntry(final TimedOperationCategory category, final String label) {
×
182
            this.operation = new TimedOperationKey(category, label);
×
183
            this.start = System.nanoTime();
×
184
        }
×
185

186
        @Override
187
        public String toString() {
188
            return "TimerEntry for " + operation;
×
189
        }
190
    }
191

192
    /**
193
     * Aggregate results measured so far for a given category + label.
194
     */
195
    /* package */ static class TimedResult {
×
196
        /* package */ AtomicLong totalTimeNanos = new AtomicLong();
×
197
        /* package */ AtomicLong selfTimeNanos = new AtomicLong();
×
198
        /* package */ AtomicInteger callCount = new AtomicInteger();
×
199
        /* package */ AtomicLong extraDataCounter = new AtomicLong();
×
200

201
        /**
202
         * Adds a new {@link TimerEntry} to the results.
203
         * @param timerEntry The entry to be added
204
         * @param extraData Any extra data counter to be added
205
         * @return The delta time transcurred since the {@link TimerEntry} began in nanos.
206
         */
207
        /* package */ long accumulate(final TimerEntry timerEntry, final long extraData) {
208
            final long delta = System.nanoTime() - timerEntry.start;
×
209

210
            totalTimeNanos.getAndAdd(delta);
×
211
            selfTimeNanos.getAndAdd(delta - timerEntry.inNestedOperationsNanos);
×
NEW
212
            callCount.getAndIncrement(); // NOPMD: UselessPureMethodCall false-positive #6055
×
213
            extraDataCounter.getAndAdd(extraData);
×
214

215
            return delta;
×
216
        }
217

218
        /**
219
         * Merges the times (and only the times) from another {@link TimedResult} into self.
220
         * @param timedResult The {@link TimedResult} to merge
221
         */
222
        /* package */ void mergeTimes(final TimedResult timedResult) {
223
            totalTimeNanos.getAndAdd(timedResult.totalTimeNanos.get());
×
224
            selfTimeNanos.getAndAdd(timedResult.selfTimeNanos.get());
×
225
        }
×
226
    }
227

228
    /**
229
     * A unique identifier for a timed operation
230
     */
231
    /* package */ static class TimedOperationKey {
232
        /* package */ final TimedOperationCategory category;
233
        /* package */ final String label;
234

235
        /* package */ TimedOperationKey(final TimedOperationCategory category, final String label) {
×
236
            this.category = category;
×
237
            this.label = label;
×
238
        }
×
239

240
        @Override
241
        public int hashCode() {
242
            final int prime = 31;
×
243
            int result = 1;
×
244
            result = prime * result + ((category == null) ? 0 : category.hashCode());
×
245
            result = prime * result + ((label == null) ? 0 : label.hashCode());
×
246
            return result;
×
247
        }
248

249
        @Override
250
        public boolean equals(final Object obj) {
251
            if (this == obj) {
×
252
                return true;
×
253
            }
254
            if (obj == null) {
×
255
                return false;
×
256
            }
257
            if (getClass() != obj.getClass()) {
×
258
                return false;
×
259
            }
260
            TimedOperationKey other = (TimedOperationKey) obj;
×
261
            return category == other.category && Objects.equals(label, other.label);
×
262
        }
263

264
        @Override
265
        public String toString() {
266
            return "TimedOperationKey [category=" + category + ", label=" + label + "]";
×
267
        }
268
    }
269

270
    /**
271
     * A standard timed operation implementation.
272
     */
273
    private static final class TimedOperationImpl implements TimedOperation {
×
274
        private boolean closed = false;
×
275

276
        @Override
277
        public void close() {
278
            close(0);
×
279
        }
×
280

281
        @Override
282
        public void close(int extraDataCounter) {
283
            if (closed) {
×
284
                return;
×
285
            }
286

287
            closed = true;
×
288
            TimeTracker.finishOperation(extraDataCounter);
×
289
        }
×
290
    }
291
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc