diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a8a77b9..8f1cf05 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -16,6 +16,7 @@ diff --git a/.idea/runConfigurations/Debug_Robot_via_IP.xml b/.idea/runConfigurations/Debug_Robot_via_IP.xml deleted file mode 100644 index 4c693ff..0000000 --- a/.idea/runConfigurations/Debug_Robot_via_IP.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Debug_Robot_via_USB.xml b/.idea/runConfigurations/Debug_Robot_via_USB.xml deleted file mode 100644 index b83b06c..0000000 --- a/.idea/runConfigurations/Debug_Robot_via_USB.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/lib/src/main/java/badgerlog/events/EventRegistry.java b/lib/src/main/java/badgerlog/events/EventRegistry.java index 0989d46..b192431 100644 --- a/lib/src/main/java/badgerlog/events/EventRegistry.java +++ b/lib/src/main/java/badgerlog/events/EventRegistry.java @@ -6,12 +6,11 @@ import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.wpilibj.Timer; -import java.util.ArrayList; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; /** @@ -19,7 +18,7 @@ */ public class EventRegistry { private static final Queue> eventQueue = new ConcurrentLinkedQueue<>(); - private static final Map>> watchedEntries = new HashMap<>(); + private static final Map>> watchedEntries = new ConcurrentHashMap<>(); /** * Activates any queued events from the previous loop @@ -74,14 +73,14 @@ public static void registerWatcher(WatcherEvent event, EventMetadata metadata */ public static void addWatchedEntry(NTEntry entry, List watcherNames) { for (String watcher : watcherNames) { - List> entries = watchedEntries.computeIfAbsent(watcher, k -> new ArrayList<>()); + Queue> entries = watchedEntries.computeIfAbsent(watcher, k -> new ConcurrentLinkedQueue<>()); entries.add(entry); } } @SuppressWarnings("unchecked") private static void addManagedWatcherEvent(WatcherEvent event, EventMetadata metadata, NetworkTableEvent ntEvent) { - List> namedEntries = watchedEntries.getOrDefault(metadata.name(), new ArrayList<>()); + Queue> namedEntries = watchedEntries.getOrDefault(metadata.name(), new ConcurrentLinkedQueue<>()); for (NTEntry entry : namedEntries) { String actualKey = "/BadgerLog/" + entry.getKey(); diff --git a/lib/src/main/java/badgerlog/processing/EntryAspect.java b/lib/src/main/java/badgerlog/processing/EntryAspect.java index 00a83f1..cc424e0 100644 --- a/lib/src/main/java/badgerlog/processing/EntryAspect.java +++ b/lib/src/main/java/badgerlog/processing/EntryAspect.java @@ -32,8 +32,10 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; /** * Utilizes AspectJ to weave entry generation and management into target classes. @@ -41,6 +43,7 @@ @Aspect public class EntryAspect { private final Entries entries = new Entries(new HashMap<>()); + private final List> blacklistedClasses = new ArrayList<>(); @Pointcut("!within(edu.wpi.first..*) && !within(badgerlog..*) && !within(java..*) && !within(javax..*)") public void onlyRobotCode() { @@ -73,6 +76,13 @@ public void getterMethodExecution(Entry entry) { @After("onlyRobotCode() && staticinitialization(*)") public void createStaticEntries(JoinPoint joinPoint) { Class clazz = joinPoint.getSignature().getDeclaringType(); + if (blacklistedClasses.contains(clazz)) { + return; + } + if (!Members.hasAnyOfAnnotation(clazz, Entry.class)) { + blacklistedClasses.add(clazz); + } + entries.addInstance(clazz, null); Members.iterateOverAnnotatedFields(clazz, Entry.class, true, field -> createFieldEntry(field, null)); @@ -83,7 +93,16 @@ public void createStaticEntries(JoinPoint joinPoint) { @After("onlyRobotCode() && newInitialization()") public void createInstanceEntries(JoinPoint joinPoint) { Object instance = joinPoint.getThis(); - entries.addInstance(instance.getClass(), instance); + Class clazz = joinPoint.getSignature().getDeclaringType(); + + if (blacklistedClasses.contains(clazz)) { + return; + } + if (!Members.hasAnyOfAnnotation(clazz, Entry.class)) { + blacklistedClasses.add(clazz); + } + + entries.addInstance(clazz, instance); Members.iterateOverAnnotatedFields(instance .getClass(), Entry.class, false, field -> createFieldEntry(field, instance)); @@ -91,8 +110,8 @@ public void createInstanceEntries(JoinPoint joinPoint) { Members.iterateOverAnnotatedMethods(instance .getClass(), Entry.class, false, method -> createMethodEntry(method, instance)); - if (instance.getClass().isAnnotationPresent(Entry.class)) { - Field[] allFields = instance.getClass().getFields(); + if (clazz.isAnnotationPresent(Entry.class)) { + Field[] allFields = clazz.getFields(); Arrays.stream(allFields) .filter(this::isValidForClassGeneration) .forEach(field -> createFieldEntry(field, instance)); diff --git a/lib/src/main/java/badgerlog/processing/EventAspect.java b/lib/src/main/java/badgerlog/processing/EventAspect.java index cde36eb..937501c 100644 --- a/lib/src/main/java/badgerlog/processing/EventAspect.java +++ b/lib/src/main/java/badgerlog/processing/EventAspect.java @@ -14,6 +14,8 @@ import org.aspectj.lang.annotation.Pointcut; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -22,6 +24,8 @@ @Aspect public class EventAspect { + private final List> blacklistedClasses = new ArrayList<>(); + @Pointcut("!within(edu.wpi.first..*) && !within(badgerlog..*) && !within(java..*) && !within(javax..*)") public void onlyRobotCode() { } @@ -34,6 +38,13 @@ public void newInitialization() { public void createStaticEvents(JoinPoint joinPoint) { Class clazz = joinPoint.getSignature().getDeclaringType(); + if (blacklistedClasses.contains(clazz)) { + return; + } + if (!Members.hasAnyOfAnnotation(clazz, Watcher.class) && !Members.hasAnyOfAnnotation(clazz, RawWatcher.class)) { + blacklistedClasses.add(clazz); + } + Members.iterateOverAnnotatedMethods(clazz, Watcher.class, true, method -> handleWatcherMethod(method, null)); Members.iterateOverAnnotatedMethods(clazz, RawWatcher.class, true, method -> handleRawWatcherMethod(method, null)); } @@ -43,6 +54,13 @@ public void createInstanceEvents(JoinPoint joinPoint) { Class clazz = joinPoint.getSignature().getDeclaringType(); Object workingClass = joinPoint.getThis(); + if (blacklistedClasses.contains(clazz)) { + return; + } + if (!Members.hasAnyOfAnnotation(clazz, Watcher.class) && !Members.hasAnyOfAnnotation(clazz, RawWatcher.class)) { + blacklistedClasses.add(clazz); + } + Members.iterateOverAnnotatedMethods(clazz, Watcher.class, false, method -> handleWatcherMethod(method, workingClass)); Members.iterateOverAnnotatedMethods(clazz, RawWatcher.class, false, method -> handleRawWatcherMethod(method, workingClass)); } diff --git a/lib/src/main/java/badgerlog/processing/data/ClassData.java b/lib/src/main/java/badgerlog/processing/data/ClassData.java index 327c989..8a5e2d4 100644 --- a/lib/src/main/java/badgerlog/processing/data/ClassData.java +++ b/lib/src/main/java/badgerlog/processing/data/ClassData.java @@ -1,5 +1,7 @@ package badgerlog.processing.data; +import lombok.Getter; + import java.lang.reflect.Field; import java.util.Map; import java.util.Objects; @@ -7,14 +9,21 @@ /** * Holds the data used for an entire class with entries inside. Contains all instances of the class. */ -public record ClassData(Map fieldMap, Map instanceEntries) { +public final class ClassData { + private final Map fieldMap; + private final Map instanceEntries; + + @Getter + private int instanceCount = 0; /** * Constructs a new ClassData given two maps for fields and instances. * * @param fieldMap the map of strings to fields to use * @param instanceEntries the map of instances to {@link InstanceData} */ - public ClassData { + public ClassData(Map fieldMap, Map instanceEntries) { + this.fieldMap = fieldMap; + this.instanceEntries = instanceEntries; } /** @@ -26,10 +35,38 @@ public void addField(Field field) { fieldMap.put(field.getName(), field); } - /** - * {@return the number of instances currently present for the class} - */ - public int getInstanceCount() { - return Math.toIntExact(instanceEntries().keySet().stream().filter(Objects::nonNull).count()); + public Map fieldMap() { + return fieldMap; + } + + public Map instanceEntries() { + return instanceEntries; + } + + public void incrementInstanceCount() { + instanceCount++; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + var that = (ClassData) obj; + return Objects.equals(this.fieldMap, that.fieldMap) && Objects + .equals(this.instanceEntries, that.instanceEntries); + } + + @Override + public int hashCode() { + return Objects.hash(fieldMap, instanceEntries); + } + + @Override + public String toString() { + return "ClassData[" + "fieldMap=" + fieldMap + ", " + "instanceEntries=" + instanceEntries + ']'; } } diff --git a/lib/src/main/java/badgerlog/processing/data/Entries.java b/lib/src/main/java/badgerlog/processing/data/Entries.java index be89340..6fad8e0 100644 --- a/lib/src/main/java/badgerlog/processing/data/Entries.java +++ b/lib/src/main/java/badgerlog/processing/data/Entries.java @@ -3,6 +3,7 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import java.util.WeakHashMap; /** * Contains all the generated entry data. @@ -58,10 +59,12 @@ public InstanceData getInstanceEntries(Class clazz, Object instance) { public void addInstance(Class clazz, Object instance) { ClassData classData = getClassData(clazz); if (classData == null) { - classData = new ClassData(new HashMap<>(), new HashMap<>()); + classData = new ClassData(new HashMap<>(), new WeakHashMap<>()); classDataMap.put(clazz, classData); } - + if (instance != null) { + classData.incrementInstanceCount(); + } classData.instanceEntries().putIfAbsent(instance, new InstanceData(new HashMap<>())); } diff --git a/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java b/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java index 3544fcb..660d1e0 100644 --- a/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java +++ b/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java @@ -1,5 +1,6 @@ package badgerlog.utilities; +import badgerlog.networktables.MockNTEntry; import badgerlog.networktables.NT; import badgerlog.networktables.NTEntry; import badgerlog.networktables.NTUpdatable; @@ -24,8 +25,20 @@ public NT put(String key, NT value) { if (containsKey(key)) { NT oldValue = this.get(key); if (oldValue instanceof AutoCloseable closeable) { + String extraInfo = ""; + + if (oldValue instanceof NTEntry entry) { + extraInfo = entry.getKey(); + } + if (oldValue instanceof MockNTEntry entry) { + extraInfo = entry.realEntry().getKey(); + } + + extraInfo = extraInfo.isEmpty() ? "No Extra Info" : extraInfo + " closed by " + key; + extraInfo += "\n"; + closeable.close(); - ErrorLogger.customError("NetworkTable entry closed from adding another entry"); + ErrorLogger.customError("NetworkTable entry closed from adding another entry.\n" + extraInfo); } } diff --git a/lib/src/main/java/badgerlog/utilities/Members.java b/lib/src/main/java/badgerlog/utilities/Members.java index e6bfa5e..283137c 100644 --- a/lib/src/main/java/badgerlog/utilities/Members.java +++ b/lib/src/main/java/badgerlog/utilities/Members.java @@ -48,6 +48,22 @@ public static void iterateOverAnnotatedFields(Class clazz, Class clazz, Class annotationClass) { + Member[] fields = getFieldsWithAnnotation(clazz, annotationClass); + Member[] methods = getMethodsWithAnnotation(clazz, annotationClass); + boolean hasClassAnnotation = clazz.isAnnotationPresent(annotationClass); + return fields.length > 0 || methods.length > 0 || hasClassAnnotation; + } + /** * {@code object} defaults to {@code null}. This method should only be used if the field is known to be static. * diff --git a/test-project/build.gradle b/test-project/build.gradle index 29097bb..e5fadfc 100644 --- a/test-project/build.gradle +++ b/test-project/build.gradle @@ -6,6 +6,7 @@ plugins { id "idea" // id 'io.freefair.aspectj' version '8.14' id 'io.freefair.aspectj.post-compile-weaving' version '8.14' + id 'me.champeau.jmh' version '0.7.2' } def javaVersion = JavaVersion.VERSION_17 @@ -86,11 +87,24 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-params" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" + jmh 'org.openjdk.jmh:jmh-core:1.37' + jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + implementation "org.aspectj:aspectjrt:1.9.24" - aspect files("../lib/build/libs/lib.jar") + aspect project(":badgerlog") + + implementation project(":badgerlog") + annotationProcessor project(":badgerlog") +} + +jmh { + resultFormat = 'JSON' + resultsFile = file("${buildDir}/reports/jmh/results.json") + jvmArgs = [ + "-Djava.library.path=${projectDir}/build/jni/release", '-Xms6G', '-Xmx6G', "-XX:+FlightRecorder" + ] as Iterable - implementation files("../lib/build/libs/lib.jar") - annotationProcessor files("../lib/build/libs/lib.jar") + profilers = ['gc'] } tasks.register('simulate') { @@ -103,11 +117,12 @@ tasks.register('buildLib', GradleBuild) { } sourceSets { - files("../lib/build/libs/lib-sources.jar") + project(":badgerlog") } test{ useJUnitPlatform() + maxHeapSize = "4g" systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true' } diff --git a/test-project/settings.gradle b/test-project/settings.gradle index 2365259..a09672b 100644 --- a/test-project/settings.gradle +++ b/test-project/settings.gradle @@ -26,5 +26,8 @@ pluginManagement { } } +include 'badgerlog' +project(':badgerlog').projectDir = file('../lib') + Properties props = System.getProperties() props.setProperty("org.gradle.internal.native.headers.unresolved.dependencies.ignore", "true") diff --git a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java new file mode 100644 index 0000000..9338069 --- /dev/null +++ b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java @@ -0,0 +1,39 @@ +package badgerlog; + +import frc.robot.testing.performance.NoAnnotation; +import frc.robot.testing.performance.SingleAnnotation; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@State(Scope.Thread) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 25) +@Fork(1) +public class EntryCreationBenchmark { + + @Benchmark + public void normalIntegerCreation(){ + new NoAnnotation(); + } + + @Benchmark + public void entryIntegerCreation(){ + new SingleAnnotation(); + } + + @Benchmark + public void defaultObjectCreation(){ + new Object(); + } +} diff --git a/test-project/src/main/java/frc/robot/EventsTest.java b/test-project/src/main/java/frc/robot/EventsTest.java index 8e04c3e..f406330 100644 --- a/test-project/src/main/java/frc/robot/EventsTest.java +++ b/test-project/src/main/java/frc/robot/EventsTest.java @@ -14,12 +14,12 @@ public class EventsTest implements Testing{ @Entry(EntryType.SUBSCRIBER) - @Watched("integer") + @Watched({"integer", "tester"}) private int watcherTest = 1; @Entry(EntryType.SUBSCRIBER) @Struct(StructType.SUB_TABLE) - @Watched("pose2d") + @Watched({"pose2d", "tester"}) private Pose2d robotPose = Pose2d.kZero; @Override @@ -42,6 +42,11 @@ private void integerWatcher(EventData data){ System.out.println("EVENT value: "+data + "-> Fired"); } + @Watcher(type = void.class, name = "tester") + private void allWatcher(EventData data){ + System.out.println("ALL value: "+data + "-> Fired"); + } + @RawWatcher(type = void.class, keys = "/BadgerLog/EventsTest", eventType = EventType.ALL) private void integerRawWatcher(EventData data){ System.out.println("EVENT value RAW: "+data + "-> Fired"); diff --git a/test-project/src/main/java/frc/robot/testing/performance/NoAnnotation.java b/test-project/src/main/java/frc/robot/testing/performance/NoAnnotation.java new file mode 100644 index 0000000..0eb9408 --- /dev/null +++ b/test-project/src/main/java/frc/robot/testing/performance/NoAnnotation.java @@ -0,0 +1,5 @@ +package frc.robot.testing.performance; + +public class NoAnnotation { + public Integer integer = 0; +} diff --git a/test-project/src/main/java/frc/robot/testing/performance/SingleAnnotation.java b/test-project/src/main/java/frc/robot/testing/performance/SingleAnnotation.java new file mode 100644 index 0000000..a064658 --- /dev/null +++ b/test-project/src/main/java/frc/robot/testing/performance/SingleAnnotation.java @@ -0,0 +1,9 @@ +package frc.robot.testing.performance; + +import badgerlog.annotations.Entry; +import badgerlog.annotations.EntryType; + +public class SingleAnnotation { + @Entry(EntryType.PUBLISHER) + public Integer integer = 0; +} diff --git a/vendordep.json b/vendordep.json index 4aca3fc..55e955c 100644 --- a/vendordep.json +++ b/vendordep.json @@ -3,7 +3,7 @@ { "groupId": "com.github.team1306", "artifactId": "badger-log", - "version": "2025.4.1" + "version": "2025.4.2" } ], "fileName": "badger-log.json", @@ -15,6 +15,6 @@ "https://jitpack.io" ], "cppDependencies": [], - "version": "2025.4.1", + "version": "2025.4.2", "uuid": "e5beadf1-8b5a-4317-8b9a-91a77b5390f6" }