Skip to content

Commit

Permalink
Add mc_world_size (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
eruizc-dev committed Apr 11, 2022
1 parent 112e715 commit 308c136
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ mc_loaded_chunks_total | Chunks loaded per world
mc_players_online_total | Online players per world
mc_entities_total | Entities loaded per world (living + non-living)
mc_villagers_total | Villagers
mc_world_size | World size in bytes
mc_jvm_memory | JVM memory usage
mc_jvm_threads | JVM threads info
mc_tps | Server tickrate (TPS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class PrometheusExporterConfig {
metricConfig("players_online_total", true, PlayersOnlineTotal::new),
metricConfig("players_total", true, PlayersTotal::new),
metricConfig("tps", true, Tps::new),
metricConfig("world_size", true, WorldSize::new),

metricConfig("jvm_threads", true, ThreadsWrapper::new),
metricConfig("jvm_gc", true, GarbageCollectorWrapper::new),
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/de/sldk/mc/metrics/WorldSize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package de.sldk.mc.metrics;

import de.sldk.mc.utils.PathFileSize;
import io.prometheus.client.Gauge;
import java.util.logging.Logger;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;

public class WorldSize extends WorldMetric {

private final Logger log;
private static final Gauge WORLD_SIZE = Gauge.build()
.name(prefix("world_size"))
.help("World size in bytes")
.labelNames("world")
.create();

public WorldSize(Plugin plugin) {
super(plugin, WORLD_SIZE);
this.log = plugin.getLogger();
}

@Override
protected void clear() {
WORLD_SIZE.clear();
}

@Override
public void collect(World world) {
try {
PathFileSize pathUtils = new PathFileSize(world.getWorldFolder().toPath());
long size = pathUtils.getSize();
String worldName = world.getName();
WORLD_SIZE.labels(worldName).set(size);
} catch (Throwable t) {
log.throwing(this.getClass().getSimpleName(), "collect", t);
}
}
}
32 changes: 32 additions & 0 deletions src/main/java/de/sldk/mc/utils/PathFileSize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.sldk.mc.utils;

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicLong;

public class PathFileSize {
final private Path path;

public PathFileSize(Path path) {
if (path == null) {
throw new IllegalArgumentException("Path must not be null!");
}
this.path = path;
}

public long getSize() throws IOException {
final AtomicLong size = new AtomicLong(0);
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
size.addAndGet(attrs.size());
return FileVisitResult.CONTINUE;
}

public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
});
return size.get();
}
}
108 changes: 108 additions & 0 deletions src/test/java/de/sldk/mc/metrics/WorldSizeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package de.sldk.mc.metrics;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import io.prometheus.client.CollectorRegistry;
import java.io.*;
import java.nio.file.*;
import java.util.Random;
import net.bytebuddy.utility.RandomString;
import org.bukkit.World;
import org.bukkit.plugin.Plugin;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.io.TempDir;

public class WorldSizeTest {

private static final String METRIC_NAME = "mc_world_size";
private static final String[] METRIC_LABELS = { "world" };

private WorldSize worldSizeMetric;

@TempDir
private Path path;
private File file;
private World world;

@BeforeEach
public void beforeEach() {
worldSizeMetric = new WorldSize(mock(Plugin.class));
worldSizeMetric.enable();
}

@AfterEach
public void afterEach() {
CollectorRegistry.defaultRegistry.clear();
}

@Test
public void doesNotThrowIfFileDoesNotExist() {
givenWorldFileDoesNotExist();
assertDoesNotThrow(() -> worldSizeMetric.collect(world));
}

@Test
public void doesNotPopulateMetricIfFileDoesNotExist() {
givenWorldFileDoesNotExist();
worldSizeMetric.collect(world);
assertMetricIsEmpty();
}

@Test
public void setsMetricWithCorrectNameAndLabel() {
String worldName = new RandomString(10).nextString();
givenWorldFileExists(worldName);
worldSizeMetric.collect(world);
assertNotNull(getMetricValue(worldName));
}

@Test
public void setsCorrectWorldSizeValue() throws IOException {
String worldName = new RandomString(10).nextString();
int worldSize = new Random().ints(128, 1024).findFirst().getAsInt();
givenWorldFileExists(worldName, worldSize);
worldSizeMetric.collect(world);
Double value = getMetricValue(worldName);
assertEquals(worldSize, value.longValue());
}

private Double getMetricValue(String worldName) {
return CollectorRegistry.defaultRegistry.getSampleValue(
METRIC_NAME,
METRIC_LABELS,
new String[] { worldName }
);
}

private void assertMetricIsEmpty() {
assertNull(CollectorRegistry.defaultRegistry.getSampleValue(METRIC_NAME));
}

private void givenWorldFileDoesNotExist() {
String worldName = "some_file_that_surely_does_not_exist_" + new RandomString(10).nextString();
file = mock(File.class);
world = mock(World.class);

when(file.toPath()).thenReturn(path);
when(world.getWorldFolder()).thenReturn(file);
when(world.getName()).thenReturn(worldName);
}

private void givenWorldFileExists(String worldName, int worldSize) throws IOException {
givenWorldFileExists(worldName);
String filename = new RandomString(10).nextString();
File f = path.resolve(filename).toFile();
try (FileWriter writer = new FileWriter(f)) {
writer.write(new RandomString(worldSize).nextString());
}
}

private void givenWorldFileExists(String worldName) {
file = path.toFile();
world = mock(World.class);

when(world.getWorldFolder()).thenReturn(file);
when(world.getName()).thenReturn(worldName);
}
}
123 changes: 123 additions & 0 deletions src/test/java/de/sldk/mc/utils/PathFileSizeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package de.sldk.mc.utils;

import static org.junit.jupiter.api.Assertions.*;

import java.io.*;
import java.nio.file.*;
import java.util.Random;
import net.bytebuddy.utility.RandomString;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.io.TempDir;

public class PathFileSizeTest {
@TempDir
private Path path;
private PathFileSize pathFileSize;

@Nested
public class WhenPathIsNull {
@Test
public void shouldThrowException() {
assertThrows(IllegalArgumentException.class, () -> new PathFileSize(null));
}
}

@Nested
public class WhenPathDoesNotExist {
@BeforeEach
public void beforeEach() {
path = new File("./some/random/path/that/surely/does/not/exist.bf").toPath();
pathFileSize = new PathFileSize(path);
}

@Test
public void returnsSizeZero() throws IOException {
assertEquals(0, pathFileSize.getSize());
}
}

@Nested
public class WhenPathIsFile {
@BeforeEach
public void beforeEach() throws IOException {
path = File.createTempFile("test", "txt").toPath();
pathFileSize = new PathFileSize(path);
}

@Test
public void returnsZeroIfFileIsEmpty() throws IOException {
assertEquals(0, pathFileSize.getSize());
}

@RepeatedTest(5)
public void returnsFileSize() throws IOException {
int length = new Random().nextInt(10000);
try (FileWriter fileWriter = new FileWriter(path.toFile())) {
fileWriter.write(new RandomString(length).nextString());
}
assertEquals(length, pathFileSize.getSize());
}
}

@Nested
public class WhenPathIsDirectory {
@BeforeEach
public void beforeEach() throws IOException {
path = Files.createTempDirectory("test-");
pathFileSize = new PathFileSize(path);
}

@Test
public void returnsZeroIfDirectoryIsEmpty() throws IOException {
assertEquals(0, pathFileSize.getSize());
}

@Test
public void returnsSizeOfSingleFile() throws IOException {
long length = createMultipleFilesInTmpDirectory(path, 1);
assertEquals(length, pathFileSize.getSize());
}

@Test
public void returnsSizeOfMultipleFiles() throws IOException {
int files = new Random().ints(2, 10).findFirst().getAsInt();
long length = createMultipleFilesInTmpDirectory(path, files);
assertEquals(length, pathFileSize.getSize());
}

@Test
public void returnSizeOfNestedDirectories() throws IOException {
int directories = new Random().ints(2, 10).findFirst().getAsInt();
long length = createMultiplePopulatedDirectories(path, directories);
assertEquals(length, pathFileSize.getSize());
}
}

private static long createMultiplePopulatedDirectories(Path path, int directories) throws IOException {
long length = 0;
for (int i = 0; i < directories; i++) {
int files = new Random().ints(2, 10).findFirst().getAsInt();
Path directory = Files.createTempDirectory(path, "dir" + i + "-");
length += createMultipleFilesInTmpDirectory(directory, files);
}
return length;
}

private static long createMultipleFilesInTmpDirectory(Path path, int files) throws IOException {
int totalLength = 0;
for (int i = 0; i < files; i++) {
int length = new Random().nextInt(10000);
createFileInTmpDirectory(path, "test" + i + "txt", length);
totalLength += length;
}
return totalLength;
}

private static File createFileInTmpDirectory(Path path, String name, int length) throws IOException {
File file = path.resolve(name).toFile();
try (FileWriter fileWriter = new FileWriter(file)) {
fileWriter.write(new RandomString(length).nextString());
}
return file;
}
}

0 comments on commit 308c136

Please sign in to comment.