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

Hyshmily / hotkey / 27925388821

22 Jun 2026 02:14AM UTC coverage: 92.403% (-1.2%) from 93.615%
27925388821

push

github

Hyshmily
fix:fix known bugs and promote performance

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

1143 of 1289 branches covered (88.67%)

Branch coverage included in aggregate %.

184 of 236 new or added lines in 19 files covered. (77.97%)

6 existing lines in 3 files now uncovered.

3321 of 3542 relevant lines covered (93.76%)

4.22 hits per line

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

82.89
/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(r -> {
7✔
71
      Thread t = new Thread(r, "cpu-monitor");
6✔
72
      t.setDaemon(true);
3✔
73
      return t;
2✔
74
    }), pollIntervalMs, decay, true);
75
  }
1✔
76

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

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

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

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

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

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

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