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

Hyshmily / hotkey / 28738321635

05 Jul 2026 10:51AM UTC coverage: 90.457% (+0.04%) from 90.421%
28738321635

push

github

Hyshmily
refactor: extract interfaces for 12 core classes (Batch 1+2)

Extract interfaces from concrete classes, impls moved to impl/ subpackages:

Batch 1 (HotPath):
- CircuitBreaker, ExpireManager, SingleFlight
- KeyReporter, RuleMatcher, HealthView

Batch 2 (Supporting):
- VersionController, RingManager, HotKeyStateMachine
- SystemLoadMonitor, BbrRateLimiter, SreRateLimiter

Pattern: interface at original package, impl named XxxImpl under impl/

1296 of 1498 branches covered (86.52%)

Branch coverage included in aggregate %.

448 of 480 new or added lines in 19 files covered. (93.33%)

3690 of 4014 relevant lines covered (91.93%)

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/impl/SystemLoadMonitorImpl.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.impl;
17

18
import io.github.hyshmily.hotkey.Internal;
19
import io.github.hyshmily.hotkey.util.HotKeyThreadFactory;
20
import io.github.hyshmily.hotkey.util.SystemLoadMonitor;
21
import java.lang.management.ManagementFactory;
22
import java.lang.management.OperatingSystemMXBean;
23
import java.util.concurrent.Executors;
24
import java.util.concurrent.ScheduledExecutorService;
25
import java.util.concurrent.ScheduledFuture;
26
import java.util.concurrent.TimeUnit;
27
import java.util.concurrent.atomic.AtomicBoolean;
28
import java.util.concurrent.atomic.AtomicLong;
29
import lombok.extern.slf4j.Slf4j;
30

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

45
  private static final double DEFAULT_DECAY = 0.95;
46
  private static final long DEFAULT_POLL_MS = 500;
47

48
  private final long pollIntervalMs;
49
  private final double decay;
50
  private final ScheduledExecutorService scheduler;
51
  private final boolean ownsScheduler;
52

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

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

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

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

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

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

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

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

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

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