Fix performance bug in logging: Avoid expensive stack trace/reflection

This shows significant resource usage for Flume jobs in pprof. It might
also improve performance in the frontend.

Also rename LazyArg-s for clarity, and to avoid the impression that use
of non-lazy args for logging is OK.

Change-Id: Ibd60407c5c87e79bdadd4546cb0c7bbd5b72156d
This commit is contained in:
Joerg Zieren
2019-10-11 09:13:19 +02:00
parent a581656c74
commit fd724b9b0f
7 changed files with 68 additions and 57 deletions

View File

@@ -18,11 +18,14 @@ import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.LazyArg;
import com.google.common.flogger.LazyArgs;
import com.google.gerrit.common.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
/** Metadata that is provided to {@link PerformanceLogger}s as context for performance records. */
@@ -141,7 +144,8 @@ public abstract class Metadata {
public abstract Optional<String> username();
/**
* Returns a string representation of this instance that is suitable for logging.
* Returns a string representation of this instance that is suitable for logging. This is wrapped
* in a {@link LazyArg<String>} because it is expensive to evaluate.
*
* <p>{@link #toString()} formats the {@link Optional} fields as {@code key=Optional[value]} or
* {@code key=Optional.empty}. Since this class has many optional fields from which usually only a
@@ -178,59 +182,64 @@ public abstract class Metadata {
*
* @return string representation of this instance that is suitable for logging
*/
public String toStringForLogging() {
// Append class name.
String className = getClass().getSimpleName();
if (className.startsWith("AutoValue_")) {
className = className.substring(10);
}
ToStringHelper stringHelper = MoreObjects.toStringHelper(className);
LazyArg<String> toStringForLoggingLazy() {
return LazyArgs.lazy(
() -> {
// Append class name.
String className = getClass().getSimpleName();
if (className.startsWith("AutoValue_")) {
className = className.substring(10);
}
ToStringHelper stringHelper = MoreObjects.toStringHelper(className);
// Append key-value pairs for field which are set.
Method[] methods = Metadata.class.getDeclaredMethods();
Arrays.<Method>sort(methods, (m1, m2) -> m1.getName().compareTo(m2.getName()));
for (Method method : methods) {
if (Modifier.isStatic(method.getModifiers())) {
// skip static method
continue;
}
// Append key-value pairs for field which are set.
Method[] methods = Metadata.class.getDeclaredMethods();
Arrays.sort(methods, Comparator.comparing(Method::getName));
for (Method method : methods) {
if (Modifier.isStatic(method.getModifiers())) {
// skip static method
continue;
}
if (method.getName().equals(Thread.currentThread().getStackTrace()[1].getMethodName())) {
// skip this method (toStringForLogging() method)
continue;
}
if (method.getName().matches("(lambda\\$)?toStringForLoggingLazy(\\$0)?")) {
// skip toStringForLoggingLazy() and the lambda itself
continue;
}
if (method.getReturnType().equals(Void.TYPE) || method.getParameterCount() > 0) {
// skip method since it's not a getter
continue;
}
if (method.getReturnType().equals(Void.TYPE) || method.getParameterCount() > 0) {
// skip method since it's not a getter
continue;
}
method.setAccessible(true);
method.setAccessible(true);
Object returnValue;
try {
returnValue = method.invoke(this);
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
// should never happen
throw new IllegalStateException(e);
}
Object returnValue;
try {
returnValue = method.invoke(this);
} catch (IllegalArgumentException
| IllegalAccessException
| InvocationTargetException e) {
// should never happen
throw new IllegalStateException(e);
}
if (returnValue instanceof Optional) {
Optional<?> fieldValueOptional = (Optional<?>) returnValue;
if (!fieldValueOptional.isPresent()) {
// drop this key-value pair
continue;
}
if (returnValue instanceof Optional) {
Optional<?> fieldValueOptional = (Optional<?>) returnValue;
if (!fieldValueOptional.isPresent()) {
// drop this key-value pair
continue;
}
// format as 'key=value' instead of 'key=Optional[value]'
stringHelper.add(method.getName(), fieldValueOptional.get());
} else {
// not an Optional value, keep as is
stringHelper.add(method.getName(), returnValue);
}
}
// format as 'key=value' instead of 'key=Optional[value]'
stringHelper.add(method.getName(), fieldValueOptional.get());
} else {
// not an Optional value, keep as is
stringHelper.add(method.getName(), returnValue);
}
}
return stringHelper.toString();
return stringHelper.toString();
});
}
public static Metadata.Builder builder() {