Skip to content

Commit

Permalink
Improve unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
lucko committed Sep 2, 2024
1 parent 55b3839 commit 35b557a
Show file tree
Hide file tree
Showing 14 changed files with 568 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,11 @@
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics;
import me.lucko.spark.common.platform.MetadataProvider;
import me.lucko.spark.common.platform.SparkMetadata;
import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider;
import me.lucko.spark.common.sampler.aggregator.DataAggregator;
import me.lucko.spark.common.sampler.node.MergeMode;
import me.lucko.spark.common.sampler.node.ThreadNode;
import me.lucko.spark.common.sampler.source.ClassSourceLookup;
import me.lucko.spark.common.sampler.source.SourceMetadata;
import me.lucko.spark.common.sampler.window.ProtoTimeEncoder;
import me.lucko.spark.common.sampler.window.WindowStatisticsCollector;
import me.lucko.spark.common.util.classfinder.ClassFinder;
Expand All @@ -42,7 +39,6 @@
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static int windowNow() {
/**
* Gets a prune predicate that can be passed to {@link DataAggregator#pruneData(IntPredicate)}.
*
* @return the prune predicate
* @return the prune predicate - returns true for windows that should be pruned
*/
public static IntPredicate keepHistoryBefore(int currentWindow) {
// windows that were earlier than (currentWindow minus history size) should be pruned
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

package me.lucko.spark.common.sampler.window;

import me.lucko.spark.common.sampler.async.jfr.Dictionary;
import me.lucko.spark.common.sampler.node.ThreadNode;
import org.jetbrains.annotations.VisibleForTesting;

import java.util.HashMap;
import java.util.List;
Expand All @@ -43,16 +43,10 @@ public class ProtoTimeEncoder {
/** A map of key value -> index in the keys array */
private final Map<Integer, Integer> keysToIndex;

public ProtoTimeEncoder(LongToDoubleFunction valueTransformer, List<ThreadNode> sourceData) {
@VisibleForTesting
ProtoTimeEncoder(LongToDoubleFunction valueTransformer, IntStream keys) {
this.valueTransformer = valueTransformer;

// get an array of all keys that show up in the source data
this.keys = sourceData.stream()
.map(n -> n.getTimeWindows().stream().mapToInt(i -> i))
.reduce(IntStream.empty(), IntStream::concat)
.distinct()
.sorted()
.toArray();
this.keys = keys.distinct().sorted().toArray();

// construct a reverse index lookup
this.keysToIndex = new HashMap<>(this.keys.length);
Expand All @@ -61,6 +55,13 @@ public ProtoTimeEncoder(LongToDoubleFunction valueTransformer, List<ThreadNode>
}
}

public ProtoTimeEncoder(LongToDoubleFunction valueTransformer, List<ThreadNode> sourceData) {
this(valueTransformer, sourceData.stream()
.map(n -> n.getTimeWindows().stream().mapToInt(i -> i))
.reduce(IntStream.empty(), IntStream::concat)
);
}

/**
* Gets an array of the keys that could be encoded by this encoder.
*
Expand All @@ -71,7 +72,7 @@ public int[] getKeys() {
}

/**
* Encode a {@link Dictionary} (map) of times/durations into a double array.
* Encode a map of times/durations into a double array.
*
* @param times a dictionary of times (unix-time millis -> duration in microseconds)
* @return the times encoded as a double array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,20 @@

package me.lucko.spark.common;

import com.google.common.collect.ImmutableSet;
import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.test.plugin.TestSparkPlugin;
import net.kyori.adventure.text.Component;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class SparkPlatformTest {
Expand All @@ -37,4 +45,63 @@ public void testEnableDisable(@TempDir Path directory) {
}
}

@Test
public void testPermissions(@TempDir Path directory) {
try (TestSparkPlugin plugin = new TestSparkPlugin(directory)) {
SparkPlatform platform = plugin.platform();

Set<String> permissions = platform.getAllSparkPermissions();
assertEquals(
ImmutableSet.of(
"spark",
"spark.profiler",
"spark.tps",
"spark.ping",
"spark.healthreport",
"spark.tickmonitor",
"spark.gc",
"spark.gcmonitor",
"spark.heapsummary",
"spark.heapdump",
"spark.activity"
),
permissions
);

TestCommandSender testSender = new TestCommandSender();
assertFalse(platform.hasPermissionForAnyCommand(testSender));

testSender.permissions.add("spark.tps");
assertTrue(platform.hasPermissionForAnyCommand(testSender));

testSender.permissions.clear();
testSender.permissions.add("spark");
assertTrue(platform.hasPermissionForAnyCommand(testSender));
}
}

private static final class TestCommandSender implements CommandSender {
private final Set<String> permissions = new HashSet<>();

@Override
public String getName() {
return "Test";
}

@Override
public UUID getUniqueId() {
return new UUID(0, 0);
}

@Override
public void sendMessage(Component message) {

}

@Override
public boolean hasPermission(String permission) {
return this.permissions.contains(permission);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* This file is part of spark.
*
* Copyright (c) lucko (Luck) <[email protected]>
* 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.sampler.node;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import me.lucko.spark.common.sampler.SamplerMode;
import me.lucko.spark.common.sampler.async.AsyncStackTraceElement;
import me.lucko.spark.common.sampler.window.ProtoTimeEncoder;
import me.lucko.spark.common.util.MethodDisambiguator;
import me.lucko.spark.proto.SparkSamplerProtos;
import org.junit.jupiter.api.Test;

import java.util.Collection;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class NodeTest {

private static final StackTraceNode.Describer<AsyncStackTraceElement> STACK_TRACE_DESCRIBER = (element, parent) -> new StackTraceNode.Description(element.getClassName(), element.getMethodName(), element.getMethodDescription());
private static final int WINDOW = 10;

private static final AsyncStackTraceElement NODE_0 = new AsyncStackTraceElement("java.lang.Thread", "run", "()V");
private static final AsyncStackTraceElement NODE_1_1 = new AsyncStackTraceElement("test.Foo", "run", "()V");
private static final AsyncStackTraceElement NODE_1_2_1 = new AsyncStackTraceElement("test.Foo", "example", "()V");
private static final AsyncStackTraceElement NODE_2_1 = new AsyncStackTraceElement("test.Bar", "run", "()V");
private static final AsyncStackTraceElement NODE_2_2_1 = new AsyncStackTraceElement("test.Bar", "example", "()V");

private static final AsyncStackTraceElement[] STACK_1 = {NODE_1_2_1, NODE_1_1, NODE_0};
private static final AsyncStackTraceElement[] STACK_2 = {NODE_2_2_1, NODE_2_1, NODE_0};

@Test
public void testThreadLabels() {
ThreadNode node = new ThreadNode("Test Thread");
assertEquals("Test Thread", node.getThreadGroup());
assertEquals("Test Thread", node.getThreadLabel());

node.setThreadLabel("Test");
assertEquals("Test", node.getThreadLabel());
}

@Test
public void testBasicLog() {
ThreadNode threadNode = new ThreadNode("Test Thread");
assertEquals(0, threadNode.getTimeWindows().size());

threadNode.log(STACK_TRACE_DESCRIBER, STACK_1, TimeUnit.SECONDS.toMicros(1), WINDOW);

Collection<StackTraceNode> children1 = threadNode.getChildren();
assertEquals(1, children1.size());
assertEquals(ImmutableSet.of(WINDOW), threadNode.getTimeWindows());

StackTraceNode node1 = children1.iterator().next();
assertEquals(ImmutableSet.of(WINDOW), node1.getTimeWindows());
assertEquals("java.lang.Thread", node1.getClassName());
assertEquals("run", node1.getMethodName());
assertEquals("()V", node1.getMethodDescription());
assertEquals(StackTraceNode.NULL_LINE_NUMBER, node1.getLineNumber());
assertEquals(StackTraceNode.NULL_LINE_NUMBER, node1.getParentLineNumber());
assertEquals(TimeUnit.SECONDS.toMicros(1), node1.getTimeAccumulator(WINDOW).longValue());

threadNode.log(STACK_TRACE_DESCRIBER, STACK_2, TimeUnit.SECONDS.toMicros(1), WINDOW);
assertEquals(TimeUnit.SECONDS.toMicros(2), node1.getTimeAccumulator(WINDOW).longValue());

Collection<StackTraceNode> children2 = node1.getChildren();
assertEquals(2, children2.size());

for (StackTraceNode node2 : children2) {
assertEquals(ImmutableSet.of(WINDOW), node2.getTimeWindows());
assertEquals(TimeUnit.SECONDS.toMicros(1), node2.getTimeAccumulator(WINDOW).longValue());
}
}

@Test
public void testToProto() {
ThreadNode threadNode = new ThreadNode("Test Thread");
threadNode.log(STACK_TRACE_DESCRIBER, STACK_1, TimeUnit.SECONDS.toMicros(1), WINDOW);
threadNode.log(STACK_TRACE_DESCRIBER, STACK_1, TimeUnit.SECONDS.toMicros(1), WINDOW + 1);
threadNode.log(STACK_TRACE_DESCRIBER, STACK_2, TimeUnit.SECONDS.toMicros(1), WINDOW + 1);

ProtoTimeEncoder timeEncoder = new ProtoTimeEncoder(SamplerMode.EXECUTION.valueTransformer(), ImmutableList.of(threadNode));
int[] keys = timeEncoder.getKeys();
assertArrayEquals(new int[]{WINDOW, WINDOW + 1}, keys);

SparkSamplerProtos.ThreadNode proto = threadNode.toProto(
MergeMode.sameMethod(new MethodDisambiguator(null)),
timeEncoder
);

SparkSamplerProtos.ThreadNode expected = SparkSamplerProtos.ThreadNode.newBuilder()
.setName("Test Thread")
.addTimes(1000)
.addTimes(2000)
.addChildren(SparkSamplerProtos.StackTraceNode.newBuilder()
.setClassName("test.Bar")
.setMethodDesc("()V")
.setMethodName("example")
.addTimes(0)
.addTimes(1000)
)
.addChildren(SparkSamplerProtos.StackTraceNode.newBuilder()
.setClassName("test.Bar")
.setMethodDesc("()V")
.setMethodName("run")
.addTimes(0)
.addTimes(1000)
.addChildrenRefs(0)
)
.addChildren(SparkSamplerProtos.StackTraceNode.newBuilder()
.setClassName("test.Foo")
.setMethodDesc("()V")
.setMethodName("example")
.addTimes(1000)
.addTimes(1000)
)
.addChildren(SparkSamplerProtos.StackTraceNode.newBuilder()
.setClassName("test.Foo")
.setMethodDesc("()V")
.setMethodName("run")
.addTimes(1000)
.addTimes(1000)
.addChildrenRefs(2)
)
.addChildren(SparkSamplerProtos.StackTraceNode.newBuilder()
.setClassName("java.lang.Thread")
.setMethodDesc("()V")
.setMethodName("run")
.addTimes(1000)
.addTimes(2000)
.addChildrenRefs(1)
.addChildrenRefs(3)
)
.addChildrenRefs(4)
.build();

assertEquals(expected, proto);
}

@Test
public void testRemoveTimeWindows() {
ThreadNode threadNode = new ThreadNode("Test Thread");
threadNode.log(STACK_TRACE_DESCRIBER, STACK_1, TimeUnit.SECONDS.toMicros(1), WINDOW);
threadNode.log(STACK_TRACE_DESCRIBER, STACK_2, TimeUnit.SECONDS.toMicros(1), WINDOW + 1);

StackTraceNode threadRunNode = threadNode.getChildren().iterator().next();
Collection<StackTraceNode> fooBarNodes = threadRunNode.getChildren();

assertEquals(2, threadNode.getTimeWindows().size());
assertEquals(2, threadRunNode.getChildren().size());
assertEquals(2, threadRunNode.getTimeWindows().size());

for (StackTraceNode node : fooBarNodes) {
assertEquals(1, node.getTimeWindows().size());
assertEquals(1, node.getChildren().size());
assertEquals(1, node.getChildren().iterator().next().getTimeWindows().size());
assertEquals(0, node.getChildren().iterator().next().getChildren().size());
}

assertFalse(threadNode.removeTimeWindowsRecursively(w -> w == WINDOW));
assertEquals(1, threadNode.getTimeWindows().size());
assertEquals(1, threadRunNode.getChildren().size());
assertEquals(1, threadRunNode.getTimeWindows().size());

assertTrue(threadNode.removeTimeWindowsRecursively(w -> w == WINDOW + 1));
assertEquals(0, threadNode.getTimeWindows().size());
assertEquals(0, threadNode.getChildren().size());

// doesn't bother updating nested children that have been removed
for (StackTraceNode node : fooBarNodes) {
assertEquals(1, node.getTimeWindows().size());
assertEquals(1, node.getChildren().size());
assertEquals(1, node.getChildren().iterator().next().getTimeWindows().size());
assertEquals(0, node.getChildren().iterator().next().getChildren().size());
}
}

}
Loading

0 comments on commit 35b557a

Please sign in to comment.