diff --git a/core/src/main/java/com/linecorp/armeria/server/healthcheck/DiskMemoryHealthChecker.java b/core/src/main/java/com/linecorp/armeria/server/healthcheck/DiskMemoryHealthChecker.java new file mode 100644 index 00000000000..06f81b0c7e7 --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/server/healthcheck/DiskMemoryHealthChecker.java @@ -0,0 +1,70 @@ +/* + * Copyright 2024 LY Corporation + * + * LY 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 java.util.Objects.requireNonNull; + +import java.io.File; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +/** + * A {@link HealthChecker} that reports as unhealthy + * when the target free disk space exceeds a file disk usable space. + * For example: + *
{@code
+ * final DiskMemoryHealthChecker diskMemoryHealthChecker = HealthChecker.ofDisk(0.8, new File("/tmp"));
+ *
+ * // Returns false if a file disk usable memory space is less than 80%,
+ * // or true if a file disk usable memory space is greater than or equal to 80%.
+ * final boolean healthy = diskMemoryHealthChecker.isHealthy();
+ * }
+ */ +// Forked from DiskSpaceMetrics.java in the micrometer core. +final class DiskMemoryHealthChecker implements Supplier> { + + private final double freeDiskSpacePercentage; + + private final File path; + + DiskMemoryHealthChecker(double freeDiskSpacePercentage, File path) { + checkArgument(freeDiskSpacePercentage >= 0 && freeDiskSpacePercentage <= 1, + "freeDiskSpacePercentage: %s (expected >= 0 and expected <= 1)", freeDiskSpacePercentage); + requireNonNull(path); + this.freeDiskSpacePercentage = freeDiskSpacePercentage; + this.path = path; + } + + /** + * Returns true if the file usable space is greater or equal than the target space. + * @return boolean + */ + public boolean isHealthy() { + return freeDiskSpacePercentage * path.getTotalSpace() <= path.getUsableSpace(); + } + + /** + * Returns a {@link CompletionStage} that represents + * the health of the file disk usable space asynchronously. + * @return CompletionStage + */ + @Override + public CompletionStage get() { + return CompletableFuture.supplyAsync(() -> new HealthCheckStatus(isHealthy(), 500)); + } +} diff --git a/core/src/main/java/com/linecorp/armeria/server/healthcheck/HealthChecker.java b/core/src/main/java/com/linecorp/armeria/server/healthcheck/HealthChecker.java index 61e5f84d84a..24f69c9ff53 100644 --- a/core/src/main/java/com/linecorp/armeria/server/healthcheck/HealthChecker.java +++ b/core/src/main/java/com/linecorp/armeria/server/healthcheck/HealthChecker.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; +import java.io.File; import java.time.Duration; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; @@ -105,6 +106,22 @@ static HealthChecker ofCpu(double targetSystemCpuUsage, double targetProcessCpuU return new CpuHealthChecker(targetSystemCpuUsage, targetProcessCpuUsage); } + /** + * Creates a new instance of {@link ListenableHealthChecker} which reports health based on free disk space + * of the file system the specified path belongs to. + * There must be more free space (inclusive) than the specified threshold in order + * for the {@link ListenableHealthChecker} to report as healthy. + * + * @param freeDiskSpacePercentage the target free disk space as a percentage (0 - 1). + * @param path the path to the file system to check. + * @return an instance of {@link ListenableHealthChecker} configured with + * the provided disk memory space targets. + */ + static ListenableHealthChecker ofDisk(double freeDiskSpacePercentage, File path) { + return new ScheduledHealthChecker(new DiskMemoryHealthChecker(freeDiskSpacePercentage, path), + Duration.ofSeconds(5), CommonPools.workerGroup().next()); + } + /** * Returns {@code true} if and only if the {@link Server} is healthy. */ diff --git a/core/src/test/java/com/linecorp/armeria/server/healthcheck/DiskMemoryCheckerTest.java b/core/src/test/java/com/linecorp/armeria/server/healthcheck/DiskMemoryCheckerTest.java new file mode 100644 index 00000000000..b012d839029 --- /dev/null +++ b/core/src/test/java/com/linecorp/armeria/server/healthcheck/DiskMemoryCheckerTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 LY Corporation + * + * LY 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 static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +class DiskMemoryCheckerTest { + + @Test + void throwExceptionWhenTargetFreeDiskSpaceLowerThanZero() { + final double targetFreeDiskSpace = -1.0; + assertThatThrownBy(() -> HealthChecker.ofDisk(targetFreeDiskSpace, new File("/tmp"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("freeDiskSpacePercentage: %.1f (expected >= 0 and expected <= 1)", + targetFreeDiskSpace); + } + + @Test + void throwExceptionWhenPathIsNull() { + final double targetFreeDiskSpace = 1.0; + assertThatThrownBy(() -> HealthChecker.ofDisk(targetFreeDiskSpace, null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void shouldReturnTrueWhenDiskSpaceIsHealthy() { + final DiskMemoryHealthChecker diskMemoryHealthChecker = new DiskMemoryHealthChecker(0.8, new File("")); + assertThat(diskMemoryHealthChecker.isHealthy()).isTrue(); + } +}