TickRateMonitor.java

1
package com.rox.clock;
2
3
import com.rox.time.TimeSource;
4
5
import java.util.concurrent.atomic.AtomicLong;
6
7
/**
8
 * Measures how many ticks a {@link Clock} actually delivers per wall-clock second - compare against the
9
 * intended rate (e.g. the NES CPU's 1,789,773Hz) to tell "the emulator can't keep up" from "the game
10
 * itself is slow". {@link #tick()} runs on the clock thread; {@link #measuredHz()} on a single reader
11
 * thread (e.g. a UI refresh timer).
12
 *
13
 * <p>{@code FPSClock} ticks a whole frame in a burst then sleeps, so a short measuring window would
14
 * swing wildly depending on how many bursts it caught. The rate is therefore only recalculated once at
15
 * least {@code windowNanos} has passed, and the last completed window's rate is reported in between.
16
 */
17
public final class TickRateMonitor implements ClockWatcher {
18
    public static final long DEFAULT_WINDOW_NANOS = 1_000_000_000L;
19
    private static final double NANOS_PER_SECOND = 1_000_000_000.0;
20
21
    private final TimeSource timeSource;
22
    private final long windowNanos;
23
    //written only by the clock thread: a plain counter, published with lazySet (a cheap ordered store,
24
    //no full fence) since this runs on every tick
25
    private long count;
26
    private final AtomicLong publishedCount = new AtomicLong();
27
28
    //reader-thread only
29
    private long windowStartNanos;
30
    private long windowStartCount;
31
    private double lastMeasuredHz;
32
33
    public TickRateMonitor(final TimeSource timeSource){
34
        this(timeSource, DEFAULT_WINDOW_NANOS);
35
    }
36
37
    public TickRateMonitor(final TimeSource timeSource, final long windowNanos){
38
        this.timeSource = timeSource;
39
        this.windowNanos = windowNanos;
40
        this.windowStartNanos = timeSource.nanoTime();
41
    }
42
43
    @Override
44
    public void tick(){
45 2 1. tick : Replaced long addition with subtraction → KILLED
2. tick : removed call to java/util/concurrent/atomic/AtomicLong::lazySet → KILLED
        publishedCount.lazySet(++count);
46
    }
47
48
    /** Every tick seen so far. */
49
    public long ticks(){
50 1 1. ticks : replaced long return with 0 for com/rox/clock/TickRateMonitor::ticks → KILLED
        return publishedCount.get();
51
    }
52
53
    /** Ticks per second over the last completed window - 0 until the first window completes. */
54
    public double measuredHz(){
55
        final long now = timeSource.nanoTime();
56 1 1. measuredHz : Replaced long subtraction with addition → KILLED
        final long elapsed = now - windowStartNanos;
57 2 1. measuredHz : negated conditional → KILLED
2. measuredHz : changed conditional boundary → KILLED
        if (elapsed >= windowNanos){
58
            final long ticksNow = publishedCount.get();
59 3 1. measuredHz : Replaced double division with multiplication → KILLED
2. measuredHz : Replaced long subtraction with addition → KILLED
3. measuredHz : Replaced double multiplication with division → KILLED
            lastMeasuredHz = (ticksNow - windowStartCount) * NANOS_PER_SECOND / elapsed;
60
            windowStartNanos = now;
61
            windowStartCount = ticksNow;
62
        }
63 1 1. measuredHz : replaced double return with 0.0d for com/rox/clock/TickRateMonitor::measuredHz → KILLED
        return lastMeasuredHz;
64
    }
65
}

Mutations

45

1.1
Location : tick
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:countsEveryTick()]
Replaced long addition with subtraction → KILLED

2.2
Location : tick
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:countsEveryTick()]
removed call to java/util/concurrent/atomic/AtomicLong::lazySet → KILLED

50

1.1
Location : ticks
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:countsEveryTick()]
replaced long return with 0 for com/rox/clock/TickRateMonitor::ticks → KILLED

56

1.1
Location : measuredHz
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:reportsZeroUntilTheFirstWindowCompletes()]
Replaced long subtraction with addition → KILLED

57

1.1
Location : measuredHz
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:reportsZeroUntilTheFirstWindowCompletes()]
negated conditional → KILLED

2.2
Location : measuredHz
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:holdsTheLastRateUntilTheNextWindowCompletes()]
changed conditional boundary → KILLED

59

1.1
Location : measuredHz
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:scalesALongerWindowToPerSecond()]
Replaced double division with multiplication → KILLED

2.2
Location : measuredHz
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:eachWindowOnlyCountsItsOwnTicks()]
Replaced long subtraction with addition → KILLED

3.3
Location : measuredHz
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:scalesALongerWindowToPerSecond()]
Replaced double multiplication with division → KILLED

63

1.1
Location : measuredHz
Killed by : com.rox.clock.TickRateMonitorTest.[engine:junit-jupiter]/[class:com.rox.clock.TickRateMonitorTest]/[method:scalesALongerWindowToPerSecond()]
replaced double return with 0.0d for com/rox/clock/TickRateMonitor::measuredHz → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1