mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
SCRIPT Move java files
This commit is contained in:
committed by
Peter Johnson
parent
7ca1be9bae
commit
c350c5f112
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Placed on a subclass of {@code ClassSpecificLogger}. Epilogue will detect it at compile time and
|
||||
* allow logging of data types compatible with the logger.
|
||||
*
|
||||
* <pre><code>
|
||||
* {@literal @}CustomLoggerFor(VendorMotorType.class)
|
||||
* class ExampleMotorLogger extends ClassSpecificLogger<VendorMotorType> { }
|
||||
* </code></pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface CustomLoggerFor {
|
||||
/**
|
||||
* The class or classes of objects able to be logged with the annotated logger.
|
||||
*
|
||||
* @return the supported data types
|
||||
*/
|
||||
Class<?>[] value();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue;
|
||||
|
||||
import edu.wpi.first.epilogue.logging.EpilogueBackend;
|
||||
import edu.wpi.first.epilogue.logging.NTEpilogueBackend;
|
||||
import edu.wpi.first.epilogue.logging.errors.ErrorHandler;
|
||||
import edu.wpi.first.epilogue.logging.errors.ErrorPrinter;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import edu.wpi.first.units.measure.Time;
|
||||
|
||||
/**
|
||||
* A configuration object to be used by the generated {@code Epilogue} class to customize its
|
||||
* behavior.
|
||||
*/
|
||||
@SuppressWarnings("checkstyle:MemberName")
|
||||
public class EpilogueConfiguration {
|
||||
/**
|
||||
* The backend implementation for Epilogue to use. By default, this will log data directly to
|
||||
* NetworkTables. NetworkTable data can be mirrored to a log file on disk by calling {@code
|
||||
* DataLogManager.start()} in your robot class constructor.
|
||||
*/
|
||||
public EpilogueBackend backend = new NTEpilogueBackend(NetworkTableInstance.getDefault());
|
||||
|
||||
/**
|
||||
* The period Epilogue will log at. By default this is the period that the robot runs at. This is
|
||||
* the field used by bind to configure speed when adding the periodic logging function
|
||||
*/
|
||||
public Time loggingPeriod;
|
||||
|
||||
/**
|
||||
* The offset from the periodic run that Epilogue will log at. By default this will be half of the
|
||||
* robots period. This is the field used by bind when adding the periodic logging function
|
||||
*/
|
||||
public Time loggingPeriodOffset;
|
||||
|
||||
/**
|
||||
* The minimum importance level of data to be logged. Defaults to debug, which logs data of all
|
||||
* importance levels. Any data tagged with an importance level lower than this will not be logged.
|
||||
*/
|
||||
public Logged.Importance minimumImportance = Logged.Importance.DEBUG;
|
||||
|
||||
/**
|
||||
* The error handler for loggers to use if they encounter an error while logging. Defaults to
|
||||
* printing an error to the standard output.
|
||||
*/
|
||||
public ErrorHandler errorHandler = new ErrorPrinter();
|
||||
|
||||
/**
|
||||
* The root identifier to use for all logged data. Defaults to "Robot", but can be changed to any
|
||||
* string.
|
||||
*/
|
||||
public String root = "Robot";
|
||||
|
||||
/** Default constructor. */
|
||||
public EpilogueConfiguration() {}
|
||||
}
|
||||
135
epilogue-runtime/src/main/java/org/wpilib/epilogue/Logged.java
Normal file
135
epilogue-runtime/src/main/java/org/wpilib/epilogue/Logged.java
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Place this annotation on a class to automatically log every field and every public accessor
|
||||
* method (methods with no arguments and return a loggable data type). Use {@link #strategy()} to
|
||||
* flag a class as logging everything it can, except for those elements tagged with
|
||||
* {@code @Logged(importance = NONE)}; or for logging only specific items also tagged with
|
||||
* {@code @Logged}.
|
||||
*
|
||||
* <p>Logged fields may have any access modifier. Logged methods must be public; non-public methods
|
||||
* will be ignored.
|
||||
*
|
||||
* <p>Epilogue can log all primitive types, arrays of primitive types (except char and short),
|
||||
* Strings, arrays of Strings, sendable objects, objects with a struct serializer, and arrays of
|
||||
* objects with struct serializers.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface Logged {
|
||||
/**
|
||||
* The name for the annotated element to be logged as. Does nothing on class-level annotations.
|
||||
* Fields and methods will default to be logged using their in-code names; use this attribute to
|
||||
* set it to something custom.
|
||||
*
|
||||
* <p>If the annotation is placed on a class, the specified name will not change logged data
|
||||
* (since that uses the names of the specific usages of the class in fields and methods); however,
|
||||
* it will be used to set the names of the generated logger that Logged will use to log instances
|
||||
* of the class. This can be used to avoid name conflicts if you have multiple classes with the
|
||||
* same name, but in different packages, and want to be able to log both.
|
||||
*
|
||||
* @return the name to use to log the field or method under; or the name of the generated
|
||||
* class-specific logger
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/** Opt-in or opt-out strategies for logging. */
|
||||
enum Strategy {
|
||||
/**
|
||||
* Log everything except for those elements explicitly opted out of with the skip = true
|
||||
* attribute. This is the default behavior.
|
||||
*/
|
||||
OPT_OUT,
|
||||
|
||||
/** Log only fields and methods tagged with an {@link Logged} annotation. */
|
||||
OPT_IN
|
||||
}
|
||||
|
||||
/**
|
||||
* The strategy to use for logging. Only has an effect on annotations on class or interface
|
||||
* declarations.
|
||||
*
|
||||
* @return the strategy to use to determine which fields and methods in the class to log
|
||||
*/
|
||||
Strategy strategy() default Strategy.OPT_OUT;
|
||||
|
||||
/**
|
||||
* Data importance. Can be used at the class level to set the default importance for all data
|
||||
* points in the class, and can be used on individual fields and methods to set a specific
|
||||
* importance level overriding the class-level default.
|
||||
*/
|
||||
enum Importance {
|
||||
/** Debug information. Useful for low-level information like raw sensor values. */
|
||||
DEBUG,
|
||||
|
||||
/**
|
||||
* Informational data. Useful for higher-level information like pose estimates or subsystem
|
||||
* state.
|
||||
*/
|
||||
INFO,
|
||||
|
||||
/** Critical data that should always be present in logs. */
|
||||
CRITICAL
|
||||
}
|
||||
|
||||
/**
|
||||
* The importance of the annotated data. If placed on a class or interface, this will be the
|
||||
* default importance of all data within that class; this can be overridden on a per-element basis
|
||||
* by annotating fields and methods with their own {@code @Logged(importance = ...)} annotation.
|
||||
*
|
||||
* @return the importance of the annotated element
|
||||
*/
|
||||
Importance importance() default Importance.DEBUG;
|
||||
|
||||
/**
|
||||
* Different behaviors for how Epilogue will generate the names of logged data points. This only
|
||||
* applies to automatically generated names; any specific name provided with {@link #name()} will
|
||||
* take precedence over an automatically generated name.
|
||||
*/
|
||||
enum Naming {
|
||||
/**
|
||||
* Sets the default naming strategy to use the name of the element as it appears in source code.
|
||||
* For example, a field {@code double m_x} would be labeled as {@code "m_x"} by default, and a
|
||||
* {@code getX()} accessor would be labeled as {@code "getX"}.
|
||||
*/
|
||||
USE_CODE_NAME,
|
||||
|
||||
/**
|
||||
* Sets the default naming strategy to use a human-readable name based on the name of the name
|
||||
* of the element as it appears in source code. For example, a field {@code double m_x} would be
|
||||
* labeled as {@code "X"} by default, and a {@code getX()} accessor would also be labeled as
|
||||
* {@code "X"}. Because logged names must be unique, this configuration would fail to compile
|
||||
* and require either one of the fields to be excluded from logs (which, for simple accessors,
|
||||
* would be ideal to avoid duplicate data), or to rename one or both elements so the logged data
|
||||
* fields would have unique names.
|
||||
*/
|
||||
USE_HUMAN_NAME
|
||||
}
|
||||
|
||||
/**
|
||||
* The default naming behavior to use. Defaults to {@link Naming#USE_CODE_NAME}, which uses the
|
||||
* raw code name directly in logs. Any configuration of the {@link #name()} attribute on logged
|
||||
* fields and methods will take precedence over an automatically generated name.
|
||||
*
|
||||
* @return the naming strategy for and annotated field or method, or the default naming strategy
|
||||
* for all logged fields and methods in an annotated class
|
||||
*/
|
||||
Naming defaultNaming() default Naming.USE_CODE_NAME;
|
||||
|
||||
/**
|
||||
* Class-level only: if {@link #strategy()} is {@link Strategy#OPT_OUT}, this can be used to quiet
|
||||
* the warnings that are printed for non-loggable fields and methods detected within the class.
|
||||
*
|
||||
* @return true if warnings should be printed, or false if warnings should not be printed
|
||||
*/
|
||||
boolean warnForNonLoggableTypes() default false;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A field or method annotated as {@code @NotLogged} will be ignored by Epilogue when determining
|
||||
* the data to log.
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NotLogged {}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.epilogue.CustomLoggerFor;
|
||||
import edu.wpi.first.epilogue.logging.errors.ErrorHandler;
|
||||
import edu.wpi.first.util.sendable.Sendable;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for class-specific generated loggers. Loggers are generated at compile time by the
|
||||
* Epilogue annotation processor and are used at runtime for zero-overhead data logging. Users may
|
||||
* also declare custom loggers, annotated with {@link CustomLoggerFor @CustomLoggerFor}, for
|
||||
* Epilogue to pull in during compile time to use for logging third party types.
|
||||
*
|
||||
* @param <T> the type of data supported by the logger
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used by generated subclasses
|
||||
public abstract class ClassSpecificLogger<T> {
|
||||
private final Class<T> m_clazz;
|
||||
// TODO: This will hold onto Sendables that are otherwise no longer referenced by a robot program.
|
||||
// Determine if that's a concern
|
||||
// Linked hashmap to maintain insert order
|
||||
private final Map<Sendable, SendableBuilder> m_sendables = new LinkedHashMap<>();
|
||||
|
||||
private boolean m_disabled = false;
|
||||
|
||||
/**
|
||||
* Instantiates the logger.
|
||||
*
|
||||
* @param clazz the Java class of objects that can be logged
|
||||
*/
|
||||
protected ClassSpecificLogger(Class<T> clazz) {
|
||||
this.m_clazz = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an object's fields in a data log.
|
||||
*
|
||||
* @param backend the backend to update
|
||||
* @param object the object to update in the log
|
||||
*/
|
||||
protected abstract void update(EpilogueBackend backend, T object);
|
||||
|
||||
/**
|
||||
* Attempts to update the data log. Will do nothing if the logger is {@link #disable() disabled}.
|
||||
*
|
||||
* @param backend the backend to log data to
|
||||
* @param object the data object to log
|
||||
* @param errorHandler the handler to use if logging raised an exception
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
public final void tryUpdate(EpilogueBackend backend, T object, ErrorHandler errorHandler) {
|
||||
if (m_disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
update(backend, object);
|
||||
} catch (Exception e) {
|
||||
errorHandler.handle(e, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this logger has been disabled.
|
||||
*
|
||||
* @return true if this logger has been disabled by {@link #disable()}, false if not
|
||||
*/
|
||||
public final boolean isDisabled() {
|
||||
return m_disabled;
|
||||
}
|
||||
|
||||
/** Disables this logger. Any log calls made while disabled will be ignored. */
|
||||
public final void disable() {
|
||||
m_disabled = true;
|
||||
}
|
||||
|
||||
/** Reenables this logger after being disabled. Has no effect if the logger is already enabled. */
|
||||
public final void reenable() {
|
||||
m_disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of the data this logger accepts.
|
||||
*
|
||||
* @return the logged data type
|
||||
*/
|
||||
public final Class<T> getLoggedType() {
|
||||
return m_clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a sendable type.
|
||||
*
|
||||
* @param backend the backend to log data into
|
||||
* @param sendable the sendable object to log
|
||||
*/
|
||||
protected void logSendable(EpilogueBackend backend, Sendable sendable) {
|
||||
if (sendable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sendables.containsKey(sendable)) {
|
||||
m_sendables.get(sendable).update();
|
||||
} else {
|
||||
var builder = new LogBackedSendableBuilder(backend);
|
||||
sendable.initSendable(builder);
|
||||
m_sendables.put(sendable, builder);
|
||||
builder.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.units.Measure;
|
||||
import edu.wpi.first.units.Unit;
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.Collection;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
/** A backend is a generic interface for Epilogue to log discrete data points. */
|
||||
public interface EpilogueBackend {
|
||||
/**
|
||||
* Creates a backend that logs to multiple backends at once. Data reads will still only occur
|
||||
* once; data is passed to all composed backends at once.
|
||||
*
|
||||
* @param backends the backends to compose together
|
||||
* @return the multi backend
|
||||
*/
|
||||
static EpilogueBackend multi(EpilogueBackend... backends) {
|
||||
return new MultiBackend(backends);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lazy version of this backend. A lazy backend will only log data to a field when its
|
||||
* value changes, which can help keep file size and bandwidth usage in check. However, there is an
|
||||
* additional CPU and memory overhead associated with tracking the current value of every logged
|
||||
* entry. The most surefire way to reduce CPU and memory usage associated with logging is to log
|
||||
* fewer things - which can be done by opting out of logging unnecessary data or increasing the
|
||||
* minimum logged importance level in the Epilogue configuration.
|
||||
*
|
||||
* @return the lazy backend
|
||||
*/
|
||||
default EpilogueBackend lazy() {
|
||||
return new LazyBackend(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a backend that can be used to log nested data underneath a specific path.
|
||||
*
|
||||
* @param path the path to use for logging nested data under
|
||||
* @return the nested backend
|
||||
*/
|
||||
EpilogueBackend getNested(String path);
|
||||
|
||||
/**
|
||||
* Logs a 32-bit integer data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, int value);
|
||||
|
||||
/**
|
||||
* Logs a 64-bit integer data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, long value);
|
||||
|
||||
/**
|
||||
* Logs a 32-bit floating point data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, float value);
|
||||
|
||||
/**
|
||||
* Logs a 64-bit floating point data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, double value);
|
||||
|
||||
/**
|
||||
* Logs a boolean data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, boolean value);
|
||||
|
||||
/**
|
||||
* Logs a raw byte array data point. <strong>NOTE:</strong> serializable data should be logged
|
||||
* using {@link #log(String, Object, Struct)}.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, byte[] value);
|
||||
|
||||
/**
|
||||
* Logs a 32-bit integer array data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, int[] value);
|
||||
|
||||
/**
|
||||
* Logs a 64-bit integer array data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, long[] value);
|
||||
|
||||
/**
|
||||
* Logs a 32-bit floating point array data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, float[] value);
|
||||
|
||||
/**
|
||||
* Logs a 64-bit floating point array data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, double[] value);
|
||||
|
||||
/**
|
||||
* Logs a boolean array data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, boolean[] value);
|
||||
|
||||
/**
|
||||
* Logs a text data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, String value);
|
||||
|
||||
/**
|
||||
* Logs a string array data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
void log(String identifier, String[] value);
|
||||
|
||||
/**
|
||||
* Logs a collection of strings data point.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
*/
|
||||
default void log(String identifier, Collection<String> value) {
|
||||
log(identifier, value.toArray(String[]::new));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a struct-serializable object.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
* @param struct the struct to use to serialize the data
|
||||
* @param <S> the serializable type
|
||||
*/
|
||||
<S> void log(String identifier, S value, Struct<S> struct);
|
||||
|
||||
/**
|
||||
* Logs an array of struct-serializable objects.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
* @param struct the struct to use to serialize the objects
|
||||
* @param <S> the serializable type
|
||||
*/
|
||||
<S> void log(String identifier, S[] value, Struct<S> struct);
|
||||
|
||||
/**
|
||||
* Logs a collection of struct-serializable objects.
|
||||
*
|
||||
* @param identifier the identifier of the data
|
||||
* @param value the collection of objects to log
|
||||
* @param struct the struct to use to serialize the objects
|
||||
* @param <S> the serializable type
|
||||
*/
|
||||
default <S> void log(String identifier, Collection<S> value, Struct<S> struct) {
|
||||
@SuppressWarnings("unchecked")
|
||||
S[] array = (S[]) value.toArray(Object[]::new);
|
||||
log(identifier, array, struct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a protobuf-serializable object.
|
||||
*
|
||||
* @param identifier the identifier of the data point
|
||||
* @param value the value of the data point
|
||||
* @param proto the protobuf to use to serialize the data
|
||||
* @param <P> the protobuf-serializable type
|
||||
* @param <M> the protobuf message type
|
||||
*/
|
||||
<P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto);
|
||||
|
||||
/**
|
||||
* Logs a measurement's value in terms of its base unit.
|
||||
*
|
||||
* @param identifier the identifier of the data field
|
||||
* @param value the new value of the data field
|
||||
*/
|
||||
default void log(String identifier, Measure<?> value) {
|
||||
log(identifier, value.baseUnitMagnitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a measurement's value in terms of another unit.
|
||||
*
|
||||
* @param identifier the identifier of the data field
|
||||
* @param value the new value of the data field
|
||||
* @param unit the unit to log the measurement in
|
||||
* @param <U> the dimension of the unit
|
||||
*/
|
||||
default <U extends Unit> void log(String identifier, Measure<U> value, U unit) {
|
||||
log(identifier, value.in(unit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an enum value. The value will appear as a string entry using the name of the enum.
|
||||
*
|
||||
* @param identifier the identifier of the data field
|
||||
* @param value the new value of the data field
|
||||
*/
|
||||
default void log(String identifier, Enum<?> value) {
|
||||
log(identifier, value.name());
|
||||
}
|
||||
|
||||
// TODO: Add default methods to support common no-struct no-sendable types like joysticks?
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
|
||||
|
||||
import edu.wpi.first.datalog.BooleanArrayLogEntry;
|
||||
import edu.wpi.first.datalog.BooleanLogEntry;
|
||||
import edu.wpi.first.datalog.DataLog;
|
||||
import edu.wpi.first.datalog.DataLogEntry;
|
||||
import edu.wpi.first.datalog.DoubleArrayLogEntry;
|
||||
import edu.wpi.first.datalog.DoubleLogEntry;
|
||||
import edu.wpi.first.datalog.FloatArrayLogEntry;
|
||||
import edu.wpi.first.datalog.FloatLogEntry;
|
||||
import edu.wpi.first.datalog.IntegerArrayLogEntry;
|
||||
import edu.wpi.first.datalog.IntegerLogEntry;
|
||||
import edu.wpi.first.datalog.ProtobufLogEntry;
|
||||
import edu.wpi.first.datalog.RawLogEntry;
|
||||
import edu.wpi.first.datalog.StringArrayLogEntry;
|
||||
import edu.wpi.first.datalog.StringLogEntry;
|
||||
import edu.wpi.first.datalog.StructArrayLogEntry;
|
||||
import edu.wpi.first.datalog.StructLogEntry;
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
/** A backend implementation that saves information to a WPILib {@link DataLog} file on disk. */
|
||||
public class FileBackend implements EpilogueBackend {
|
||||
private final DataLog m_dataLog;
|
||||
private final Map<String, DataLogEntry> m_entries = new HashMap<>();
|
||||
private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
|
||||
private final Set<Struct<?>> m_seenSchemas = new HashSet<>();
|
||||
private final Set<Protobuf<?, ?>> m_seenProtos = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Creates a new file-based backend.
|
||||
*
|
||||
* @param dataLog the data log to save data to
|
||||
*/
|
||||
public FileBackend(DataLog dataLog) {
|
||||
this.m_dataLog = requireNonNullParam(dataLog, "dataLog", "FileBackend");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend getNested(String path) {
|
||||
if (!m_subLoggers.containsKey(path)) {
|
||||
var nested = new NestedBackend(path, this);
|
||||
m_subLoggers.put(path, nested);
|
||||
return nested;
|
||||
}
|
||||
|
||||
return m_subLoggers.get(path);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <E extends DataLogEntry> E getEntry(
|
||||
String identifier, BiFunction<DataLog, String, ? extends E> ctor) {
|
||||
if (m_entries.get(identifier) != null) {
|
||||
return (E) m_entries.get(identifier);
|
||||
}
|
||||
|
||||
var entry = ctor.apply(m_dataLog, identifier);
|
||||
m_entries.put(identifier, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {
|
||||
getEntry(identifier, IntegerLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {
|
||||
getEntry(identifier, IntegerLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {
|
||||
getEntry(identifier, FloatLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {
|
||||
getEntry(identifier, DoubleLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {
|
||||
getEntry(identifier, BooleanLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {
|
||||
getEntry(identifier, RawLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PMD.UnnecessaryCastRule")
|
||||
public void log(String identifier, int[] value) {
|
||||
long[] widened = new long[value.length];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
widened[i] = (long) value[i];
|
||||
}
|
||||
getEntry(identifier, IntegerArrayLogEntry::new).append(widened);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {
|
||||
getEntry(identifier, IntegerArrayLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {
|
||||
getEntry(identifier, FloatArrayLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {
|
||||
getEntry(identifier, DoubleArrayLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {
|
||||
getEntry(identifier, BooleanArrayLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {
|
||||
getEntry(identifier, StringLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {
|
||||
getEntry(identifier, StringArrayLogEntry::new).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {
|
||||
// DataLog.addSchema has checks that we're able to skip, avoiding allocations
|
||||
if (m_seenSchemas.add(struct)) {
|
||||
m_dataLog.addSchema(struct);
|
||||
}
|
||||
|
||||
if (!m_entries.containsKey(identifier)) {
|
||||
m_entries.put(identifier, StructLogEntry.create(m_dataLog, identifier, struct));
|
||||
}
|
||||
|
||||
((StructLogEntry<S>) m_entries.get(identifier)).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {
|
||||
// DataLog.addSchema has checks that we're able to skip, avoiding allocations
|
||||
if (m_seenSchemas.add(struct)) {
|
||||
m_dataLog.addSchema(struct);
|
||||
}
|
||||
|
||||
if (!m_entries.containsKey(identifier)) {
|
||||
m_entries.put(identifier, StructArrayLogEntry.create(m_dataLog, identifier, struct));
|
||||
}
|
||||
|
||||
((StructArrayLogEntry<S>) m_entries.get(identifier)).append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) {
|
||||
// DataLog.addSchema has checks that we're able to skip, avoiding allocations
|
||||
if (m_seenProtos.add(proto)) {
|
||||
m_dataLog.addSchema(proto);
|
||||
}
|
||||
|
||||
if (!m_entries.containsKey(identifier)) {
|
||||
m_entries.put(identifier, ProtobufLogEntry.create(m_dataLog, identifier, proto));
|
||||
}
|
||||
|
||||
((ProtobufLogEntry<P>) m_entries.get(identifier)).append(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
/**
|
||||
* A backend implementation that only logs data when it changes. Useful for keeping bandwidth and
|
||||
* file sizes down. However, because it still needs to check that data has changed, it cannot avoid
|
||||
* expensive sensor reads.
|
||||
*/
|
||||
public class LazyBackend implements EpilogueBackend {
|
||||
private final EpilogueBackend m_backend;
|
||||
|
||||
// Keep a record of the most recent value written to each entry
|
||||
// Note that this may duplicate a lot of data, and will box primitives.
|
||||
private final Map<String, Object> m_previousValues = new HashMap<>();
|
||||
private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new lazy backend wrapper around another backend.
|
||||
*
|
||||
* @param backend the backend to delegate to
|
||||
*/
|
||||
public LazyBackend(EpilogueBackend backend) {
|
||||
this.m_backend = backend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend lazy() {
|
||||
// Already lazy, don't need to wrap it again
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend getNested(String path) {
|
||||
if (!m_subLoggers.containsKey(path)) {
|
||||
var nested = new NestedBackend(path, this);
|
||||
m_subLoggers.put(path, nested);
|
||||
return nested;
|
||||
}
|
||||
|
||||
return m_subLoggers.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof Integer oldValue && oldValue == value) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof Long oldValue && oldValue == value) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof Float oldValue && oldValue == value) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof Double oldValue && oldValue == value) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof Boolean oldValue && oldValue == value) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof byte[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int[] value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof int[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof long[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof float[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof double[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof boolean[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof String oldValue && oldValue.equals(value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof String[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (Objects.equals(previous, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value, struct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (previous instanceof Object[] oldValue && Arrays.equals(oldValue, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value.clone());
|
||||
m_backend.log(identifier, value, struct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) {
|
||||
var previous = m_previousValues.get(identifier);
|
||||
|
||||
if (Objects.equals(previous, value)) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_backend.log(identifier, value, proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.function.BooleanConsumer;
|
||||
import edu.wpi.first.util.function.FloatConsumer;
|
||||
import edu.wpi.first.util.function.FloatSupplier;
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.DoubleConsumer;
|
||||
import java.util.function.DoubleSupplier;
|
||||
import java.util.function.LongConsumer;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** A sendable builder implementation that sends data to a {@link EpilogueBackend}. */
|
||||
public class LogBackedSendableBuilder implements SendableBuilder {
|
||||
private final EpilogueBackend m_backend;
|
||||
private final Collection<Runnable> m_updates = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new sendable builder that delegates writes to an underlying backend.
|
||||
*
|
||||
* @param backend the backend to write the sendable data to
|
||||
*/
|
||||
public LogBackedSendableBuilder(EpilogueBackend backend) {
|
||||
this.m_backend = backend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmartDashboardType(String type) {
|
||||
m_backend.log(".type", type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActuator(boolean value) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsBoolean()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstBoolean(String key, boolean value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsLong()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstInteger(String key, long value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsFloat()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstFloat(String key, float value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsDouble()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstDouble(String key, double value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstString(String key, String value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBooleanArrayProperty(
|
||||
String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstBooleanArray(String key, boolean[] value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntegerArrayProperty(
|
||||
String key, Supplier<long[]> getter, Consumer<long[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstIntegerArray(String key, long[] value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFloatArrayProperty(
|
||||
String key, Supplier<float[]> getter, Consumer<float[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstFloatArray(String key, float[] value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDoubleArrayProperty(
|
||||
String key, Supplier<double[]> getter, Consumer<double[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstDoubleArray(String key, double[] value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStringArrayProperty(
|
||||
String key, Supplier<String[]> getter, Consumer<String[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstStringArray(String key, String[] value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRawProperty(
|
||||
String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstRaw(String key, String typeString, byte[] value) {
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackendKind getBackendKind() {
|
||||
return BackendKind.kUnknown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublished() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
for (Runnable update : m_updates) {
|
||||
update.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearProperties() {
|
||||
m_updates.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCloseable(AutoCloseable closeable) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
clearProperties();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
/**
|
||||
* A backend implementation that delegates to other backends. Helpful for simultaneous logging to
|
||||
* multiple data stores at once.
|
||||
*/
|
||||
public class MultiBackend implements EpilogueBackend {
|
||||
private final List<EpilogueBackend> m_backends;
|
||||
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
|
||||
|
||||
// Use EpilogueBackend.multi(...) instead of instantiation directly
|
||||
MultiBackend(EpilogueBackend... backends) {
|
||||
this.m_backends = List.of(backends);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend getNested(String path) {
|
||||
if (!m_nestedBackends.containsKey(path)) {
|
||||
var nested = new NestedBackend(path, this);
|
||||
m_nestedBackends.put(path, nested);
|
||||
return nested;
|
||||
}
|
||||
|
||||
return m_nestedBackends.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value, struct);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value, struct);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value, proto);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.networktables.BooleanArrayPublisher;
|
||||
import edu.wpi.first.networktables.BooleanPublisher;
|
||||
import edu.wpi.first.networktables.DoubleArrayPublisher;
|
||||
import edu.wpi.first.networktables.DoublePublisher;
|
||||
import edu.wpi.first.networktables.FloatArrayPublisher;
|
||||
import edu.wpi.first.networktables.FloatPublisher;
|
||||
import edu.wpi.first.networktables.IntegerArrayPublisher;
|
||||
import edu.wpi.first.networktables.IntegerPublisher;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import edu.wpi.first.networktables.ProtobufPublisher;
|
||||
import edu.wpi.first.networktables.Publisher;
|
||||
import edu.wpi.first.networktables.RawPublisher;
|
||||
import edu.wpi.first.networktables.StringArrayPublisher;
|
||||
import edu.wpi.first.networktables.StringPublisher;
|
||||
import edu.wpi.first.networktables.StructArrayPublisher;
|
||||
import edu.wpi.first.networktables.StructPublisher;
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
/**
|
||||
* A backend implementation that sends data over network tables. Be careful when using this, since
|
||||
* sending too much data may cause bandwidth or CPU starvation.
|
||||
*/
|
||||
public class NTEpilogueBackend implements EpilogueBackend {
|
||||
private final NetworkTableInstance m_nt;
|
||||
|
||||
private final Map<String, Publisher> m_publishers = new HashMap<>();
|
||||
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
|
||||
private final Set<Struct<?>> m_seenSchemas = new HashSet<>();
|
||||
private final Set<Protobuf<?, ?>> m_seenProtos = new HashSet<>();
|
||||
private final Function<String, IntegerPublisher> m_createIntPublisher;
|
||||
private final Function<String, FloatPublisher> m_createFloatPublisher;
|
||||
private final Function<String, DoublePublisher> m_createDoublePublisher;
|
||||
private final Function<String, BooleanPublisher> m_createBooleanPublisher;
|
||||
private final Function<String, RawPublisher> m_createRawPublisher;
|
||||
private final Function<String, IntegerArrayPublisher> m_createIntegerArrayPublisher;
|
||||
private final Function<String, FloatArrayPublisher> m_createFloatArrayPublisher;
|
||||
private final Function<String, DoubleArrayPublisher> m_createDoubleArrayPublisher;
|
||||
private final Function<String, BooleanArrayPublisher> m_createBooleanArrayPublisher;
|
||||
private final Function<String, StringPublisher> m_createStringPublisher;
|
||||
private final Function<String, StringArrayPublisher> m_createStringArrayPublisher;
|
||||
|
||||
/**
|
||||
* Creates a logging backend that sends information to NetworkTables.
|
||||
*
|
||||
* @param nt the NetworkTable instance to use to send data to
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public NTEpilogueBackend(NetworkTableInstance nt) {
|
||||
this.m_nt = nt;
|
||||
m_createIntPublisher = identifier -> m_nt.getIntegerTopic(identifier).publish();
|
||||
m_createFloatPublisher = identifier -> m_nt.getFloatTopic(identifier).publish();
|
||||
m_createDoublePublisher = identifier -> m_nt.getDoubleTopic(identifier).publish();
|
||||
m_createBooleanPublisher = identifier -> m_nt.getBooleanTopic(identifier).publish();
|
||||
m_createRawPublisher = identifier -> m_nt.getRawTopic(identifier).publish("raw");
|
||||
m_createIntegerArrayPublisher = identifier -> m_nt.getIntegerArrayTopic(identifier).publish();
|
||||
m_createFloatArrayPublisher = identifier -> m_nt.getFloatArrayTopic(identifier).publish();
|
||||
m_createDoubleArrayPublisher = identifier -> m_nt.getDoubleArrayTopic(identifier).publish();
|
||||
m_createBooleanArrayPublisher = identifier -> m_nt.getBooleanArrayTopic(identifier).publish();
|
||||
m_createStringPublisher = identifier -> m_nt.getStringTopic(identifier).publish();
|
||||
m_createStringArrayPublisher = identifier -> m_nt.getStringArrayTopic(identifier).publish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend getNested(String path) {
|
||||
if (!m_nestedBackends.containsKey(path)) {
|
||||
var nested = new NestedBackend(path, this);
|
||||
m_nestedBackends.put(path, nested);
|
||||
return nested;
|
||||
}
|
||||
|
||||
return m_nestedBackends.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {
|
||||
((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {
|
||||
((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {
|
||||
((FloatPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatPublisher)).set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {
|
||||
((DoublePublisher) m_publishers.computeIfAbsent(identifier, m_createDoublePublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {
|
||||
((BooleanPublisher) m_publishers.computeIfAbsent(identifier, m_createBooleanPublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {
|
||||
((RawPublisher) m_publishers.computeIfAbsent(identifier, m_createRawPublisher)).set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PMD.UnnecessaryCastRule")
|
||||
public void log(String identifier, int[] value) {
|
||||
// NT backend only supports int64[], so we have to manually widen to 64 bits before sending
|
||||
long[] widened = new long[value.length];
|
||||
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
widened[i] = (long) value[i];
|
||||
}
|
||||
|
||||
((IntegerArrayPublisher)
|
||||
m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher))
|
||||
.set(widened);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {
|
||||
((IntegerArrayPublisher)
|
||||
m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {
|
||||
((FloatArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatArrayPublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {
|
||||
((DoubleArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createDoubleArrayPublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {
|
||||
((BooleanArrayPublisher)
|
||||
m_publishers.computeIfAbsent(identifier, m_createBooleanArrayPublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {
|
||||
((StringPublisher) m_publishers.computeIfAbsent(identifier, m_createStringPublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {
|
||||
((StringArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createStringArrayPublisher))
|
||||
.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {
|
||||
// NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
|
||||
if (m_seenSchemas.add(struct)) {
|
||||
m_nt.addSchema(struct);
|
||||
}
|
||||
|
||||
if (m_publishers.containsKey(identifier)) {
|
||||
((StructPublisher<S>) m_publishers.get(identifier)).set(value);
|
||||
} else {
|
||||
StructPublisher<S> publisher = m_nt.getStructTopic(identifier, struct).publish();
|
||||
m_publishers.put(identifier, publisher);
|
||||
publisher.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {
|
||||
// NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
|
||||
if (m_seenSchemas.add(struct)) {
|
||||
m_nt.addSchema(struct);
|
||||
}
|
||||
|
||||
if (m_publishers.containsKey(identifier)) {
|
||||
((StructArrayPublisher<S>) m_publishers.get(identifier)).set(value);
|
||||
} else {
|
||||
StructArrayPublisher<S> publisher = m_nt.getStructArrayTopic(identifier, struct).publish();
|
||||
m_publishers.put(identifier, publisher);
|
||||
publisher.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) {
|
||||
// NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
|
||||
if (m_seenProtos.add(proto)) {
|
||||
m_nt.addSchema(proto);
|
||||
}
|
||||
|
||||
if (m_publishers.containsKey(identifier)) {
|
||||
((ProtobufPublisher<P>) m_publishers.get(identifier)).set(value);
|
||||
} else {
|
||||
ProtobufPublisher<P> publisher = m_nt.getProtobufTopic(identifier, proto).publish();
|
||||
m_publishers.put(identifier, publisher);
|
||||
publisher.set(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
/**
|
||||
* A backend that logs to an underlying backend, prepending all logged data with a specific prefix.
|
||||
* Useful for logging nested data structures.
|
||||
*/
|
||||
public class NestedBackend implements EpilogueBackend {
|
||||
private final String m_prefix;
|
||||
private final EpilogueBackend m_impl;
|
||||
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
|
||||
|
||||
// String concatenation can be expensive, especially for deeply nested hierarchies with many
|
||||
// logged fields. For example, logging a hypothetical Robot.elevator.io.getHeight() would result
|
||||
// in "/Robot/" + "elevator/" + "io/" + "getHeight"; three concatenations and string and byte
|
||||
// array allocations that need to be cleaned up by the GC. Caching the results means those
|
||||
// allocations only occur once, resulting in no GC (the strings are always referenced in the
|
||||
// cache), and minimal time costs (the String object caches its own hash code, so all we do is an
|
||||
// O(1) table lookup per concatenation)
|
||||
private final Map<String, String> m_prefixedIdentifiers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new nested backed underneath another backend.
|
||||
*
|
||||
* @param prefix the prefix to append to all data logged in the nested backend
|
||||
* @param impl the backend to log to
|
||||
*/
|
||||
public NestedBackend(String prefix, EpilogueBackend impl) {
|
||||
// Add a trailing slash if not already present
|
||||
if (prefix.endsWith("/")) {
|
||||
this.m_prefix = prefix;
|
||||
} else {
|
||||
this.m_prefix = prefix + "/";
|
||||
}
|
||||
this.m_impl = impl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fast lookup to avoid redundant `m_prefix + identifier` concatenations. If the identifier has
|
||||
* not been seen before, we compute the concatenation and cache the result for later invocations
|
||||
* to read. This avoids redundantly recomputing the same concatenations every loop and
|
||||
* significantly cuts down on the CPU and memory overhead of the Epilogue library.
|
||||
*
|
||||
* @param identifier The identifier to prepend with {@link #m_prefix}.
|
||||
* @return The concatenated string.
|
||||
*/
|
||||
private String withPrefix(String identifier) {
|
||||
// Using computeIfAbsent would result in a new lambda object allocation on every call
|
||||
if (m_prefixedIdentifiers.containsKey(identifier)) {
|
||||
return m_prefixedIdentifiers.get(identifier);
|
||||
}
|
||||
|
||||
String result = m_prefix + identifier;
|
||||
m_prefixedIdentifiers.put(identifier, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend getNested(String path) {
|
||||
if (!m_nestedBackends.containsKey(path)) {
|
||||
var nested = new NestedBackend(path, this);
|
||||
m_nestedBackends.put(path, nested);
|
||||
return nested;
|
||||
}
|
||||
|
||||
return m_nestedBackends.get(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int[] value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {
|
||||
m_impl.log(withPrefix(identifier), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {
|
||||
m_impl.log(withPrefix(identifier), value, struct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {
|
||||
m_impl.log(withPrefix(identifier), value, struct);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) {
|
||||
m_impl.log(m_prefix + identifier, value, proto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.protobuf.Protobuf;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import us.hebi.quickbuf.ProtoMessage;
|
||||
|
||||
/** Null backend implementation that logs nothing. */
|
||||
public class NullBackend implements EpilogueBackend {
|
||||
/** Default constructor. */
|
||||
public NullBackend() {}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend getNested(String path) {
|
||||
// Since a nested backend would still log nothing and has no state, we can just return the same
|
||||
// null-logging implementation
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int[] value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {}
|
||||
|
||||
@Override
|
||||
public <P, M extends ProtoMessage<M>> void log(
|
||||
String identifier, P value, Protobuf<P, M> proto) {}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging.errors;
|
||||
|
||||
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
|
||||
|
||||
/**
|
||||
* An error handler implementation that will throw an exception if logging raised an exception. This
|
||||
* is useful when running code in simulation or in JUnit tests to quickly identify errors in your
|
||||
* code.
|
||||
*/
|
||||
public class CrashOnError implements ErrorHandler {
|
||||
/** Default constructor. */
|
||||
public CrashOnError() {}
|
||||
|
||||
@Override
|
||||
public void handle(Throwable exception, ClassSpecificLogger<?> logger) {
|
||||
throw new RuntimeException(
|
||||
"[EPILOGUE] An error occurred while logging an instance of "
|
||||
+ logger.getLoggedType().getName(),
|
||||
exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging.errors;
|
||||
|
||||
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
|
||||
|
||||
/**
|
||||
* An error handler is used by the Logged framework to catch and process any errors that occur
|
||||
* during the logging process. Different handlers can be used in different operating modes, such as
|
||||
* crashing in simulation to identify errors before they make it to a robot, or automatically
|
||||
* disabling loggers if they encounter too many errors on the field to let the robot keep running
|
||||
* while playing a match.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ErrorHandler {
|
||||
/**
|
||||
* Handles an exception that arose while logging.
|
||||
*
|
||||
* @param exception the exception that occurred
|
||||
* @param logger the logger that was being processed that caused the error to occur
|
||||
*/
|
||||
void handle(Throwable exception, ClassSpecificLogger<?> logger);
|
||||
|
||||
/**
|
||||
* Creates an error handler that will immediately re-throw an exception and cause robot code to
|
||||
* exit. This is particularly useful when running in simulation or JUnit tests to identify errors
|
||||
* quickly and safely.
|
||||
*
|
||||
* @return the error handler
|
||||
*/
|
||||
static ErrorHandler crashOnError() {
|
||||
return new CrashOnError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error handler that will print error messages to the console output, but otherwise
|
||||
* allow logging to continue in the future. This can be helpful when errors occur only rarely and
|
||||
* you don't want your robot program to crash or disable future logging.
|
||||
*
|
||||
* @return the error handler
|
||||
*/
|
||||
static ErrorHandler printErrorMessages() {
|
||||
return new ErrorPrinter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an error handler that will automatically disable a logger if it encounters too many
|
||||
* errors. Only the error-prone logger(s) will be disabled; loggers that have not encountered any
|
||||
* errors, or encountered fewer than the limit, will continue to be used. Disabled loggers can be
|
||||
* reset by calling {@link LoggerDisabler#reset()} on the handler.
|
||||
*
|
||||
* @param maximumPermissibleErrors the maximum number of errors that a logger is permitted to
|
||||
* encounter before being disabled.
|
||||
* @return the error handler
|
||||
*/
|
||||
static LoggerDisabler disabling(int maximumPermissibleErrors) {
|
||||
return LoggerDisabler.forLimit(maximumPermissibleErrors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging.errors;
|
||||
|
||||
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
|
||||
|
||||
/** An error handler implementation that prints error information to the console. */
|
||||
public class ErrorPrinter implements ErrorHandler {
|
||||
/** Default constructor. */
|
||||
public ErrorPrinter() {}
|
||||
|
||||
@Override
|
||||
public void handle(Throwable exception, ClassSpecificLogger<?> logger) {
|
||||
System.err.println(
|
||||
"[EPILOGUE] An error occurred while logging an instance of "
|
||||
+ logger.getLoggedType().getName()
|
||||
+ ": "
|
||||
+ exception.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.epilogue.logging.errors;
|
||||
|
||||
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An error handler that disables loggers after too many exceptions are raised. Useful when playing
|
||||
* in matches, where data logging is less important than reliable control. Setting the threshold to
|
||||
* ≤0 will cause any logger that encounters an exception whilst logging to immediately be disabled.
|
||||
* Setting to higher values means your program is more tolerant of errors, but takes longer to
|
||||
* disable, and therefore may have more sets of partial or incomplete data and may have more CPU
|
||||
* overhead due to the cost of throwing exceptions.
|
||||
*/
|
||||
public class LoggerDisabler implements ErrorHandler {
|
||||
private final int m_threshold;
|
||||
private final Map<ClassSpecificLogger<?>, Integer> m_errorCounts = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new logger-disabling error handler.
|
||||
*
|
||||
* @param threshold how many errors any one logger is allowed to encounter before it is disabled.
|
||||
*/
|
||||
public LoggerDisabler(int threshold) {
|
||||
this.m_threshold = threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a disabler that kicks in after a logger raises more than {@code threshold} exceptions.
|
||||
*
|
||||
* @param threshold the threshold value for the maximum number of exceptions loggers are permitted
|
||||
* to encounter before they are disabled
|
||||
* @return the disabler
|
||||
*/
|
||||
public static LoggerDisabler forLimit(int threshold) {
|
||||
return new LoggerDisabler(threshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Throwable exception, ClassSpecificLogger<?> logger) {
|
||||
var errorCount = m_errorCounts.getOrDefault(logger, 0) + 1;
|
||||
m_errorCounts.put(logger, errorCount);
|
||||
|
||||
if (errorCount > m_threshold) {
|
||||
logger.disable();
|
||||
System.err.println(
|
||||
"[EPILOGUE] Too many errors detected in "
|
||||
+ logger.getClass().getName()
|
||||
+ " (maximum allowed: "
|
||||
+ m_threshold
|
||||
+ "). The most recent error follows:");
|
||||
System.err.println(exception.getMessage());
|
||||
exception.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets all error counts and reenables all loggers. */
|
||||
public void reset() {
|
||||
for (var logger : m_errorCounts.keySet()) {
|
||||
// Safe. This is a no-op on loggers that are already enabled
|
||||
logger.reenable();
|
||||
}
|
||||
m_errorCounts.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user