Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wpiutil] Add new Telemetry implementation #7773

Draft
wants to merge 18 commits into
base: 2027
Choose a base branch
from

Conversation

PeterJohnson
Copy link
Member

@PeterJohnson PeterJohnson commented Feb 9, 2025

Significantly simplified version of #6453. The design approach here is to make Telemetry purely imperative/immediate and write only. Read capabilities would be added as a separate Tunable implementation.

Telemetry is a utility class with only static functions to allow simple use such as Telemetry.log("name", value); (ala System.out.println()), and is intended as the primary user-facing class. Nested (structured) telemetry is available via TelemetryTable, instances of which can be gotten by calling Telemetry.getTable() (or TelemetryTable.getTable() for further nesting).

The Sendable concept equivalent is TelemetryLoggable (not a great name, but naming is hard). Unlike the current Sendable / SmartDashboard.putData(), this is designed to be immediate, not callback-based. The log(TelemetryTable) function of a TelemetryLoggable should immediately log the desired data to the provided TelemetryTable and not store the table for later use.

While we aim to fast-path most types through specific overloads, we do have a generic Object-taking overload to avoid potential overload ambiguity (particularly between StructSerializable and ProtobufSerializable, where many types implement both) and provide a fallback toString path. There's a type registry mechanism to register specific type handlers; the intent here is to use this for things like Unit types, where you might want to both set a property (indicating the unit type) and provide the value as a double.

Backends can be configured at any level; the most specific backend is used based on longest prefix match of the telemetry path (this allows for e.g. setting up NT logging at the top level, but making some tables DataLog-only). In the current implementation, backends must be configured before telemetry of matching entries starts (TelemetryTable caches entries, and this caching is not invalidated when a backend is added).

The initial backend implementation takes the type of the first log() call as the "forever" type for that particular name. Trying to later log to the same name with a different type is ignored (TBD: probably should emit a warning). Changing types dynamically both significantly increases implementation complexity and will likely result in difficult-to-debug behavior in downstream tooling; it's hard to see the user benefits to supporting this.

TODO:

  • Complete DataLog backend
  • Add NT backend
  • Add no-op backend
  • Introspection for struct/protobuf (to get .struct / .proto objects)
  • C++ implementation
  • Port current use cases of SmartDashboard/Sendable (and remove the implementations)
  • Registry should cache Entry so they are safe to use with ConcurrentMap and can be reset on reset()
  • Registry should potentially cache SendableTable objects too?
  • RobotBase set up NT as the default global backend

@github-actions github-actions bot added component: wpiutil WPI utility library component: wpilibj WPILib Java 2027 2027 target labels Feb 9, 2025
Comment on lines 99 to 100
} else if (value instanceof Boolean) {
log(name, ((Boolean) value).booleanValue());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (value instanceof Boolean) {
log(name, ((Boolean) value).booleanValue());
} else if (value instanceof Boolean b) {
log(name, b.booleanValue());

I know this is still a draft, but I figured I might as well mention that this is an option.
(See 14.30 and 15.20.2 in https://docs.oracle.com/javase/specs/jls/se17/jls17.pdf if you want to see details)

Copy link

@alan412 alan412 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any locking to make sure this works from multiple threads.

I also don't understand why there are a lot of individual functions with things like "log" naming.

*
* <p>For more advanced use cases, use the NetworkTables or DataLog APIs directly.
*/
public final class Telemetry {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make this final so teams can't subclass it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you like to subclass it? This is designed for end users to use as-is.

Copy link

@alan412 alan412 Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that everything should be subclass-able unless there is a a safety reason why not. It makes it easier for teams to extend and it is better practice for anyone providing a library.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's been discussion about this over at #3237. It goes over the position's of the maintainers far better than I could. If you still have questions, feel free to ask.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, yes, letting users subclass library classes is better, there is no reason to subclass a utility class that only provides static methods and which cannot be instantiated (and thus any subclasses would not be able to subclass it). If you really don't want to type Telemetry. in front of your method calls, you can use import static edu.wpi.first.wpilibj.Telemetry.log; or import static edu.wpi.first.wpilibj.Telemetry.*;.

Copy link
Contributor

@KangarooKoala KangarooKoala Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: Although it has the same name, the Telemetry in this PR is not analogous to the FTC Telemetry class. The FTC Telemetry class is most analogous to TelemetryTable in this PR.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThadHouse - I don't agree that having everything be static is easier to teach. Since the instance variable is part of OpMode that all opmodes must derive from, it isn't any more complicated for a student to write telemetry.log than to write Telemetry.log

I am not sure what benefit we are getting by making a static utility class.

Perhaps we should consider renaming (at the least) as making it named the same as something people coming from FTC will know but works very differently seems like it is asking for confusion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it was us writing it, that telemetry variable in the opmode would have been final, and wouldn't have allowed overwriting anyway. Overwriting that built in variable breaks a ton of encapsulation. So if the instance variable is final and not allowed to be changed, theres not much of a difference between static methods or instance methods.

Copy link
Member Author

@PeterJohnson PeterJohnson Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: naming, we need to pick names that make the most sense for the long term. We will have 1 or 2 years' worth of students who remember the "old" way and potentially be confused for a bit--but it's worse to live with a bad name for 5-10 years.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThadHouse - I don't agree that having everything be static is easier to teach. Since the instance variable is part of OpMode that all opmodes must derive from, it isn't any more complicated for a student to write telemetry.log than to write Telemetry.log

I am not sure what benefit we are getting by making a static utility class.

Perhaps we should consider renaming (at the least) as making it named the same as something people coming from FTC will know but works very differently seems like it is asking for confusion.

Hi current FRC and FTC student here, when I was learning programming inheritance was much more confusing than concepts like functions or even objects. While there may be some initial confusion coming from FTC this will last at most a few years before that lot of students graduates then this will be MUCH easier to learn and use.

*/
public final class Telemetry {
/** The root {@link TelemetryTable}. */
private static final TelemetryTable m_root = new TelemetryTable("/");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest protected instead of private so subclasses can get to it.

@Gold856
Copy link
Contributor

Gold856 commented Feb 10, 2025

I also don't understand why there are a lot of individual functions with things like "log" naming.

What do you mean by this? Are you asking why everything is called log or why there's so many overloads? log was chosen because lots of other 3rd party telemetry libraries use that name, and people seem to prefer the shorter name. If you're asking about the overloads, we have them to prevent accidental casting from, say, a double to a float, or a long to an int.

@alan412
Copy link

alan412 commented Feb 10, 2025

I also don't understand why there are a lot of individual functions with things like "log" naming.

What do you mean by this? Are you asking why everything is called log or why there's so many overloads? log was chosen because lots of other 3rd party telemetry libraries use that name, and people seem to prefer the shorter name. If you're asking about the overloads, we have them to prevent accidental casting from, say, a double to a float, or a long to an int.

I wasn't clear here. This is in reference to TelemetryBackEnd.java

@PeterJohnson
Copy link
Member Author

PeterJohnson commented Feb 10, 2025

I also don't understand why there are a lot of individual functions with things like "log" naming.

I wasn't clear here. This is in reference to TelemetryBackEnd.java

I haven't finished implementing the backends, but basically all the backends have type safety and type-specific functionality. So writing a double to a datalog file or NetworkTables is different than writing a string (or an integer). If you're asking why they are uniquely named instead of overloaded, overloading is bad practice with virtual functions (particularly in C++). A bunch of overloaded "log" functions on the user-facing side makes sense for ease of use, but is a potential footgun on the backend.

@PeterJohnson
Copy link
Member Author

PeterJohnson commented Feb 10, 2025

I don't see any locking to make sure this works from multiple threads.

It should be thread safe as written already. The backends will have locking/atomics included as needed (there are some synchronized blocks there already in the start of the DataLogSendableBackend implementation). We use ConcurrentMap etc to avoid explicit locking on the frontend (e.g. TelemetryTable caching uses ConcurrentMap), at least in Java--in C++, we'll use explicit mutexes.

@github-actions github-actions bot added the component: ntcore NetworkTables library label Feb 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2027 2027 target component: ntcore NetworkTables library component: wpilibj WPILib Java component: wpiutil WPI utility library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants