aboutsummaryrefslogtreecommitdiff
path: root/spark-common/src/main/java/me/lucko/spark/common/util
diff options
context:
space:
mode:
Diffstat (limited to 'spark-common/src/main/java/me/lucko/spark/common/util')
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java27
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java25
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/Compression.java100
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java29
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java84
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java17
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/SparkThreadFactory.java49
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java189
8 files changed, 497 insertions, 23 deletions
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java b/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java
index 29ee5bb..c2ca1b1 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/BytebinClient.java
@@ -1,26 +1,21 @@
/*
- * This file is part of bytebin, licensed under the MIT License.
+ * This file is part of spark.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package me.lucko.spark.common.util;
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java b/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java
index 42a04f7..bd9ec37 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java
@@ -26,6 +26,7 @@ import me.lucko.spark.common.sampler.node.ThreadNode;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
+import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -87,8 +88,22 @@ public interface ClassSourceLookup {
*/
interface ByUrl extends ClassSourceLookup {
- default String identifyUrl(URL url) throws URISyntaxException {
- return url.getProtocol().equals("file") ? identifyFile(Paths.get(url.toURI())) : null;
+ default String identifyUrl(URL url) throws URISyntaxException, MalformedURLException {
+ Path path = null;
+
+ String protocol = url.getProtocol();
+ if (protocol.equals("file")) {
+ path = Paths.get(url.toURI());
+ } else if (protocol.equals("jar")) {
+ URL innerUrl = new URL(url.getPath());
+ path = Paths.get(innerUrl.getPath().split("!")[0]);
+ }
+
+ if (path != null) {
+ return identifyFile(path.toAbsolutePath().normalize());
+ }
+
+ return null;
}
default String identifyFile(Path path) {
@@ -123,7 +138,7 @@ public interface ClassSourceLookup {
*/
class ByCodeSource implements ClassSourceLookup, ByUrl {
@Override
- public @Nullable String identify(Class<?> clazz) throws URISyntaxException {
+ public @Nullable String identify(Class<?> clazz) throws URISyntaxException, MalformedURLException {
ProtectionDomain protectionDomain = clazz.getProtectionDomain();
if (protectionDomain == null) {
return null;
@@ -148,12 +163,12 @@ public interface ClassSourceLookup {
static Visitor createVisitor(ClassSourceLookup lookup) {
if (lookup == ClassSourceLookup.NO_OP) {
- return NoOpVistitor.INSTANCE; // don't bother!
+ return NoOpVisitor.INSTANCE; // don't bother!
}
return new VisitorImpl(lookup);
}
- enum NoOpVistitor implements Visitor {
+ enum NoOpVisitor implements Visitor {
INSTANCE;
@Override
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/Compression.java b/spark-common/src/main/java/me/lucko/spark/common/util/Compression.java
new file mode 100644
index 0000000..9295c25
--- /dev/null
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/Compression.java
@@ -0,0 +1,100 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.common.util;
+
+import org.tukaani.xz.LZMA2Options;
+import org.tukaani.xz.LZMAOutputStream;
+import org.tukaani.xz.XZOutputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.LongConsumer;
+import java.util.zip.GZIPOutputStream;
+
+public enum Compression {
+ GZIP {
+ @Override
+ public Path compress(Path file, LongConsumer progressHandler) throws IOException {
+ Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".gz");
+ try (InputStream in = Files.newInputStream(file)) {
+ try (OutputStream out = Files.newOutputStream(compressedFile)) {
+ try (GZIPOutputStream compressionOut = new GZIPOutputStream(out, 1024 * 64)) {
+ copy(in, compressionOut, progressHandler);
+ }
+ }
+ }
+ return compressedFile;
+ }
+ },
+ XZ {
+ @Override
+ public Path compress(Path file, LongConsumer progressHandler) throws IOException {
+ Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".xz");
+ try (InputStream in = Files.newInputStream(file)) {
+ try (OutputStream out = Files.newOutputStream(compressedFile)) {
+ try (XZOutputStream compressionOut = new XZOutputStream(out, new LZMA2Options())) {
+ copy(in, compressionOut, progressHandler);
+ }
+ }
+ }
+ return compressedFile;
+ }
+ },
+ LZMA {
+ @Override
+ public Path compress(Path file, LongConsumer progressHandler) throws IOException {
+ Path compressedFile = file.getParent().resolve(file.getFileName().toString() + ".lzma");
+ try (InputStream in = Files.newInputStream(file)) {
+ try (OutputStream out = Files.newOutputStream(compressedFile)) {
+ try (LZMAOutputStream compressionOut = new LZMAOutputStream(out, new LZMA2Options(), true)) {
+ copy(in, compressionOut, progressHandler);
+ }
+ }
+ }
+ return compressedFile;
+ }
+ };
+
+ public abstract Path compress(Path file, LongConsumer progressHandler) throws IOException;
+
+ private static long copy(InputStream from, OutputStream to, LongConsumer progress) throws IOException {
+ byte[] buf = new byte[1024 * 64];
+ long total = 0;
+ long iterations = 0;
+ while (true) {
+ int r = from.read(buf);
+ if (r == -1) {
+ break;
+ }
+ to.write(buf, 0, r);
+ total += r;
+
+ // report progress every 5MB
+ if (iterations++ % ((1024 / 64) * 5) == 0) {
+ progress.accept(total);
+ }
+ }
+ return total;
+ }
+}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java
index 492d4ea..c4a3d66 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java
@@ -20,6 +20,11 @@
package me.lucko.spark.common.util;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
+
+import java.util.Locale;
+
public enum FormatUtil {
;
@@ -31,10 +36,30 @@ public enum FormatUtil {
}
public static String formatBytes(long bytes) {
- if (bytes == 0) {
+ if (bytes <= 0) {
return "0 bytes";
}
int sizeIndex = (int) (Math.log(bytes) / Math.log(1024));
- return String.format("%.1f", bytes / Math.pow(1024, sizeIndex)) + " " + SIZE_UNITS[sizeIndex];
+ return String.format(Locale.ENGLISH, "%.1f", bytes / Math.pow(1024, sizeIndex)) + " " + SIZE_UNITS[sizeIndex];
+ }
+
+ public static Component formatBytes(long bytes, TextColor color, String suffix) {
+ String value;
+ String unit;
+
+ if (bytes <= 0) {
+ value = "0";
+ unit = "KB" + suffix;
+ } else {
+ int sizeIndex = (int) (Math.log(bytes) / Math.log(1024));
+ value = String.format(Locale.ENGLISH, "%.1f", bytes / Math.pow(1024, sizeIndex));
+ unit = SIZE_UNITS[sizeIndex] + suffix;
+ }
+
+ return Component.text()
+ .append(Component.text(value, color))
+ .append(Component.space())
+ .append(Component.text(unit))
+ .build();
}
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java b/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java
new file mode 100644
index 0000000..7d688d7
--- /dev/null
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/LinuxProc.java
@@ -0,0 +1,84 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.common.util;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility for reading from /proc/ on Linux systems.
+ */
+public enum LinuxProc {
+
+ /**
+ * Information about the system CPU.
+ */
+ CPUINFO("/proc/cpuinfo"),
+
+ /**
+ * Information about the system memory.
+ */
+ MEMINFO("/proc/meminfo"),
+
+ /**
+ * Information about the system network usage.
+ */
+ NET_DEV("/proc/net/dev");
+
+ private final Path path;
+
+ LinuxProc(String path) {
+ this.path = resolvePath(path);
+ }
+
+ private static @Nullable Path resolvePath(String path) {
+ try {
+ Path p = Paths.get(path);
+ if (Files.isReadable(p)) {
+ return p;
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return null;
+ }
+
+ public @NonNull List<String> read() {
+ if (this.path != null) {
+ try {
+ return Files.readAllLines(this.path, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java
index 87c41a4..65753bc 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/RollingAverage.java
@@ -21,6 +21,7 @@
package me.lucko.spark.common.util;
import me.lucko.spark.api.statistic.misc.DoubleAverageInfo;
+import me.lucko.spark.proto.SparkProtos;
import java.math.BigDecimal;
import java.math.RoundingMode;
@@ -39,6 +40,12 @@ public class RollingAverage implements DoubleAverageInfo {
this.samples = new ArrayDeque<>(this.windowSize + 1);
}
+ public int getSamples() {
+ synchronized (this) {
+ return this.samples.size();
+ }
+ }
+
public void add(BigDecimal num) {
synchronized (this) {
this.total = this.total.add(num);
@@ -105,4 +112,14 @@ public class RollingAverage implements DoubleAverageInfo {
return sortedSamples[rank].doubleValue();
}
+ public SparkProtos.RollingAverageValues toProto() {
+ return SparkProtos.RollingAverageValues.newBuilder()
+ .setMean(mean())
+ .setMax(max())
+ .setMin(min())
+ .setMedian(median())
+ .setPercentile95(percentile95th())
+ .build();
+ }
+
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/SparkThreadFactory.java b/spark-common/src/main/java/me/lucko/spark/common/util/SparkThreadFactory.java
new file mode 100644
index 0000000..156fa0d
--- /dev/null
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/SparkThreadFactory.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.common.util;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class SparkThreadFactory implements ThreadFactory, Thread.UncaughtExceptionHandler {
+ private static final AtomicInteger poolNumber = new AtomicInteger(1);
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+ private final String namePrefix;
+
+ public SparkThreadFactory() {
+ this.namePrefix = "spark-worker-pool-" +
+ poolNumber.getAndIncrement() +
+ "-thread-";
+ }
+
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r, this.namePrefix + this.threadNumber.getAndIncrement());
+ t.setUncaughtExceptionHandler(this);
+ t.setDaemon(true);
+ return t;
+ }
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ System.err.println("Uncaught exception thrown by thread " + t.getName());
+ e.printStackTrace();
+ }
+}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java b/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java
new file mode 100644
index 0000000..22ee9bb
--- /dev/null
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/StatisticFormatter.java
@@ -0,0 +1,189 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package me.lucko.spark.common.util;
+
+import com.google.common.base.Strings;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.format.TextColor;
+
+import java.lang.management.MemoryUsage;
+import java.util.Locale;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
+
+public enum StatisticFormatter {
+ ;
+
+ private static final String BAR_TRUE_CHARACTER = "┃";
+ private static final String BAR_FALSE_CHARACTER = "╻";
+
+ public static TextComponent formatTps(double tps) {
+ TextColor color;
+ if (tps > 18.0) {
+ color = GREEN;
+ } else if (tps > 16.0) {
+ color = YELLOW;
+ } else {
+ color = RED;
+ }
+
+ return text((tps > 20.0 ? "*" : "") + Math.min(Math.round(tps * 100.0) / 100.0, 20.0), color);
+ }
+
+ public static TextComponent formatTickDurations(RollingAverage average) {
+ return text()
+ .append(formatTickDuration(average.min()))
+ .append(text('/', GRAY))
+ .append(formatTickDuration(average.median()))
+ .append(text('/', GRAY))
+ .append(formatTickDuration(average.percentile95th()))
+ .append(text('/', GRAY))
+ .append(formatTickDuration(average.max()))
+ .build();
+ }
+
+ public static TextComponent formatTickDuration(double duration) {
+ TextColor color;
+ if (duration >= 50d) {
+ color = RED;
+ } else if (duration >= 40d) {
+ color = YELLOW;
+ } else {
+ color = GREEN;
+ }
+
+ return text(String.format(Locale.ENGLISH, "%.1f", duration), color);
+ }
+
+ public static TextComponent formatCpuUsage(double usage) {
+ TextColor color;
+ if (usage > 0.9) {
+ color = RED;
+ } else if (usage > 0.65) {
+ color = YELLOW;
+ } else {
+ color = GREEN;
+ }
+
+ return text(FormatUtil.percent(usage, 1d), color);
+ }
+
+ public static TextComponent formatPingRtts(double min, double median, double percentile95th, double max) {
+ return text()
+ .append(formatPingRtt(min))
+ .append(text('/', GRAY))
+ .append(formatPingRtt(median))
+ .append(text('/', GRAY))
+ .append(formatPingRtt(percentile95th))
+ .append(text('/', GRAY))
+ .append(formatPingRtt(max))
+ .build();
+ }
+
+ public static TextComponent formatPingRtt(double ping) {
+ TextColor color;
+ if (ping >= 200) {
+ color = RED;
+ } else if (ping >= 100) {
+ color = YELLOW;
+ } else {
+ color = GREEN;
+ }
+
+ return text((int) Math.ceil(ping), color);
+ }
+
+ public static TextComponent generateMemoryUsageDiagram(MemoryUsage usage, int length) {
+ double used = usage.getUsed();
+ double committed = usage.getCommitted();
+ double max = usage.getMax();
+
+ int usedChars = (int) ((used * length) / max);
+ int committedChars = (int) ((committed * length) / max);
+
+ TextComponent.Builder line = text().content(Strings.repeat(BAR_TRUE_CHARACTER, usedChars)).color(YELLOW);
+ if (committedChars > usedChars) {
+ line.append(text(Strings.repeat(BAR_FALSE_CHARACTER, (committedChars - usedChars) - 1), GRAY));
+ line.append(Component.text(BAR_FALSE_CHARACTER, RED));
+ }
+ if (length > committedChars) {
+ line.append(text(Strings.repeat(BAR_FALSE_CHARACTER, (length - committedChars)), GRAY));
+ }
+
+ return text()
+ .append(text("[", DARK_GRAY))
+ .append(line.build())
+ .append(text("]", DARK_GRAY))
+ .build();
+ }
+
+ public static TextComponent generateMemoryPoolDiagram(MemoryUsage usage, MemoryUsage collectionUsage, int length) {
+ double used = usage.getUsed();
+ double collectionUsed = used;
+ if (collectionUsage != null) {
+ collectionUsed = collectionUsage.getUsed();
+ }
+ double committed = usage.getCommitted();
+ double max = usage.getMax();
+
+ int usedChars = (int) ((used * length) / max);
+ int collectionUsedChars = (int) ((collectionUsed * length) / max);
+ int committedChars = (int) ((committed * length) / max);
+
+ TextComponent.Builder line = text().content(Strings.repeat(BAR_TRUE_CHARACTER, collectionUsedChars)).color(YELLOW);
+
+ if (usedChars > collectionUsedChars) {
+ line.append(Component.text(BAR_TRUE_CHARACTER, RED));
+ line.append(text(Strings.repeat(BAR_TRUE_CHARACTER, (usedChars - collectionUsedChars) - 1), YELLOW));
+ }
+ if (committedChars > usedChars) {
+ line.append(text(Strings.repeat(BAR_FALSE_CHARACTER, (committedChars - usedChars) - 1), GRAY));
+ line.append(Component.text(BAR_FALSE_CHARACTER, YELLOW));
+ }
+ if (length > committedChars) {
+ line.append(text(Strings.repeat(BAR_FALSE_CHARACTER, (length - committedChars)), GRAY));
+ }
+
+ return text()
+ .append(text("[", DARK_GRAY))
+ .append(line.build())
+ .append(text("]", DARK_GRAY))
+ .build();
+ }
+
+ public static TextComponent generateDiskUsageDiagram(double used, double max, int length) {
+ int usedChars = (int) ((used * length) / max);
+ int freeChars = length - usedChars;
+ return text()
+ .append(text("[", DARK_GRAY))
+ .append(text(Strings.repeat(BAR_TRUE_CHARACTER, usedChars), YELLOW))
+ .append(text(Strings.repeat(BAR_FALSE_CHARACTER, freeChars), GRAY))
+ .append(text("]", DARK_GRAY))
+ .build();
+ }
+}