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

Hyshmily / hotkey / 28356107387

29 Jun 2026 07:35AM UTC coverage: 91.168% (-0.01%) from 91.178%
28356107387

push

github

Hyshmily
test : fix CI tests

Signed-off-by: Hyshmily <cxm8607@outlook.com>

1276 of 1461 branches covered (87.34%)

Branch coverage included in aggregate %.

10 of 10 new or added lines in 4 files covered. (100.0%)

117 existing lines in 15 files now uncovered.

3596 of 3883 relevant lines covered (92.61%)

4.24 hits per line

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

82.19
/common/src/main/java/io/github/hyshmily/hotkey/util/SystemLoadMonitor.java
1
/*
2
 * Copyright 2026 Hyshmily. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package io.github.hyshmily.hotkey.util;
17
import lombok.extern.slf4j.Slf4j;
18

19
import java.lang.management.ManagementFactory;
20
import java.lang.management.OperatingSystemMXBean;
21
import java.util.concurrent.Executors;
22
import java.util.concurrent.ScheduledExecutorService;
23
import java.util.concurrent.ScheduledFuture;
24
import java.util.concurrent.TimeUnit;
25
import java.util.concurrent.atomic.AtomicBoolean;
26
import java.util.concurrent.atomic.AtomicLong;
27

28
/**
29
 * Monitors system CPU load using the JDK platform MXBean with EMA smoothing.
30
 *
31
 * <p>Uses {@link com.sun.management.OperatingSystemMXBean#getCpuLoad()} to
32
 * read the CPU load of the JVM process. The value is smoothed with an
33
 * exponential moving average ({@code decay=0.95}) to filter out transient
34
 * spikes.
35
 * <p>Polling interval defaults to 500 ms. The monitor is started and stopped
36
 * explicitly via {@link #start()} / {@link #stop()}.
37
 */
38
@Slf4j
4✔
39
public class SystemLoadMonitor {
40

41

42
  private static final double DEFAULT_DECAY = 0.95;
43
  private static final long DEFAULT_POLL_MS = 500;
44

45
  private final long pollIntervalMs;
46
  private final double decay;
47
  private final ScheduledExecutorService scheduler;
48
  private final boolean ownsScheduler;
49

50
  private final AtomicLong emaCpuLoadBits = new AtomicLong(Double.doubleToLongBits(0.0));
7✔
51
  private final AtomicBoolean running = new AtomicBoolean(false);
6✔
52
  private ScheduledFuture<?> flushTask;
53

54
  /** @deprecated Use {@link #SystemLoadMonitor(long, double)} for explicit configuration. */
55
  @Deprecated
56
  public SystemLoadMonitor() {
57
    this(DEFAULT_POLL_MS, DEFAULT_DECAY);
4✔
58
  }
1✔
59

60
  /**
61
   * Creates a CPU load monitor with the given polling interval and EMA decay factor.
62
   * Creates its own scheduler (deprecated, prefer shared scheduler).
63
   *
64
   * @param pollIntervalMs polling interval in milliseconds; must be positive, otherwise the
65
   *                       default (500 ms) is used
66
   * @param decay          EMA decay factor in (0, 1); higher values smooth more aggressively,
67
   *                       values outside (0, 1) fall back to the default (0.95)
68
   */
69
  public SystemLoadMonitor(long pollIntervalMs, double decay) {
70
    this(Executors.newSingleThreadScheduledExecutor(
10✔
71
      new HotKeyThreadFactory("hotkey-cpu-monitor")), pollIntervalMs, decay, true);
72
  }
1✔
73

74
  /**
75
   * Creates a CPU load monitor with a shared external scheduler.
76
   *
77
   * @param scheduler      the shared scheduler (not shut down on stop)
78
   * @param pollIntervalMs polling interval in milliseconds
79
   * @param decay          EMA decay factor in (0, 1)
80
   */
81
  public SystemLoadMonitor(ScheduledExecutorService scheduler, long pollIntervalMs, double decay) {
82
    this(scheduler, pollIntervalMs, decay, false);
6✔
83
  }
1✔
84

85
  private SystemLoadMonitor(ScheduledExecutorService scheduler, long pollIntervalMs, double decay, boolean ownsScheduler) {
2✔
86
    this.pollIntervalMs = pollIntervalMs > 0 ? pollIntervalMs : DEFAULT_POLL_MS;
9✔
87
    this.decay = (decay > 0 && decay < 1) ? decay : DEFAULT_DECAY;
13✔
88
    this.scheduler = scheduler;
3✔
89
    this.ownsScheduler = ownsScheduler;
3✔
90
  }
1✔
91

92
  /** Start periodic CPU sampling. Idempotent. */
93
  public void start() {
94
    if (!running.compareAndSet(false, true)) {
6✔
95
      return;
1✔
96
    }
97
    try {
98
      flushTask = scheduler.scheduleAtFixedRate(this::sample, 0, pollIntervalMs, TimeUnit.MILLISECONDS);
11✔
99
      log.debug("SystemLoadMonitor started: pollIntervalMs={}, decay={}", pollIntervalMs, decay);
9✔
UNCOV
100
    } catch (Exception e) {
×
UNCOV
101
      log.error("Failed to start SystemLoadMonitor sampler; CPU-based BBR backpressure " +
×
102
          "will fall back to 0 CPU load (permissive).", e);
103
    }
1✔
104
  }
1✔
105

106
  /** Stop the background sampler. */
107
  public void stop() {
108
    running.set(false);
4✔
109
    if (flushTask != null) {
3✔
110
      flushTask.cancel(false);
5✔
111
    }
112
    if (ownsScheduler) {
3✔
113
      scheduler.shutdown();
3✔
114
      try {
115
        if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
6!
UNCOV
116
          scheduler.shutdownNow();
×
117
        }
UNCOV
118
      } catch (InterruptedException e) {
×
119
        scheduler.shutdownNow();
×
UNCOV
120
        Thread.currentThread().interrupt();
×
121
      }
1✔
122
    }
123
  }
1✔
124

125
  /**
126
   * Returns the EMA-smoothed CPU load (0.0 – 1.0).
127
   * Returns 0.0 if no sample has been taken yet.
128
   */
129
  public double getCpuLoadEMA() {
130
    return Double.longBitsToDouble(emaCpuLoadBits.get());
5✔
131
  }
132

133
  /**
134
   * Returns the raw (non-smoothed) CPU load (0.0 – 1.0).
135
   * Reads directly from the MXBean each call.
136
   */
137
  public double getCpuLoadRaw() {
138
    try {
139
      OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
2✔
140
      if (osBean instanceof com.sun.management.OperatingSystemMXBean sunOsBean) {
6!
141
        return Math.min(1.0, Math.max(0.0, sunOsBean.getCpuLoad()));
7✔
142
      }
UNCOV
143
    } catch (Exception ignored) {
×
UNCOV
144
    }
×
UNCOV
145
    return 0.0;
×
146
  }
147

148
  private void sample() {
149
    try {
150
      double raw = getCpuLoadRaw();
3✔
151
      double current = getCpuLoadEMA();
3✔
152
      if (current == 0.0 && raw > 0) {
8✔
153
        emaCpuLoadBits.set(Double.doubleToLongBits(raw));
6✔
154
      } else {
155
        double next = current * decay + raw * (1.0 - decay);
12✔
156
        emaCpuLoadBits.set(Double.doubleToLongBits(next));
5✔
157
      }
158
      log.trace("SystemLoadMonitor tick: raw={}, ema={}", raw, getCpuLoadEMA());
8✔
UNCOV
159
    } catch (Exception e) {
×
UNCOV
160
      log.warn("Failed to sample CPU load", e);
×
161
    }
1✔
162
  }
1✔
163
}
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