001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.epilogue.logging; 006 007import edu.wpi.first.util.protobuf.Protobuf; 008import edu.wpi.first.util.struct.Struct; 009import java.util.HashMap; 010import java.util.Map; 011import us.hebi.quickbuf.ProtoMessage; 012 013/** 014 * A backend that logs to an underlying backend, prepending all logged data with a specific prefix. 015 * Useful for logging nested data structures. 016 */ 017public class NestedBackend implements EpilogueBackend { 018 private final String m_prefix; 019 private final EpilogueBackend m_impl; 020 private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>(); 021 022 // String concatenation can be expensive, especially for deeply nested hierarchies with many 023 // logged fields. For example, logging a hypothetical Robot.elevator.io.getHeight() would result 024 // in "/Robot/" + "elevator/" + "io/" + "getHeight"; three concatenations and string and byte 025 // array allocations that need to be cleaned up by the GC. Caching the results means those 026 // allocations only occur once, resulting in no GC (the strings are always referenced in the 027 // cache), and minimal time costs (the String object caches its own hash code, so all we do is an 028 // O(1) table lookup per concatenation) 029 private final Map<String, String> m_prefixedIdentifiers = new HashMap<>(); 030 031 /** 032 * Creates a new nested backed underneath another backend. 033 * 034 * @param prefix the prefix to append to all data logged in the nested backend 035 * @param impl the backend to log to 036 */ 037 public NestedBackend(String prefix, EpilogueBackend impl) { 038 // Add a trailing slash if not already present 039 if (prefix.endsWith("/")) { 040 this.m_prefix = prefix; 041 } else { 042 this.m_prefix = prefix + "/"; 043 } 044 this.m_impl = impl; 045 } 046 047 /** 048 * Fast lookup to avoid redundant `m_prefix + identifier` concatenations. If the identifier has 049 * not been seen before, we compute the concatenation and cache the result for later invocations 050 * to read. This avoids redundantly recomputing the same concatenations every loop and 051 * significantly cuts down on the CPU and memory overhead of the Epilogue library. 052 * 053 * @param identifier The identifier to prepend with {@link #m_prefix}. 054 * @return The concatenated string. 055 */ 056 private String withPrefix(String identifier) { 057 // Using computeIfAbsent would result in a new lambda object allocation on every call 058 if (m_prefixedIdentifiers.containsKey(identifier)) { 059 return m_prefixedIdentifiers.get(identifier); 060 } 061 062 String result = m_prefix + identifier; 063 m_prefixedIdentifiers.put(identifier, result); 064 return result; 065 } 066 067 @Override 068 public EpilogueBackend getNested(String path) { 069 if (!m_nestedBackends.containsKey(path)) { 070 var nested = new NestedBackend(path, this); 071 m_nestedBackends.put(path, nested); 072 return nested; 073 } 074 075 return m_nestedBackends.get(path); 076 } 077 078 @Override 079 public void log(String identifier, int value) { 080 m_impl.log(withPrefix(identifier), value); 081 } 082 083 @Override 084 public void log(String identifier, long value) { 085 m_impl.log(withPrefix(identifier), value); 086 } 087 088 @Override 089 public void log(String identifier, float value) { 090 m_impl.log(withPrefix(identifier), value); 091 } 092 093 @Override 094 public void log(String identifier, double value) { 095 m_impl.log(withPrefix(identifier), value); 096 } 097 098 @Override 099 public void log(String identifier, boolean value) { 100 m_impl.log(withPrefix(identifier), value); 101 } 102 103 @Override 104 public void log(String identifier, byte[] value) { 105 m_impl.log(withPrefix(identifier), value); 106 } 107 108 @Override 109 public void log(String identifier, int[] value) { 110 m_impl.log(withPrefix(identifier), value); 111 } 112 113 @Override 114 public void log(String identifier, long[] value) { 115 m_impl.log(withPrefix(identifier), value); 116 } 117 118 @Override 119 public void log(String identifier, float[] value) { 120 m_impl.log(withPrefix(identifier), value); 121 } 122 123 @Override 124 public void log(String identifier, double[] value) { 125 m_impl.log(withPrefix(identifier), value); 126 } 127 128 @Override 129 public void log(String identifier, boolean[] value) { 130 m_impl.log(withPrefix(identifier), value); 131 } 132 133 @Override 134 public void log(String identifier, String value) { 135 m_impl.log(withPrefix(identifier), value); 136 } 137 138 @Override 139 public void log(String identifier, String[] value) { 140 m_impl.log(withPrefix(identifier), value); 141 } 142 143 @Override 144 public <S> void log(String identifier, S value, Struct<S> struct) { 145 m_impl.log(withPrefix(identifier), value, struct); 146 } 147 148 @Override 149 public <S> void log(String identifier, S[] value, Struct<S> struct) { 150 m_impl.log(withPrefix(identifier), value, struct); 151 } 152 153 @Override 154 public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) { 155 m_impl.log(m_prefix + identifier, value, proto); 156 } 157}