RobotCore 1.0
WPILib port to raspberry Pi
Loading...
Searching...
No Matches
TelemetryRegistry.java
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5package org.wpilib.telemetry;
6
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.IdentityHashMap;
11import java.util.LinkedHashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Set;
15import java.util.concurrent.ConcurrentHashMap;
16import java.util.concurrent.ConcurrentMap;
17import java.util.function.BiConsumer;
18import org.wpilib.telemetry.util.PathUtil;
19import org.wpilib.util.collections.PrefixMap;
20import org.wpilib.util.collections.prefixmap.StringPrefixMap;
21
23public final class TelemetryRegistry {
29 @FunctionalInterface
30 public interface TypeHandler<T> {
38 void logTo(TelemetryTable table, String name, T value);
39 }
40
47 private record TypeHandlerData<T>(Class<T> cls, TypeHandler<T> handler) {}
48
49 private record BackendPrefix(String prefix, TelemetryBackend backend) {}
50
51 private record RemovedEntry(TelemetryBackend backend, String path) {}
52
53 private static final class EntryMetadata {
54 private final Map<String, String> m_properties = new LinkedHashMap<>();
55 private boolean m_keepDuplicates;
56
57 synchronized void keepDuplicates() {
58 m_keepDuplicates = true;
59 }
60
61 synchronized void setProperty(String key, String value) {
62 m_properties.put(key, value);
63 }
64
65 void apply(TelemetryEntry entry) {
66 boolean keepDuplicates;
67 Map<String, String> properties;
68 synchronized (this) {
69 keepDuplicates = m_keepDuplicates;
70 properties = new LinkedHashMap<>(m_properties);
71 }
72
73 if (keepDuplicates) {
74 entry.keepDuplicates();
75 }
76 for (var property : properties.entrySet()) {
77 entry.setProperty(property.getKey(), property.getValue());
78 }
79 }
80 }
81
82 private static final List<TypeHandlerData<?>> s_typeHandlers = new ArrayList<>();
83 private static final PrefixMap<TelemetryBackend> s_backends = new StringPrefixMap<>();
84 private static final Map<String, TelemetryBackend> s_entryBackends = new LinkedHashMap<>();
85 private static final ConcurrentMap<String, TelemetryTable> s_tables = new ConcurrentHashMap<>();
86 private static final ConcurrentMap<String, EntryMetadata> s_entryMetadata =
87 new ConcurrentHashMap<>();
88 private static final TelemetryBackend s_missingBackend = new DiscardTelemetryBackend();
89 private static BiConsumer<String, String> s_reportWarning =
90 TelemetryRegistry::defaultReportWarning;
91
92 private TelemetryRegistry() {
93 throw new UnsupportedOperationException("This is a utility class!");
94 }
95
96 private static void defaultReportWarning(String path, String msg) {
97 // TODO: do something smarter here
98 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
99 StringBuilder traceString = new StringBuilder();
100 for (int i = 1; i < stackTrace.length; i++) {
101 String loc = stackTrace[i].toString();
102 traceString.append("\tat ").append(loc).append('\n');
103 }
104
105 System.err.println("Telemetry '" + path + "': warning: " + msg + "\n" + traceString.toString());
106 }
107
115 public static void setReportWarning(BiConsumer<String, String> func) {
116 synchronized (TelemetryRegistry.class) {
117 if (func == null) {
118 s_reportWarning = TelemetryRegistry::defaultReportWarning;
119 } else {
120 s_reportWarning = func;
121 }
122 }
123 }
124
130 public static BiConsumer<String, String> getReportWarning() {
131 synchronized (TelemetryRegistry.class) {
132 return s_reportWarning;
133 }
134 }
135
142 public static void reportWarning(String path, String msg) {
143 synchronized (TelemetryRegistry.class) {
144 s_reportWarning.accept(path, msg);
145 }
146 }
147
156 public static <T> void registerTypeHandler(Class<T> cls, TypeHandler<T> handler) {
157 synchronized (s_typeHandlers) {
158 // we want this ordered such that the more specific types come before the less specific ones
159 // this is O(N^2) but N should be small
160 boolean replace = false;
161 int i = 0;
162 for (var entry : s_typeHandlers) {
163 if (entry.cls.equals(cls)) {
164 // replace existing
165 replace = true;
166 break;
167 }
168 if (entry.cls.isAssignableFrom(cls)) {
169 // superclass; insert before
170 break;
171 }
172 i++;
173 }
174
175 if (replace) {
176 s_typeHandlers.set(i, new TypeHandlerData<>(cls, handler));
177 } else {
178 s_typeHandlers.add(i, new TypeHandlerData<>(cls, handler));
179 }
180 }
181 }
182
190 @SuppressWarnings("PMD.CompareObjectsWithEquals")
191 public static void registerBackend(String prefix, TelemetryBackend backend) {
192 String normalizedPrefix = PathUtil.normalizeBackendPrefix(prefix);
193 List<RemovedEntry> removedEntries = new ArrayList<>();
194 List<TelemetryBackend> registeredBackends = new ArrayList<>();
195 Set<TelemetryBackend> registeredOwnedBackends =
196 Collections.newSetFromMap(new IdentityHashMap<>());
197 Set<TelemetryBackend> displacedBackends = Collections.newSetFromMap(new IdentityHashMap<>());
198 synchronized (s_backends) {
199 // Reset table generations before backend routing changes become visible.
200 for (var table : s_tables.values()) {
201 table.reset();
202 }
203
204 TelemetryBackend oldBackend = s_backends.put(normalizedPrefix, backend);
205 if (oldBackend != null) {
206 displacedBackends.add(oldBackend);
207 }
208
209 for (var iterator = s_entryBackends.entrySet().iterator(); iterator.hasNext(); ) {
210 var entry = iterator.next();
211 TelemetryBackend newBackend = getBackendForNormalizedPath(entry.getKey());
212 if (entry.getValue() != newBackend) {
213 removedEntries.add(new RemovedEntry(entry.getValue(), entry.getKey()));
214 iterator.remove();
215 }
216 }
217 registeredBackends.addAll(s_backends.values());
218 for (TelemetryBackend registeredBackend : registeredBackends) {
219 collectOwnedBackends(registeredBackend, registeredOwnedBackends);
220 }
221 }
222 removeBackendsSharingOwnershipWith(displacedBackends, registeredBackends);
223
224 try {
225 for (var entry : removedEntries) {
226 entry.backend().removeEntry(entry.path());
227 }
228 } finally {
229 closeBackends(displacedBackends, registeredOwnedBackends);
230 }
231 }
232
240 public static <T> TypeHandler<T> getTypeHandler(T value) {
241 synchronized (s_typeHandlers) {
242 for (var entry : s_typeHandlers) {
243 if (entry.cls.isInstance(value)) {
244 @SuppressWarnings("unchecked")
245 TypeHandler<T> handler = (TypeHandler<T>) entry.handler;
246 return handler;
247 }
248 }
249 return null;
250 }
251 }
252
260 public static TelemetryBackend getBackend(String path) {
261 String normalized = PathUtil.normalizeName(path);
262 TelemetryBackend backend;
263 synchronized (s_backends) {
264 backend = getBackendForNormalizedPath(normalized);
265 }
266 if (backend == null) {
267 reportWarning(normalized, "no backend for path");
268 return s_missingBackend;
269 }
270 return backend;
271 }
272
279 @SuppressWarnings("PMD.CompareObjectsWithEquals")
280 static TelemetryEntry getEntry(String path) {
281 String normalized = PathUtil.normalizeName(path);
282 for (; ; ) {
283 TelemetryBackend backend;
284 boolean missingBackend = false;
285 synchronized (s_backends) {
286 backend = getBackendForNormalizedPath(normalized);
287 if (backend == null) {
288 backend = s_missingBackend;
289 missingBackend = true;
290 }
291 }
292 if (missingBackend) {
293 reportWarning(normalized, "no backend for path");
294 }
295
296 TelemetryEntry entry = backend.getEntry(normalized);
297 synchronized (s_backends) {
298 TelemetryBackend currentBackend = getBackendForNormalizedPath(normalized);
299 if (currentBackend == null) {
300 currentBackend = s_missingBackend;
301 }
302 if (backend == currentBackend) {
303 s_entryBackends.put(normalized, backend);
304 return entry;
305 }
306 }
307
308 backend.removeEntry(normalized);
309 }
310 }
311
312 static void keepEntryDuplicates(String path) {
313 s_entryMetadata
314 .computeIfAbsent(PathUtil.normalizeName(path), k -> new EntryMetadata())
315 .keepDuplicates();
316 }
317
318 static void setEntryProperty(String path, String key, String value) {
319 s_entryMetadata
320 .computeIfAbsent(PathUtil.normalizeName(path), k -> new EntryMetadata())
321 .setProperty(key, value);
322 }
323
324 static void applyEntryMetadata(String path, TelemetryEntry entry) {
325 EntryMetadata metadata = s_entryMetadata.get(PathUtil.normalizeName(path));
326 if (metadata != null) {
327 metadata.apply(entry);
328 }
329 }
330
331 static boolean hasNonDiscardDescendant(String tablePath) {
332 String normalized = PathUtil.normalizeTableName(tablePath);
333 List<BackendPrefix> descendants = new ArrayList<>();
334 synchronized (s_backends) {
335 for (Map.Entry<String, TelemetryBackend> entry : s_backends.entrySet()) {
336 if (PathUtil.isPathOrDescendant(entry.getKey(), normalized)) {
337 descendants.add(new BackendPrefix(entry.getKey(), entry.getValue()));
338 }
339 }
340 }
341
342 for (BackendPrefix descendant : descendants) {
343 if (!descendant.backend().getEntry(descendant.prefix()).isDiscard()) {
344 return true;
345 }
346 }
347 return false;
348 }
349
356 public static TelemetryTable getTable(String path) {
357 return s_tables.computeIfAbsent(PathUtil.normalizeTableName(path), TelemetryTable::new);
358 }
359
364 public static void reset() {
365 List<RemovedEntry> removedEntries = new ArrayList<>();
366 Set<TelemetryBackend> backends = Collections.newSetFromMap(new IdentityHashMap<>());
367 synchronized (s_typeHandlers) {
368 s_typeHandlers.clear();
369 }
370 synchronized (s_backends) {
371 // Reset table generations before backend routing changes become visible.
372 for (var table : s_tables.values()) {
373 table.reset();
374 }
375
376 for (var entry : s_entryBackends.entrySet()) {
377 removedEntries.add(new RemovedEntry(entry.getValue(), entry.getKey()));
378 }
379 backends.addAll(s_backends.values());
380 s_backends.clear();
381 s_entryBackends.clear();
382 }
383 removeBackendsOwnedByOtherBackends(backends);
384
385 for (var entry : removedEntries) {
386 entry.backend().removeEntry(entry.path());
387 }
388 closeBackends(backends, Collections.emptySet());
389 s_entryMetadata.clear();
390 }
391
392 private static TelemetryBackend getBackendForNormalizedPath(String path) {
393 String candidate = path;
394 while (true) {
395 TelemetryBackend backend = s_backends.get(candidate);
396 if (backend != null) {
397 return backend;
398 }
399
400 int slash = candidate.lastIndexOf('/');
401 if (slash <= 0) {
402 break;
403 }
404 candidate = candidate.substring(0, slash);
405 }
406
407 TelemetryBackend rootBackend = s_backends.get("/");
408 if (rootBackend != null) {
409 return rootBackend;
410 }
411 return s_backends.get("");
412 }
413
414 @SuppressWarnings("PMD.AvoidCatchingGenericException")
415 private static void closeBackends(
416 Collection<TelemetryBackend> backends, Set<TelemetryBackend> skipBackends) {
417 Set<TelemetryBackend> closedBackends = Collections.newSetFromMap(new IdentityHashMap<>());
418 for (TelemetryBackend backend : backends) {
419 try {
420 closeBackend(backend, closedBackends, skipBackends);
421 } catch (Exception e) {
422 System.out.println("Unexpected exception when closing backend: " + e);
423 }
424 }
425 }
426
427 @SuppressWarnings("PMD.SignatureDeclareThrowsException")
428 static void closeBackend(
429 TelemetryBackend backend,
430 Set<TelemetryBackend> closedBackends,
431 Set<TelemetryBackend> skipBackends)
432 throws Exception {
433 if (skipBackends.contains(backend) || closedBackends.contains(backend)) {
434 return;
435 }
436 if (backend instanceof MultiTelemetryBackend multiBackend) {
437 multiBackend.close(closedBackends, skipBackends);
438 return;
439 }
440 closedBackends.add(backend);
441 backend.close();
442 }
443
444 static void collectOwnedBackends(TelemetryBackend backend, Set<TelemetryBackend> ownedBackends) {
445 if (backend instanceof MultiTelemetryBackend multiBackend) {
446 multiBackend.collectOwnedBackends(ownedBackends);
447 } else {
448 ownedBackends.add(backend);
449 }
450 }
451
452 private static void removeBackendsSharingOwnershipWith(
453 Set<TelemetryBackend> candidateBackends, Iterable<TelemetryBackend> ownerBackends) {
454 for (TelemetryBackend ownerBackend : ownerBackends) {
455 for (var iterator = candidateBackends.iterator(); iterator.hasNext(); ) {
456 TelemetryBackend candidateBackend = iterator.next();
457 if (candidateBackend instanceof MultiTelemetryBackend) {
458 continue;
459 }
460 if (ownerBackend.sharesBackendWith(candidateBackend)) {
461 iterator.remove();
462 }
463 }
464 }
465 }
466
467 @SuppressWarnings("PMD.CompareObjectsWithEquals")
468 private static void removeBackendsOwnedByOtherBackends(Set<TelemetryBackend> candidateBackends) {
469 List<TelemetryBackend> ownerBackends = new ArrayList<>(candidateBackends);
470 for (TelemetryBackend ownerBackend : ownerBackends) {
471 for (var iterator = candidateBackends.iterator(); iterator.hasNext(); ) {
472 TelemetryBackend candidateBackend = iterator.next();
473 if (ownerBackend != candidateBackend && ownerBackend.ownsBackend(candidateBackend)) {
474 iterator.remove();
475 }
476 }
477 }
478 }
479}
static void reportWarning(String path, String msg)
static void registerBackend(String prefix, TelemetryBackend backend)
static TelemetryBackend getBackend(String path)
static< T > TypeHandler< T > getTypeHandler(T value)
static< T > void registerTypeHandler(Class< T > cls, TypeHandler< T > handler)
static void setReportWarning(BiConsumer< String, String > func)
static BiConsumer< String, String > getReportWarning()
static TelemetryTable getTable(String path)
static String normalizeBackendPrefix(String prefix)
Definition PathUtil.java:36
static String normalizeTableName(String path)
Definition PathUtil.java:20
static String normalizeName(String path)
Definition PathUtil.java:53
TelemetryEntry getEntry(String path)
void logTo(TelemetryTable table, String name, T value)