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

Hyshmily / hotkey / 28637103998

03 Jul 2026 03:48AM UTC coverage: 90.399% (-0.2%) from 90.63%
28637103998

push

github

Hyshmily
fix: add packaging to gitignore, fix flaky jitter test, update docs references

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

1264 of 1461 branches covered (86.52%)

Branch coverage included in aggregate %.

3604 of 3924 relevant lines covered (91.85%)

4.19 hits per line

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

83.56
/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

18
import io.github.hyshmily.hotkey.Internal;
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
import lombok.extern.slf4j.Slf4j;
28

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

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

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

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

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

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

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

90
  private SystemLoadMonitor(
91
    ScheduledExecutorService scheduler,
92
    long pollIntervalMs,
93
    double decay,
94
    boolean ownsScheduler
95
  ) {
2✔
96
    this.pollIntervalMs = pollIntervalMs > 0 ? pollIntervalMs : DEFAULT_POLL_MS;
9✔
97
    this.decay = (decay > 0 && decay < 1) ? decay : DEFAULT_DECAY;
13✔
98
    this.scheduler = scheduler;
3✔
99
    this.ownsScheduler = ownsScheduler;
3✔
100
  }
1✔
101

102
  /** Start periodic CPU sampling. Idempotent. */
103
  public void start() {
104
    if (!running.compareAndSet(false, true)) {
6✔
105
      return;
1✔
106
    }
107
    try {
108
      flushTask = scheduler.scheduleAtFixedRate(this::sample, 0, pollIntervalMs, TimeUnit.MILLISECONDS);
11✔
109
      log.debug("SystemLoadMonitor started: pollIntervalMs={}, decay={}", pollIntervalMs, decay);
9✔
110
    } catch (Exception e) {
×
111
      log.error(
×
112
        "Failed to start SystemLoadMonitor sampler; CPU-based BBR backpressure " +
113
          "will fall back to 0 CPU load (permissive).",
114
        e
115
      );
116
    }
1✔
117
  }
1✔
118

119
  /** Stop the background sampler. */
120
  public void stop() {
121
    running.set(false);
4✔
122
    if (flushTask != null) {
3✔
123
      flushTask.cancel(false);
5✔
124
    }
125
    if (ownsScheduler) {
3✔
126
      scheduler.shutdown();
3✔
127
      try {
128
        if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
6!
129
          scheduler.shutdownNow();
×
130
        }
131
      } catch (InterruptedException e) {
×
132
        scheduler.shutdownNow();
×
133
        Thread.currentThread().interrupt();
×
134
      }
1✔
135
    }
136
  }
1✔
137

138
  /**
139
   * Returns the EMA-smoothed CPU load (0.0 – 1.0).
140
   * Returns 0.0 if no sample has been taken yet.
141
   */
142
  public double getCpuLoadEMA() {
143
    return Double.longBitsToDouble(emaCpuLoadBits.get());
5✔
144
  }
145

146
  /**
147
   * Returns the raw (non-smoothed) CPU load (0.0 – 1.0).
148
   * Reads directly from the MXBean each call.
149
   */
150
  public double getCpuLoadRaw() {
151
    try {
152
      OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
2✔
153
      if (osBean instanceof com.sun.management.OperatingSystemMXBean sunOsBean) {
6!
154
        return Math.min(1.0, Math.max(0.0, sunOsBean.getCpuLoad()));
7✔
155
      }
156
    } catch (Exception ignored) {}
×
157
    return 0.0;
×
158
  }
159

160
  private void sample() {
161
    try {
162
      double raw = getCpuLoadRaw();
3✔
163
      double current = getCpuLoadEMA();
3✔
164
      if (current == 0.0 && raw > 0) {
8✔
165
        emaCpuLoadBits.set(Double.doubleToLongBits(raw));
6✔
166
      } else {
167
        double next = current * decay + raw * (1.0 - decay);
12✔
168
        emaCpuLoadBits.set(Double.doubleToLongBits(next));
5✔
169
      }
170
      log.trace("SystemLoadMonitor tick: raw={}, ema={}", raw, getCpuLoadEMA());
8✔
171
    } catch (Exception e) {
×
172
      log.warn("Failed to sample CPU load", e);
×
173
    }
1✔
174
  }
1✔
175
}
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