Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added DefaultCpuHealthChecker #4673

Merged
merged 41 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f08cb8b
Added DefaultCpuHealthChecker
Bue-von-hon Feb 14, 2023
08c706e
Fixed javadoc and lint based on code review
Bue-von-hon Feb 28, 2023
551263a
Fix Lint
Bue-von-hon Mar 1, 2023
afd1843
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
5e5069e
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
e4132b4
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
35da7bc
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
a0c04bb
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
dd68de9
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
8934066
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
32fd4f7
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Mar 11, 2023
0a0f815
Fixed javadoc, access modifier, invoke method's algorithm and Added T…
Bue-von-hon Mar 18, 2023
3b1770b
Fix Lint, Construct method's access level
Bue-von-hon Apr 5, 2023
c8c1664
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Def…
Bue-von-hon Jun 8, 2023
eda64c4
Fixed base on code review
Bue-von-hon Jun 9, 2023
a4733f9
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Jun 24, 2023
8be0675
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Jul 2, 2023
07c4f02
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Jul 24, 2023
d5f162f
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Aug 8, 2023
760496c
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Aug 28, 2023
a1df8fa
Resolves code review
Bue-von-hon Aug 28, 2023
3f4a5fc
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Sep 7, 2023
0b14883
Fix lint
Bue-von-hon Sep 7, 2023
667a0bf
Merge remote-tracking branch 'Bue-von-hon/cpu-based-health-checker' i…
Bue-von-hon Sep 7, 2023
2dae89c
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Oct 1, 2023
9d7cd92
Optimize CPU Health Check Implementations
Bue-von-hon Nov 15, 2023
69df576
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Nov 21, 2023
8f2b4a1
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Nov 22, 2023
f6ef458
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Cpu…
Bue-von-hon Jan 30, 2024
a766919
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Cpu…
Bue-von-hon Jan 30, 2024
f469ac7
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Feb 26, 2024
fad1155
Resolves code review
Bue-von-hon Feb 26, 2024
29048d3
Rewrite the test correctly
Bue-von-hon Feb 26, 2024
20e3188
Merge branch 'main' into cpu-based-health-checker
Bue-von-hon Mar 21, 2024
5535d6a
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Cpu…
Bue-von-hon Mar 22, 2024
759763b
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Cpu…
Bue-von-hon Mar 22, 2024
fb59ec4
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Cpu…
Bue-von-hon Mar 22, 2024
d1a06e3
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Cpu…
Bue-von-hon Mar 22, 2024
1dda317
Update core/src/main/java/com/linecorp/armeria/server/healthcheck/Cpu…
Bue-von-hon Mar 22, 2024
828e51a
Add a factory method to an HealthChecker interface
Bue-von-hon Mar 22, 2024
d619208
supplement javadocs
jrhee17 Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* Copyright 2023 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.linecorp.armeria.server.healthcheck;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.DoubleSupplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;

import com.linecorp.armeria.common.annotation.Nullable;

/**
* A {@link HealthChecker} that reports as unhealthy when the current
* CPU usage or CPU load exceeds threshold. For example:
* <pre>{@code
* final CpuHealthChecker cpuHealthChecker = HealthChecker.of(0.1, 0.1);
*
* // Returns false if either is greater than 10%,
* // or true if both are less than or equal to 10%.
* final boolean healthy = cpuHealthChecker.isHealthy();
* }</pre>
*/
// Forked from <a href="https://github.com/micrometer-metrics/micrometer/blob/8339d57bef8689beb8d7a18b429a166f6595f2af/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/system/ProcessorMetrics.java">ProcessorMetrics.java</a> in the micrometer core.
final class CpuHealthChecker implements HealthChecker {
private static final Logger logger = LoggerFactory.getLogger(CpuHealthChecker.class);

private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

private static final DoubleSupplier currentSystemCpuUsageSupplier;

private static final DoubleSupplier currentProcessCpuUsageSupplier;

@Nullable
private static final OperatingSystemMXBean operatingSystemBean;

@Nullable
private static final Class<?> operatingSystemBeanClass;
Bue-von-hon marked this conversation as resolved.
Show resolved Hide resolved

private static final List<String> OPERATING_SYSTEM_BEAN_CLASS_NAMES = ImmutableList.of(
"com.ibm.lang.management.OperatingSystemMXBean", // J9
"com.sun.management.OperatingSystemMXBean" // HotSpot
);

@Nullable
private static final MethodHandle systemCpuLoad;

@Nullable
private static final MethodHandle processCpuLoad;

static {
operatingSystemBeanClass = getFirstClassFound(OPERATING_SYSTEM_BEAN_CLASS_NAMES);
operatingSystemBean = ManagementFactory.getOperatingSystemMXBean();
final MethodHandle getCpuLoad = detectMethod("getCpuLoad");
systemCpuLoad = getCpuLoad != null ? getCpuLoad : detectMethod("getSystemCpuLoad");
processCpuLoad = detectMethod("getProcessCpuLoad");
currentSystemCpuUsageSupplier = () -> invoke(systemCpuLoad);
currentProcessCpuUsageSupplier = () -> invoke(processCpuLoad);
}

private final DoubleSupplier systemCpuUsageSupplier;

private final DoubleSupplier processCpuUsageSupplier;

@VisibleForTesting
final double targetProcessCpuLoad;

@VisibleForTesting
final double targetSystemCpuUsage;

/**
* Instantiates a new Default cpu health checker.
*
* @param targetSystemCpuUsage the target cpu usage
* @param targetProcessCpuUsage the target process cpu usage
*/
CpuHealthChecker(double targetSystemCpuUsage, double targetProcessCpuUsage) {
this(targetSystemCpuUsage, targetProcessCpuUsage,
currentSystemCpuUsageSupplier, currentProcessCpuUsageSupplier);
}

private CpuHealthChecker(double targetSystemCpuUsage, double targetProcessCpuLoad,
DoubleSupplier systemCpuUsageSupplier, DoubleSupplier processCpuUsageSupplier) {
jrhee17 marked this conversation as resolved.
Show resolved Hide resolved
checkArgument(targetSystemCpuUsage >= 0 && targetSystemCpuUsage <= 1.0,
"cpuUsage: %s (expected: 0 <= cpuUsage <= 1)", targetSystemCpuUsage);
checkArgument(targetProcessCpuLoad >= 0 && targetProcessCpuLoad <= 1.0,
"processCpuLoad: %s (expected: 0 <= processCpuLoad <= 1)", targetProcessCpuLoad);
this.targetSystemCpuUsage = targetSystemCpuUsage;
this.targetProcessCpuLoad = targetProcessCpuLoad;
Bue-von-hon marked this conversation as resolved.
Show resolved Hide resolved
this.systemCpuUsageSupplier = systemCpuUsageSupplier;
this.processCpuUsageSupplier = processCpuUsageSupplier;
checkState(operatingSystemBeanClass != null, "Unable to find an 'OperatingSystemMXBean' class");
checkState(operatingSystemBean != null, "Unable to find an 'OperatingSystemMXBean'");
checkState(systemCpuLoad != null, "Unable to find the method 'OperatingSystemMXBean.getCpuLoad'" +
" or 'OperatingSystemMXBean.getSystemCpuLoad'");
checkState(processCpuLoad != null,
"Unable to find the method 'OperatingSystemMXBean.getProcessCpuLoad'");
}

private static double invoke(@Nullable MethodHandle mh) {
if (mh == null) {
return Double.NaN;

Check warning on line 126 in core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java#L126

Added line #L126 was not covered by tests
}

try {
return (double) mh.invoke(operatingSystemBean);
} catch (Throwable e) {
return Double.NaN;

Check warning on line 132 in core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java#L130-L132

Added lines #L130 - L132 were not covered by tests
}
}

@Nullable
@SuppressWarnings("ReturnValueIgnored")
private static MethodHandle detectMethod(final String name) {
if (operatingSystemBeanClass == null) {
return null;

Check warning on line 140 in core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java#L140

Added line #L140 was not covered by tests
}
try {
// ensure the Bean we have is actually an instance of the interface
operatingSystemBeanClass.cast(operatingSystemBean);
final Method method = operatingSystemBeanClass.getMethod(name);
return LOOKUP.unreflect(method);
} catch (ClassCastException | NoSuchMethodException | SecurityException | IllegalAccessException e) {
logger.warn("Failed to detect method {}.{} for {}", operatingSystemBeanClass.getSimpleName(),
name, CpuHealthChecker.class.getSimpleName(), e);
return null;

Check warning on line 150 in core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java#L147-L150

Added lines #L147 - L150 were not covered by tests
}
}

@Nullable
private static Class<?> getFirstClassFound(final List<String> classNames) {
for (String className : classNames) {
try {
return Class.forName(className, false, CpuHealthChecker.class.getClassLoader());
} catch (ClassNotFoundException ignore) {
}
}
return null;

Check warning on line 162 in core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java#L162

Added line #L162 was not covered by tests
}

/**
* Returns true if and only if System CPU Usage and Processes cpu usage is below the target usage.
*/
@Override
public boolean isHealthy() {
return isHealthy(systemCpuUsageSupplier, processCpuUsageSupplier);

Check warning on line 170 in core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java#L170

Added line #L170 was not covered by tests
}

private boolean isHealthy(
DoubleSupplier currentSystemCpuUsageSupplier, DoubleSupplier currentProcessCpuUsageSupplier) {
final double currentSystemCpuUsage = currentSystemCpuUsageSupplier.getAsDouble();
final double currentProcessCpuUsage = currentProcessCpuUsageSupplier.getAsDouble();

Check warning on line 176 in core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/healthcheck/CpuHealthChecker.java#L175-L176

Added lines #L175 - L176 were not covered by tests
return currentSystemCpuUsage <= targetSystemCpuUsage && currentProcessCpuUsage <= targetProcessCpuLoad;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import java.util.concurrent.CompletionStage;
import java.util.function.Supplier;

import com.sun.management.OperatingSystemMXBean;

import com.linecorp.armeria.common.CommonPools;
import com.linecorp.armeria.common.annotation.UnstableApi;
import com.linecorp.armeria.server.Server;
Expand Down Expand Up @@ -88,6 +90,21 @@ static ListenableHealthChecker of(Supplier<? extends CompletionStage<HealthCheck
requireNonNull(eventExecutor, "eventExecutor"));
}

/**
* Creates a new instance of {@link HealthChecker} which reports health based on
* cpu usage reported by {@link OperatingSystemMXBean}.
* Both system and process cpu usage must be (inclusive) lower than the specified threshold in order
* for the {@link HealthChecker} to report as healthy. If the {@link HealthChecker} is unable
* to find a suitable {@link OperatingSystemMXBean}, an exception is thrown on construction.
*
* @param targetSystemCpuUsage Target system CPU usage as a percentage (0 - 1).
* @param targetProcessCpuUsage Target process CPU usage as a percentage (0 - 1).
* @return an instance of {@link HealthChecker} configured with the provided CPU usage targets.
*/
static HealthChecker ofCpu(double targetSystemCpuUsage, double targetProcessCpuUsage) {
return new CpuHealthChecker(targetSystemCpuUsage, targetProcessCpuUsage);
}

/**
* Returns {@code true} if and only if the {@link Server} is healthy.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2023 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.linecorp.armeria.server.healthcheck;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

class CpuHealthCheckerTest {

@Test
void shouldGatherCpuUsageInformation() {
final CpuHealthChecker healthChecker =
(CpuHealthChecker) HealthChecker.ofCpu(0.1, 0.3);
assertThat(healthChecker.targetSystemCpuUsage).isNotNaN();
assertThat(healthChecker.targetProcessCpuLoad).isNotNaN();
}
}
Loading