From cc2f09c2e7f71ab3ea5712fcdaa6f7ba93d28725 Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:27:36 -0600 Subject: [PATCH 01/13] fix unnecessary computations when class has no annotated fields. --- .../badgerlog/processing/EntryAspect.java | 27 ++++++++++++++++--- .../java/badgerlog/utilities/Members.java | 13 +++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/badgerlog/processing/EntryAspect.java b/lib/src/main/java/badgerlog/processing/EntryAspect.java index 00a83f1..e13ad9d 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,7 +43,8 @@ @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/utilities/Members.java b/lib/src/main/java/badgerlog/utilities/Members.java index e6bfa5e..36797ff 100644 --- a/lib/src/main/java/badgerlog/utilities/Members.java +++ b/lib/src/main/java/badgerlog/utilities/Members.java @@ -48,6 +48,19 @@ 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. * From 7e60f06b196180b9e819cb849c4f0aeb4591c0c0 Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:27:53 -0600 Subject: [PATCH 02/13] add benchmarks to see improvements in performance --- .../runConfigurations/Debug_Robot_via_IP.xml | 12 ------- .../runConfigurations/Debug_Robot_via_USB.xml | 12 ------- test-project/build.gradle | 15 ++++++++ .../badgerlog/EntryCreationBenchmark.java | 34 +++++++++++++++++++ .../testing/performance/NoAnnotation.java | 5 +++ .../testing/performance/SingleAnnotation.java | 9 +++++ 6 files changed, 63 insertions(+), 24 deletions(-) delete mode 100644 .idea/runConfigurations/Debug_Robot_via_IP.xml delete mode 100644 .idea/runConfigurations/Debug_Robot_via_USB.xml create mode 100644 test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java create mode 100644 test-project/src/main/java/frc/robot/testing/performance/NoAnnotation.java create mode 100644 test-project/src/main/java/frc/robot/testing/performance/SingleAnnotation.java 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/test-project/build.gradle b/test-project/build.gradle index 29097bb..edb44cb 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,6 +87,9 @@ 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") @@ -93,6 +97,16 @@ dependencies { annotationProcessor files("../lib/build/libs/lib.jar") } +jmh { + resultFormat = 'JSON' + resultsFile = file("${buildDir}/reports/jmh/results.json") + jvmArgs = [ + "-Djava.library.path=${projectDir}/build/jni/release", '-Xms2G', '-Xmx2G' + ] as Iterable + + profilers = ['gc'] +} + tasks.register('simulate') { dependsOn('buildLib', 'simulateJava') } @@ -108,6 +122,7 @@ sourceSets { test{ useJUnitPlatform() + maxHeapSize = "4g" systemProperty 'junit.jupiter.extensions.autodetection.enabled', '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..8e609bd --- /dev/null +++ b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java @@ -0,0 +1,34 @@ +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.SECONDS) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 5) +@Fork(1) +public class EntryCreationBenchmark { + + @Benchmark + public void normalIntegerCreation(){ + new NoAnnotation(); + } + + @Benchmark + public void entryIntegerCreation(){ + new SingleAnnotation(); + } +} 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; +} From 71859a191097427f6bffe994ab56d5b760953a2a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 12 Dec 2025 18:29:32 +0000 Subject: [PATCH 03/13] Apply Spotless formatting [skip ci] --- .../java/badgerlog/processing/EntryAspect.java | 16 ++++++++-------- .../main/java/badgerlog/utilities/Members.java | 5 ++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/src/main/java/badgerlog/processing/EntryAspect.java b/lib/src/main/java/badgerlog/processing/EntryAspect.java index e13ad9d..cc424e0 100644 --- a/lib/src/main/java/badgerlog/processing/EntryAspect.java +++ b/lib/src/main/java/badgerlog/processing/EntryAspect.java @@ -44,7 +44,7 @@ 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() { } @@ -76,13 +76,13 @@ public void getterMethodExecution(Entry entry) { @After("onlyRobotCode() && staticinitialization(*)") public void createStaticEntries(JoinPoint joinPoint) { Class clazz = joinPoint.getSignature().getDeclaringType(); - if(blacklistedClasses.contains(clazz)) { + if (blacklistedClasses.contains(clazz)) { return; } - if (!Members.hasAnyOfAnnotation(clazz, Entry.class)){ + if (!Members.hasAnyOfAnnotation(clazz, Entry.class)) { blacklistedClasses.add(clazz); } - + entries.addInstance(clazz, null); Members.iterateOverAnnotatedFields(clazz, Entry.class, true, field -> createFieldEntry(field, null)); @@ -94,14 +94,14 @@ public void createStaticEntries(JoinPoint joinPoint) { public void createInstanceEntries(JoinPoint joinPoint) { Object instance = joinPoint.getThis(); Class clazz = joinPoint.getSignature().getDeclaringType(); - - if(blacklistedClasses.contains(clazz)) { + + if (blacklistedClasses.contains(clazz)) { return; } - if (!Members.hasAnyOfAnnotation(clazz, Entry.class)){ + if (!Members.hasAnyOfAnnotation(clazz, Entry.class)) { blacklistedClasses.add(clazz); } - + entries.addInstance(clazz, instance); Members.iterateOverAnnotatedFields(instance diff --git a/lib/src/main/java/badgerlog/utilities/Members.java b/lib/src/main/java/badgerlog/utilities/Members.java index 36797ff..283137c 100644 --- a/lib/src/main/java/badgerlog/utilities/Members.java +++ b/lib/src/main/java/badgerlog/utilities/Members.java @@ -49,9 +49,12 @@ public static void iterateOverAnnotatedFields(Class clazz, Class clazz, Class annotationClass) { From 193f4487ebe4b567572a8a43c387c802d512ace3 Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:24:10 -0600 Subject: [PATCH 04/13] change benchmark to microseconds --- test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java index 8e609bd..46659cc 100644 --- a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java +++ b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java @@ -16,7 +16,7 @@ @State(Scope.Thread) @BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.MICROSECONDS) @Warmup(iterations = 3, time = 1) @Measurement(iterations = 5, time = 5) @Fork(1) From 1a4aff98177d740b21a30f617f3e0cfd61c3b01c Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:34:08 -0600 Subject: [PATCH 05/13] switch over to the proper project dependency --- .idea/gradle.xml | 1 + test-project/build.gradle | 8 ++++---- test-project/settings.gradle | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) 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/test-project/build.gradle b/test-project/build.gradle index edb44cb..a9b852b 100644 --- a/test-project/build.gradle +++ b/test-project/build.gradle @@ -91,10 +91,10 @@ dependencies { 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 files("../lib/build/libs/lib.jar") - annotationProcessor files("../lib/build/libs/lib.jar") + implementation project(":badgerlog") + annotationProcessor project(":badgerlog") } jmh { @@ -117,7 +117,7 @@ tasks.register('buildLib', GradleBuild) { } sourceSets { - files("../lib/build/libs/lib-sources.jar") + project(":badgerlog") } test{ 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") From bf500ae5578cd0d27f870c9dc987105fa5f70844 Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:08:13 -0600 Subject: [PATCH 06/13] profile memory allocations and also change to use weak references (for the explicit object ones) --- .../badgerlog/processing/data/ClassData.java | 53 ++++++++++++++++--- .../badgerlog/processing/data/Entries.java | 6 +-- .../utilities/CheckedNetworkTablesMap.java | 15 +++++- test-project/build.gradle | 2 +- .../badgerlog/EntryCreationBenchmark.java | 9 +++- 5 files changed, 71 insertions(+), 14 deletions(-) diff --git a/lib/src/main/java/badgerlog/processing/data/ClassData.java b/lib/src/main/java/badgerlog/processing/data/ClassData.java index 327c989..a86d458 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,40 @@ 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..4c339b3 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,10 @@ 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); } - + classData.incrementInstanceCount(); classData.instanceEntries().putIfAbsent(instance, new InstanceData(new HashMap<>())); } @@ -69,7 +70,6 @@ public void addInstance(Class clazz, Object instance) { * Gets the field map for a particular class * * @param type the class to get the field map from - * * @return a copied map containing the entries from the {@link ClassData} field map */ public Map getFieldMap(Class type) { diff --git a/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java b/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java index 3544fcb..f07fe61 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/test-project/build.gradle b/test-project/build.gradle index a9b852b..e5fadfc 100644 --- a/test-project/build.gradle +++ b/test-project/build.gradle @@ -101,7 +101,7 @@ jmh { resultFormat = 'JSON' resultsFile = file("${buildDir}/reports/jmh/results.json") jvmArgs = [ - "-Djava.library.path=${projectDir}/build/jni/release", '-Xms2G', '-Xmx2G' + "-Djava.library.path=${projectDir}/build/jni/release", '-Xms6G', '-Xmx6G', "-XX:+FlightRecorder" ] as Iterable profilers = ['gc'] diff --git a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java index 46659cc..d783b33 100644 --- a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java +++ b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java @@ -16,9 +16,9 @@ @State(Scope.Thread) @BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.MICROSECONDS) +@OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 3, time = 1) -@Measurement(iterations = 5, time = 5) +@Measurement(iterations = 5, time = 1) @Fork(1) public class EntryCreationBenchmark { @@ -31,4 +31,9 @@ public void normalIntegerCreation(){ public void entryIntegerCreation(){ new SingleAnnotation(); } + + @Benchmark + public void defaultObjectCreation(){ + new Object(); + } } From 31dc25a6e9925f75f11d6c5fcf679edaa0d84e4a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 14 Dec 2025 03:08:55 +0000 Subject: [PATCH 07/13] Apply Spotless formatting [skip ci] --- .../java/badgerlog/processing/data/ClassData.java | 12 +++++------- .../main/java/badgerlog/processing/data/Entries.java | 1 + .../badgerlog/utilities/CheckedNetworkTablesMap.java | 8 ++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/badgerlog/processing/data/ClassData.java b/lib/src/main/java/badgerlog/processing/data/ClassData.java index a86d458..8a5e2d4 100644 --- a/lib/src/main/java/badgerlog/processing/data/ClassData.java +++ b/lib/src/main/java/badgerlog/processing/data/ClassData.java @@ -12,7 +12,7 @@ public final class ClassData { private final Map fieldMap; private final Map instanceEntries; - + @Getter private int instanceCount = 0; /** @@ -42,7 +42,7 @@ public Map fieldMap() { public Map instanceEntries() { return instanceEntries; } - + public void incrementInstanceCount() { instanceCount++; } @@ -56,8 +56,8 @@ public boolean equals(Object obj) { return false; } var that = (ClassData) obj; - return Objects.equals(this.fieldMap, that.fieldMap) && - Objects.equals(this.instanceEntries, that.instanceEntries); + return Objects.equals(this.fieldMap, that.fieldMap) && Objects + .equals(this.instanceEntries, that.instanceEntries); } @Override @@ -67,8 +67,6 @@ public int hashCode() { @Override public String toString() { - return "ClassData[" + - "fieldMap=" + fieldMap + ", " + - "instanceEntries=" + instanceEntries + ']'; + 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 4c339b3..bcec766 100644 --- a/lib/src/main/java/badgerlog/processing/data/Entries.java +++ b/lib/src/main/java/badgerlog/processing/data/Entries.java @@ -70,6 +70,7 @@ public void addInstance(Class clazz, Object instance) { * Gets the field map for a particular class * * @param type the class to get the field map from + * * @return a copied map containing the entries from the {@link ClassData} field map */ public Map getFieldMap(Class type) { diff --git a/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java b/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java index f07fe61..660d1e0 100644 --- a/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java +++ b/lib/src/main/java/badgerlog/utilities/CheckedNetworkTablesMap.java @@ -26,17 +26,17 @@ public NT put(String key, NT value) { NT oldValue = this.get(key); if (oldValue instanceof AutoCloseable closeable) { String extraInfo = ""; - - if(oldValue instanceof NTEntry entry) { + + if (oldValue instanceof NTEntry entry) { extraInfo = entry.getKey(); } - if(oldValue instanceof MockNTEntry entry) { + 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.\n" + extraInfo); } From 1ed415ee942a1746d302858e17fc201afbdfa664 Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:31:48 -0600 Subject: [PATCH 08/13] bump version --- vendordep.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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" } From 4778fb67a3dee078b65b329f22c576527fec9a9f Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:40:04 -0600 Subject: [PATCH 09/13] apply skipping to events, although it shouldn't have a significant impact --- .../java/badgerlog/processing/EventAspect.java | 18 ++++++++++++++++++ .../java/badgerlog/EntryCreationBenchmark.java | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/badgerlog/processing/EventAspect.java b/lib/src/main/java/badgerlog/processing/EventAspect.java index cde36eb..2a101d6 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/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java index d783b33..81d79fc 100644 --- a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java +++ b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java @@ -18,11 +18,11 @@ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 3, time = 1) -@Measurement(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 25) @Fork(1) public class EntryCreationBenchmark { - @Benchmark +// @Benchmark public void normalIntegerCreation(){ new NoAnnotation(); } @@ -31,8 +31,8 @@ public void normalIntegerCreation(){ public void entryIntegerCreation(){ new SingleAnnotation(); } - - @Benchmark + +// @Benchmark public void defaultObjectCreation(){ new Object(); } From c08d98bca294663b706196b87fdc8d34ed189621 Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:40:25 -0600 Subject: [PATCH 10/13] back benchmarks --- .../src/jmh/java/badgerlog/EntryCreationBenchmark.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java index 81d79fc..9338069 100644 --- a/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java +++ b/test-project/src/jmh/java/badgerlog/EntryCreationBenchmark.java @@ -22,7 +22,7 @@ @Fork(1) public class EntryCreationBenchmark { -// @Benchmark + @Benchmark public void normalIntegerCreation(){ new NoAnnotation(); } @@ -32,7 +32,7 @@ public void entryIntegerCreation(){ new SingleAnnotation(); } -// @Benchmark + @Benchmark public void defaultObjectCreation(){ new Object(); } From bfb42d1cddd9a510b7918018c2a2b0dd1dff75b2 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 14 Dec 2025 03:41:06 +0000 Subject: [PATCH 11/13] Apply Spotless formatting [skip ci] --- lib/src/main/java/badgerlog/processing/EventAspect.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/badgerlog/processing/EventAspect.java b/lib/src/main/java/badgerlog/processing/EventAspect.java index 2a101d6..937501c 100644 --- a/lib/src/main/java/badgerlog/processing/EventAspect.java +++ b/lib/src/main/java/badgerlog/processing/EventAspect.java @@ -44,7 +44,7 @@ public void createStaticEvents(JoinPoint joinPoint) { 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)); } @@ -60,7 +60,7 @@ public void createInstanceEvents(JoinPoint joinPoint) { 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)); } From d00b95cc04c6cd3bb43e4adfd2fc0cfd7a57ad56 Mon Sep 17 00:00:00 2001 From: NukeMinecart <145407522+NukeMinecart@users.noreply.github.com> Date: Sat, 13 Dec 2025 22:13:52 -0600 Subject: [PATCH 12/13] correct concurrent modification of events and fix small bug with instance ordering --- lib/src/main/java/badgerlog/events/EventRegistry.java | 9 ++++----- lib/src/main/java/badgerlog/processing/data/Entries.java | 4 +++- test-project/src/main/java/frc/robot/EventsTest.java | 9 +++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) 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/data/Entries.java b/lib/src/main/java/badgerlog/processing/data/Entries.java index bcec766..2cb2e77 100644 --- a/lib/src/main/java/badgerlog/processing/data/Entries.java +++ b/lib/src/main/java/badgerlog/processing/data/Entries.java @@ -62,7 +62,9 @@ public void addInstance(Class clazz, Object instance) { classData = new ClassData(new HashMap<>(), new WeakHashMap<>()); classDataMap.put(clazz, classData); } - classData.incrementInstanceCount(); + if(instance != null){ + classData.incrementInstanceCount(); + } classData.instanceEntries().putIfAbsent(instance, new InstanceData(new HashMap<>())); } 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"); From 5641db2bf4cc82d613077386e512ac21583a8b91 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 14 Dec 2025 04:14:40 +0000 Subject: [PATCH 13/13] Apply Spotless formatting [skip ci] --- lib/src/main/java/badgerlog/processing/data/Entries.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/badgerlog/processing/data/Entries.java b/lib/src/main/java/badgerlog/processing/data/Entries.java index 2cb2e77..6fad8e0 100644 --- a/lib/src/main/java/badgerlog/processing/data/Entries.java +++ b/lib/src/main/java/badgerlog/processing/data/Entries.java @@ -62,7 +62,7 @@ public void addInstance(Class clazz, Object instance) { classData = new ClassData(new HashMap<>(), new WeakHashMap<>()); classDataMap.put(clazz, classData); } - if(instance != null){ + if (instance != null) { classData.incrementInstanceCount(); } classData.instanceEntries().putIfAbsent(instance, new InstanceData(new HashMap<>()));