24public final class TelemetryTable {
25 private record StaticFieldLookup(Object value, String warning) {}
27 private record CachedEntry(
TelemetryEntry entry,
long resetGeneration) {}
34 private static final ClassValue<StaticFieldLookup> s_structLookupCache =
37 protected StaticFieldLookup computeValue(Class<?> type) {
38 return getStaticField(type,
"struct");
42 private static final ClassValue<StaticFieldLookup> s_protoLookupCache =
45 protected StaticFieldLookup computeValue(Class<?> type) {
46 return getStaticField(type,
"proto");
50 private final String m_path;
51 private final TelemetryBackend m_backend;
52 private final ConcurrentMap<String, TelemetryTable> m_tableAliasesMap =
new ConcurrentHashMap<>();
53 private final ConcurrentMap<String, TelemetryTable> m_tablesMap =
new ConcurrentHashMap<>();
54 private final ConcurrentMap<String, CachedEntry> m_entriesMap =
new ConcurrentHashMap<>();
55 private String m_type;
56 private Boolean m_hasNonDiscardDescendant;
57 private final AtomicLong m_resetGeneration =
new AtomicLong();
59 private static StaticFieldLookup getStaticField(Class<?> type, String fieldName) {
61 return new StaticFieldLookup(type.getField(fieldName).get(
null),
null);
62 }
catch (NoSuchFieldException e) {
63 return new StaticFieldLookup(
64 null,
"could not get " + fieldName +
" field for " + type.getName());
65 }
catch (IllegalAccessException e) {
66 return new StaticFieldLookup(
67 null,
"could not access " + fieldName +
" field for " + type.getName());
76 TelemetryTable(String path) {
87 this(
"/", Objects.requireNonNull(backend));
98 m_tableAliasesMap.clear();
100 m_entriesMap.clear();
102 m_hasNonDiscardDescendant =
null;
103 m_resetGeneration.incrementAndGet();
126 String expectedType =
null;
127 long resetGeneration;
128 synchronized (
this) {
129 if (m_type !=
null) {
130 if (m_type.equals(typeString)) {
133 expectedType = m_type;
134 resetGeneration = -1;
137 resetGeneration = m_resetGeneration.get();
140 if (expectedType !=
null) {
141 typeMismatch(expectedType, typeString);
147 synchronized (
this) {
149 m_resetGeneration.get() == resetGeneration && Objects.equals(typeString, m_type);
153 entry.
logString(typeString,
"string", 0);
160 private void typeMismatch(String expectedType, String typeString) {
162 m_path,
"table type mismatch, expected '" + expectedType +
"', got '" + typeString +
"'");
171 synchronized (
this) {
183 TelemetryTable table = m_tableAliasesMap.get(name);
188 String path = getTablePath(name);
189 table = m_tablesMap.get(path);
191 TelemetryTable aliasTable = m_tableAliasesMap.putIfAbsent(name, table);
192 return aliasTable !=
null ? aliasTable : table;
195 TelemetryTable newTable =
197 table = m_tablesMap.putIfAbsent(path, newTable);
202 TelemetryTable aliasTable = m_tableAliasesMap.putIfAbsent(name, table);
203 return aliasTable !=
null ? aliasTable : table;
206 private String getEntryPath(String name) {
210 private String getTablePath(String name) {
211 return PathUtil.normalizeTableName(m_path + name +
"/");
220 private TelemetryEntry getEntry(String name) {
222 CachedEntry cachedEntry = m_entriesMap.get(name);
223 if (cachedEntry !=
null) {
224 if (cachedEntry.resetGeneration() == m_resetGeneration.get()) {
225 return cachedEntry.entry();
227 m_entriesMap.remove(name, cachedEntry);
231 long resetGeneration = m_resetGeneration.get();
232 String path = getEntryPath(name);
233 TelemetryEntry newEntry =
234 m_backend !=
null ? m_backend.getEntry(path) : TelemetryRegistry.getEntry(path);
235 if (m_backend ==
null && !newEntry.isDiscard()) {
236 TelemetryRegistry.applyEntryMetadata(path, newEntry);
239 CachedEntry newCachedEntry =
new CachedEntry(newEntry, resetGeneration);
240 cachedEntry = m_entriesMap.putIfAbsent(name, newCachedEntry);
241 if (cachedEntry ==
null) {
242 cachedEntry = newCachedEntry;
244 if (cachedEntry.resetGeneration() == m_resetGeneration.get()) {
245 return cachedEntry.entry();
247 m_entriesMap.remove(name, cachedEntry);
251 private void applyEntryMetadata(
254 CachedEntry cachedEntry = m_entriesMap.get(name);
255 if (cachedEntry !=
null) {
256 if (cachedEntry.resetGeneration() == m_resetGeneration.get()) {
257 applyEntryMetadata(cachedEntry.entry(), kind, key, value);
260 m_entriesMap.remove(name, cachedEntry);
264 long resetGeneration = m_resetGeneration.get();
265 TelemetryEntry newEntry =
266 m_backend !=
null ? m_backend.getEntry(path) : TelemetryRegistry.getEntry(path);
267 boolean metadataApplied =
false;
268 if (m_backend ==
null && !newEntry.isDiscard()) {
269 TelemetryRegistry.applyEntryMetadata(path, newEntry);
270 metadataApplied =
true;
273 CachedEntry newCachedEntry =
new CachedEntry(newEntry, resetGeneration);
274 cachedEntry = m_entriesMap.putIfAbsent(name, newCachedEntry);
275 boolean inserted = cachedEntry ==
null;
276 if (cachedEntry ==
null) {
277 cachedEntry = newCachedEntry;
279 if (cachedEntry.resetGeneration() == m_resetGeneration.get()) {
280 if (!(inserted && metadataApplied)) {
281 applyEntryMetadata(cachedEntry.entry(), kind, key, value);
285 m_entriesMap.remove(name, cachedEntry);
289 private void applyEntryMetadata(
291 if (!entry.isDiscard()) {
293 entry.keepDuplicates();
295 entry.setProperty(key, value);
300 private boolean shouldLogTableValue(String name, TelemetryTable table) {
301 TelemetryEntry entry = getEntry(name);
302 return !entry.isDiscard() || m_backend ==
null && table.hasNonDiscardDescendant();
305 private boolean hasNonDiscardDescendant() {
307 long resetGeneration;
308 synchronized (
this) {
309 if (m_hasNonDiscardDescendant !=
null) {
310 return m_hasNonDiscardDescendant;
312 resetGeneration = m_resetGeneration.get();
315 boolean hasNonDiscardDescendant = TelemetryRegistry.hasNonDiscardDescendant(m_path);
316 synchronized (
this) {
317 if (m_hasNonDiscardDescendant !=
null) {
318 return m_hasNonDiscardDescendant;
320 if (resetGeneration != m_resetGeneration.get()) {
323 m_hasNonDiscardDescendant = hasNonDiscardDescendant;
324 return m_hasNonDiscardDescendant;
335 if (m_backend !=
null) {
343 String path = getEntryPath(name);
344 boolean done =
false;
346 long resetGeneration = m_resetGeneration.get();
349 done = m_resetGeneration.get() == resetGeneration;
361 if (m_backend !=
null) {
369 String path = getEntryPath(name);
370 boolean done =
false;
372 long resetGeneration = m_resetGeneration.get();
375 done = m_resetGeneration.get() == resetGeneration;
399 public <T>
void log(String name, T value) {
403 if (!shouldLogTableValue(name, table)) {
406 String typeString = v.getTelemetryType();
407 if (typeString !=
null && !table.
setType(typeString)) {
412 case StructSerializable v -> {
413 TelemetryEntry entry = getEntry(name);
414 if (entry.isDiscard()) {
417 var lookup = s_structLookupCache.get(v.getClass());
418 if (lookup.warning() !=
null) {
419 TelemetryRegistry.reportWarning(getEntryPath(name), lookup.warning());
422 switch (lookup.value()) {
423 case Struct<?> s when s.getTypeClass().isAssignableFrom(value.getClass()) -> {
424 @SuppressWarnings(
"unchecked")
425 Struct<? super T> s2 = (Struct<? super T>) s;
426 entry.logStruct(value, s2, 0);
429 TelemetryRegistry.reportWarning(
431 "type mismatch, expected '"
432 + s.getTypeClass().getName()
434 + value.getClass().getName()
437 TelemetryRegistry.reportWarning(
439 "struct field for " + v.getClass().getName() +
" is not of Struct<?> type");
442 case ProtobufSerializable v -> {
443 TelemetryEntry entry = getEntry(name);
444 if (entry.isDiscard()) {
447 var lookup = s_protoLookupCache.get(v.getClass());
448 if (lookup.warning() !=
null) {
449 TelemetryRegistry.reportWarning(getEntryPath(name), lookup.warning());
452 switch (lookup.value()) {
453 case Protobuf<?, ?> s when s.getTypeClass().isAssignableFrom(value.getClass()) -> {
454 @SuppressWarnings(
"unchecked")
455 Protobuf<? super T, ?> s2 = (Protobuf<? super T, ?>) s;
456 entry.logProtobuf(value, s2, 0);
458 case Protobuf<?, ?> s ->
459 TelemetryRegistry.reportWarning(
461 "type mismatch, expected '"
462 + s.getTypeClass().getName()
464 + value.getClass().getName()
467 TelemetryRegistry.reportWarning(
469 "proto field for " + v.getClass().getName() +
" is not of Protobuf<?, ?> type");
473 TelemetryEntry entry = getEntry(name);
474 if (!entry.isDiscard()) {
475 entry.logBoolean(v.booleanValue(), 0);
479 TelemetryEntry entry = getEntry(name);
480 if (!entry.isDiscard()) {
481 entry.logFloat(v.floatValue(), 0);
485 TelemetryEntry entry = getEntry(name);
486 if (!entry.isDiscard()) {
487 entry.logDouble(v.doubleValue(), 0);
491 TelemetryEntry entry = getEntry(name);
492 if (!entry.isDiscard()) {
493 entry.logLong(v.longValue(), 0);
497 TelemetryEntry entry = getEntry(name);
498 if (!entry.isDiscard()) {
499 entry.logLong(v.longValue(), 0);
503 TelemetryEntry entry = getEntry(name);
504 if (!entry.isDiscard()) {
505 entry.logLong(v.longValue(), 0);
509 TelemetryEntry entry = getEntry(name);
510 if (!entry.isDiscard()) {
511 entry.logLong(v.longValue(), 0);
515 TelemetryEntry entry = getEntry(name);
516 if (!entry.isDiscard()) {
517 entry.logDouble(v.doubleValue(), 0);
521 TelemetryEntry entry = getEntry(name);
522 if (!entry.isDiscard()) {
523 entry.logString(v,
"string", 0);
526 case Collection<?> _ -> {
527 TelemetryEntry entry = getEntry(name);
528 if (!entry.isDiscard()) {
529 TelemetryRegistry.reportWarning(
530 getEntryPath(name),
"collection element type must be specified");
535 var handler = TelemetryRegistry.getTypeHandler(value);
536 if (handler !=
null) {
537 TelemetryTable table =
getTable(name);
538 if (!shouldLogTableValue(name, table)) {
541 handler.logTo(
this, name, value);
544 TelemetryEntry entry = getEntry(name);
545 if (!entry.isDiscard()) {
546 entry.logString(value.toString(),
"string", 0);
561 public <T>
void log(String name, T value, Struct<? super T> struct) {
562 TelemetryEntry entry = getEntry(name);
563 if (entry.isDiscard()) {
566 entry.logStruct(value, struct, 0);
577 public <T>
void log(String name, T value, Protobuf<? super T, ?> proto) {
578 TelemetryEntry entry = getEntry(name);
579 if (entry.isDiscard()) {
582 entry.logProtobuf(value, proto, 0);
594 public <T>
void log(String name, T[] value) {
595 TelemetryEntry entry = getEntry(name);
596 if (entry.isDiscard()) {
600 case StructSerializable[] _ -> {
601 Class<?> componentType = value.getClass().getComponentType();
602 var lookup = s_structLookupCache.get(componentType);
603 if (lookup.warning() !=
null) {
604 TelemetryRegistry.reportWarning(getEntryPath(name), lookup.warning());
607 switch (lookup.value()) {
608 case Struct<?> s when s.getTypeClass().isAssignableFrom(componentType) -> {
609 @SuppressWarnings(
"unchecked")
610 Struct<? super T> s2 = (Struct<? super T>) s;
611 entry.logStructArray(value, s2, 0);
614 TelemetryRegistry.reportWarning(
616 "type mismatch, expected '"
617 + s.getTypeClass().getName()
619 + value.getClass().getName()
622 TelemetryRegistry.reportWarning(
624 "struct field for " + componentType.getName() +
" is not of Struct<?> type");
627 case Boolean[] v -> {
628 boolean[] arr =
new boolean[v.length];
629 for (
int i = 0; i < v.length; i++) {
630 arr[i] = v[i].booleanValue();
632 entry.logBooleanArray(arr, 0);
635 byte[] arr =
new byte[v.length];
636 for (
int i = 0; i < v.length; i++) {
637 arr[i] = v[i].byteValue();
639 entry.logRaw(arr,
"raw", 0);
642 float[] arr =
new float[v.length];
643 for (
int i = 0; i < v.length; i++) {
644 arr[i] = v[i].floatValue();
646 entry.logFloatArray(arr, 0);
649 double[] arr =
new double[v.length];
650 for (
int i = 0; i < v.length; i++) {
651 arr[i] = v[i].doubleValue();
653 entry.logDoubleArray(arr, 0);
656 logNumberArray(entry, v);
661 String[] strs =
new String[value.length];
662 for (
int i = 0; i < value.length; i++) {
663 strs[i] = String.valueOf(value[i]);
665 entry.logStringArray(strs, 0);
678 public <T>
void log(String name, T[] value, Struct<? super T> struct) {
679 TelemetryEntry entry = getEntry(name);
680 if (entry.isDiscard()) {
683 entry.logStructArray(value, struct, 0);
715 public void log(String name, Collection<?> value, Class<?> elementType) {
716 Objects.requireNonNull(value,
"value");
717 Objects.requireNonNull(elementType,
"elementType");
723 if (isBooleanType(elementType)) {
724 logBooleanCollection(name, entry, value);
725 }
else if (isByteType(elementType)) {
726 logByteCollection(name, entry, value);
727 }
else if (isFloatType(elementType)) {
728 logFloatCollection(name, entry, value);
729 }
else if (isDoubleType(elementType)) {
730 logDoubleCollection(name, entry, value);
731 }
else if (isIntegralNumberType(elementType)) {
732 logLongCollection(name, entry, value);
733 }
else if (isNumberType(elementType)) {
734 logDoubleCollection(name, entry, value);
735 }
else if (elementType == String.class) {
736 logStringCollection(name, entry, value);
738 var lookup = s_structLookupCache.get(elementType);
739 if (lookup.warning() !=
null) {
743 switch (lookup.value()) {
745 logStructCollection(name, entry, value, s);
749 "type mismatch, expected '"
750 + s.getTypeClass().getName()
752 + elementType.getName()
757 "struct field for " + elementType.getName() +
" is not of Struct<?> type");
760 logFallbackStringCollection(entry, value);
772 public <T>
void log(String name, Collection<T> value,
Struct<? super T> struct) {
786 public void log(String name,
boolean value) {
800 public void log(String name,
byte value) {
814 public void log(String name,
short value) {
828 public void log(String name,
int value) {
842 public void log(String name,
long value) {
856 public void log(String name,
float value) {
870 public void log(String name,
double value) {
884 public void log(String name, String value) {
899 public void log(String name, String value, String typeString) {
913 public void log(String name,
boolean[] value) {
927 public void log(String name,
short[] value) {
941 public void log(String name,
int[] value) {
955 public void log(String name,
long[] value) {
969 public void log(String name,
float[] value) {
983 public void log(String name,
double[] value) {
997 public void log(String name, String[] value) {
1011 public void log(String name,
byte[] value) {
1016 entry.
logRaw(value,
"raw", 0);
1026 public void log(String name,
byte[] value, String typeString) {
1031 entry.
logRaw(value, typeString, 0);
1034 @SuppressWarnings(
"unchecked")
1035 private static <T> T[] toArray(Collection<?> value, Class<?> componentType) {
1036 return value.toArray((T[]) Array.newInstance(componentType, value.size()));
1039 private static boolean isBooleanType(Class<?> type) {
1040 return type == Boolean.class || type == Boolean.TYPE;
1043 private static boolean isByteType(Class<?> type) {
1044 return type == Byte.class || type == Byte.TYPE;
1047 private static boolean isFloatType(Class<?> type) {
1048 return type == Float.class || type == Float.TYPE;
1051 private static boolean isDoubleType(Class<?> type) {
1052 return type == Double.class || type == Double.TYPE;
1055 private static boolean isIntegralNumberType(Class<?> type) {
1056 return type == Short.class
1057 || type == Short.TYPE
1058 || type == Integer.class
1059 || type == Integer.TYPE
1060 || type == Long.class
1061 || type == Long.TYPE;
1064 private static boolean isNumberType(Class<?> type) {
1065 return Number.class.isAssignableFrom(type);
1068 private void logBooleanCollection(String name, TelemetryEntry entry, Collection<?> value) {
1069 boolean[] arr =
new boolean[value.size()];
1071 for (Object element : value) {
1072 if (!(element instanceof Boolean v)) {
1073 collectionElementTypeMismatch(name, Boolean.class, element);
1078 entry.logBooleanArray(arr, 0);
1081 private void logByteCollection(String name, TelemetryEntry entry, Collection<?> value) {
1082 byte[] arr =
new byte[value.size()];
1084 for (Object element : value) {
1085 if (!(element instanceof Number v)) {
1086 collectionElementTypeMismatch(name, Number.class, element);
1089 arr[i++] = v.byteValue();
1091 entry.logRaw(arr,
"raw", 0);
1094 private void logFloatCollection(String name, TelemetryEntry entry, Collection<?> value) {
1095 float[] arr =
new float[value.size()];
1097 for (Object element : value) {
1098 if (!(element instanceof Number v)) {
1099 collectionElementTypeMismatch(name, Number.class, element);
1102 arr[i++] = v.floatValue();
1104 entry.logFloatArray(arr, 0);
1107 private void logDoubleCollection(String name, TelemetryEntry entry, Collection<?> value) {
1108 double[] arr =
new double[value.size()];
1110 for (Object element : value) {
1111 if (!(element instanceof Number v)) {
1112 collectionElementTypeMismatch(name, Number.class, element);
1115 arr[i++] = v.doubleValue();
1117 entry.logDoubleArray(arr, 0);
1120 private void logLongCollection(String name, TelemetryEntry entry, Collection<?> value) {
1121 long[] arr =
new long[value.size()];
1123 for (Object element : value) {
1124 if (!(element instanceof Number v)) {
1125 collectionElementTypeMismatch(name, Number.class, element);
1128 arr[i++] = v.longValue();
1130 entry.logLongArray(arr, 0);
1133 private void logStringCollection(String name, TelemetryEntry entry, Collection<?> value) {
1134 String[] arr =
new String[value.size()];
1136 for (Object element : value) {
1137 if (!(element instanceof String v)) {
1138 collectionElementTypeMismatch(name, String.class, element);
1143 entry.logStringArray(arr, 0);
1146 private void logStructCollection(
1147 String name, TelemetryEntry entry, Collection<?> value, Struct<?> struct) {
1148 logStructCollectionImpl(name, entry, value, struct);
1151 private <T>
void logStructCollectionImpl(
1152 String name, TelemetryEntry entry, Collection<?> value, Struct<T> struct) {
1154 entry.logStructArray(toArray(value, struct.getTypeClass()), struct, 0);
1155 }
catch (ArrayStoreException e) {
1156 collectionElementTypeMismatch(name, struct.getTypeClass(),
null);
1160 private static void logFallbackStringCollection(TelemetryEntry entry, Collection<?> value) {
1161 String[] strs =
new String[value.size()];
1163 for (Object element : value) {
1164 strs[i++] = String.valueOf(element);
1166 entry.logStringArray(strs, 0);
1169 private void collectionElementTypeMismatch(String name, Class<?> expectedType, Object element) {
1170 TelemetryRegistry.reportWarning(
1172 "collection element type mismatch, expected '"
1173 + expectedType.getName()
1175 + (element ==
null ?
"null" : element.getClass().getName())
1179 private static void logNumberArray(TelemetryEntry entry, Number[] value) {
1180 if (isIntegralNumberType(value.getClass().getComponentType())) {
1181 long[] arr =
new long[value.length];
1182 for (
int i = 0; i < value.length; i++) {
1183 arr[i] = value[i].longValue();
1185 entry.logLongArray(arr, 0);
1187 double[] arr =
new double[value.length];
1188 for (
int i = 0; i < value.length; i++) {
1189 arr[i] = value[i].doubleValue();
1191 entry.logDoubleArray(arr, 0);