23public final class TelemetryRegistry {
47 private record TypeHandlerData<T>(Class<T> cls,
TypeHandler<T> handler) {}
51 private record RemovedEntry(TelemetryBackend backend, String path) {}
53 private static final class EntryMetadata {
54 private final Map<String, String> m_properties =
new LinkedHashMap<>();
55 private boolean m_keepDuplicates;
57 synchronized void keepDuplicates() {
58 m_keepDuplicates =
true;
61 synchronized void setProperty(String key, String value) {
62 m_properties.put(key, value);
65 void apply(TelemetryEntry entry) {
66 boolean keepDuplicates;
67 Map<String, String> properties;
69 keepDuplicates = m_keepDuplicates;
70 properties =
new LinkedHashMap<>(m_properties);
74 entry.keepDuplicates();
76 for (var property : properties.entrySet()) {
77 entry.setProperty(property.getKey(), property.getValue());
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<>();
89 private static BiConsumer<String, String> s_reportWarning =
90 TelemetryRegistry::defaultReportWarning;
92 private TelemetryRegistry() {
93 throw new UnsupportedOperationException(
"This is a utility class!");
96 private static void defaultReportWarning(String path, String msg) {
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');
105 System.err.println(
"Telemetry '" + path +
"': warning: " + msg +
"\n" + traceString.toString());
116 synchronized (TelemetryRegistry.class) {
118 s_reportWarning = TelemetryRegistry::defaultReportWarning;
120 s_reportWarning = func;
131 synchronized (TelemetryRegistry.class) {
132 return s_reportWarning;
143 synchronized (TelemetryRegistry.class) {
144 s_reportWarning.accept(path, msg);
157 synchronized (s_typeHandlers) {
160 boolean replace =
false;
162 for (var entry : s_typeHandlers) {
163 if (entry.cls.equals(cls)) {
168 if (entry.cls.isAssignableFrom(cls)) {
176 s_typeHandlers.set(i,
new TypeHandlerData<>(cls, handler));
178 s_typeHandlers.add(i,
new TypeHandlerData<>(cls, handler));
190 @SuppressWarnings(
"PMD.CompareObjectsWithEquals")
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) {
200 for (var table : s_tables.values()) {
205 if (oldBackend !=
null) {
206 displacedBackends.add(oldBackend);
209 for (var iterator = s_entryBackends.entrySet().iterator(); iterator.hasNext(); ) {
210 var entry = iterator.next();
212 if (entry.getValue() != newBackend) {
213 removedEntries.add(
new RemovedEntry(entry.getValue(), entry.getKey()));
217 registeredBackends.addAll(s_backends.values());
219 collectOwnedBackends(registeredBackend, registeredOwnedBackends);
222 removeBackendsSharingOwnershipWith(displacedBackends, registeredBackends);
225 for (var entry : removedEntries) {
226 entry.backend().removeEntry(entry.path());
229 closeBackends(displacedBackends, registeredOwnedBackends);
241 synchronized (s_typeHandlers) {
242 for (var entry : s_typeHandlers) {
243 if (entry.cls.isInstance(value)) {
244 @SuppressWarnings(
"unchecked")
263 synchronized (s_backends) {
264 backend = getBackendForNormalizedPath(normalized);
266 if (backend ==
null) {
268 return s_missingBackend;
279 @SuppressWarnings(
"PMD.CompareObjectsWithEquals")
284 boolean missingBackend =
false;
285 synchronized (s_backends) {
286 backend = getBackendForNormalizedPath(normalized);
287 if (backend ==
null) {
288 backend = s_missingBackend;
289 missingBackend =
true;
292 if (missingBackend) {
296 TelemetryEntry entry = backend.
getEntry(normalized);
297 synchronized (s_backends) {
298 TelemetryBackend currentBackend = getBackendForNormalizedPath(normalized);
299 if (currentBackend ==
null) {
300 currentBackend = s_missingBackend;
302 if (backend == currentBackend) {
303 s_entryBackends.put(normalized, backend);
312 static void keepEntryDuplicates(String path) {
314 .computeIfAbsent(PathUtil.normalizeName(path), k ->
new EntryMetadata())
318 static void setEntryProperty(String path, String key, String value) {
320 .computeIfAbsent(PathUtil.normalizeName(path), k ->
new EntryMetadata())
321 .setProperty(key, value);
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);
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()));
342 for (BackendPrefix descendant : descendants) {
343 if (!descendant.backend().getEntry(descendant.prefix()).isDiscard()) {
365 List<RemovedEntry> removedEntries =
new ArrayList<>();
366 Set<TelemetryBackend> backends = Collections.newSetFromMap(
new IdentityHashMap<>());
367 synchronized (s_typeHandlers) {
368 s_typeHandlers.clear();
370 synchronized (s_backends) {
372 for (var table : s_tables.values()) {
376 for (var entry : s_entryBackends.entrySet()) {
377 removedEntries.add(
new RemovedEntry(entry.getValue(), entry.getKey()));
379 backends.addAll(s_backends.values());
381 s_entryBackends.clear();
383 removeBackendsOwnedByOtherBackends(backends);
385 for (var entry : removedEntries) {
386 entry.backend().removeEntry(entry.path());
388 closeBackends(backends, Collections.emptySet());
389 s_entryMetadata.clear();
393 String candidate = path;
396 if (backend !=
null) {
400 int slash = candidate.lastIndexOf(
'/');
404 candidate = candidate.substring(0, slash);
407 TelemetryBackend rootBackend = s_backends.get(
"/");
408 if (rootBackend !=
null) {
411 return s_backends.get(
"");
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) {
420 closeBackend(backend, closedBackends, skipBackends);
421 }
catch (Exception e) {
422 System.out.println(
"Unexpected exception when closing backend: " + e);
427 @SuppressWarnings(
"PMD.SignatureDeclareThrowsException")
428 static
void closeBackend(
429 TelemetryBackend backend,
430 Set<TelemetryBackend> closedBackends,
431 Set<TelemetryBackend> skipBackends)
433 if (skipBackends.contains(backend) || closedBackends.contains(backend)) {
436 if (backend instanceof MultiTelemetryBackend multiBackend) {
437 multiBackend.close(closedBackends, skipBackends);
440 closedBackends.add(backend);
444 static void collectOwnedBackends(TelemetryBackend backend, Set<TelemetryBackend> ownedBackends) {
445 if (backend instanceof MultiTelemetryBackend multiBackend) {
446 multiBackend.collectOwnedBackends(ownedBackends);
448 ownedBackends.add(backend);
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) {
460 if (ownerBackend.sharesBackendWith(candidateBackend)) {
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)) {