mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Merge pull request #2978 from ebean-orm/feature/stackWalker
Use StackWalker for ProfileLocation and CallOrigin
This commit is contained in:
@@ -2,6 +2,7 @@ package io.ebean.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static io.ebean.util.EncodeB64.enc;
|
||||
|
||||
@@ -27,19 +28,26 @@ public final class CallStack implements Serializable, CallOrigin {
|
||||
|
||||
private final String zeroHash;
|
||||
private final String pathHash;
|
||||
private final StackTraceElement[] callStack;
|
||||
private final Object[] callStack;
|
||||
private final int hc;
|
||||
|
||||
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
|
||||
public CallStack(Object[] callStack, int zeroHash, int pathHash) {
|
||||
this.callStack = callStack;
|
||||
this.hc = computeHashCode();
|
||||
this.zeroHash = enc(zeroHash);
|
||||
this.pathHash = enc(pathHash);
|
||||
}
|
||||
|
||||
public CallStack(List<StackWalker.StackFrame> frames) {
|
||||
this.callStack = frames.toArray(new Object[0]);
|
||||
this.hc = computeHashCode();
|
||||
this.zeroHash = enc(callStack[0].hashCode());
|
||||
this.pathHash = enc(hc);
|
||||
}
|
||||
|
||||
private int computeHashCode() {
|
||||
int hc = 0;
|
||||
for (StackTraceElement element : callStack) {
|
||||
for (Object element : callStack) {
|
||||
hc = 92821 * hc + element.hashCode();
|
||||
}
|
||||
return hc;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ebean.util;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Provides a stack filter that excludes ebean and jdk code.
|
||||
*/
|
||||
public final class StackWalkFilter {
|
||||
|
||||
private static final Filter FILTER = new Filter();
|
||||
|
||||
/**
|
||||
* Return a stack filter that excludes ebean and jdk code.
|
||||
*/
|
||||
public static Predicate<StackWalker.StackFrame> filter() {
|
||||
return FILTER;
|
||||
}
|
||||
|
||||
private static class Filter implements Predicate<StackWalker.StackFrame> {
|
||||
|
||||
@Override
|
||||
public boolean test(StackWalker.StackFrame stackFrame) {
|
||||
return !stackFrame.getClassName().startsWith("io.ebean")
|
||||
&& !stackFrame.getClassName().startsWith("jdk.")
|
||||
&& !stackFrame.getMethodName().startsWith("_ebean_");
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-52
@@ -2,17 +2,17 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.bean.CallOrigin;
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.util.StackWalkFilter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Default CallStackFactory where the Hash function for StackTraceElement includes the line number.
|
||||
*/
|
||||
public final class DefaultCallOriginFactory implements CallOriginFactory {
|
||||
|
||||
private static final int IGNORE_LEADING_ELEMENTS = 5;
|
||||
|
||||
private static final String IO_EBEAN = "io.ebean";
|
||||
final class DefaultCallOriginFactory implements CallOriginFactory {
|
||||
|
||||
private final int maxCallStack;
|
||||
|
||||
@@ -22,57 +22,18 @@ public final class DefaultCallOriginFactory implements CallOriginFactory {
|
||||
|
||||
@Override
|
||||
public CallOrigin createCallOrigin() {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
|
||||
// ignore the first 6 as they are always avaje stack elements
|
||||
int startIndex = IGNORE_LEADING_ELEMENTS;
|
||||
|
||||
// find the first non-avaje stackElement
|
||||
for (; startIndex < stackTrace.length; startIndex++) {
|
||||
if (!ignore(stackTrace[startIndex])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int stackLength = stackTrace.length - startIndex;
|
||||
if (stackLength > maxCallStack) {
|
||||
// maximum of maxCallStack stackTrace elements
|
||||
stackLength = maxCallStack;
|
||||
}
|
||||
|
||||
// create the 'interesting' part of the stackTrace
|
||||
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
|
||||
System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
|
||||
|
||||
if (stackLength < 1) {
|
||||
final var frames = StackWalker.getInstance().walk(this::filter);
|
||||
if (frames.isEmpty()) {
|
||||
// this should not really happen
|
||||
throw new RuntimeException("StackTraceElement size 0? stack: " + Arrays.toString(stackTrace));
|
||||
throw new RuntimeException("stackFrames filtered to empty for stack: " + Arrays.toString(Thread.currentThread().getStackTrace()));
|
||||
}
|
||||
|
||||
return createCallStack(finalTrace);
|
||||
return new CallStack(frames);
|
||||
}
|
||||
|
||||
private boolean ignore(StackTraceElement element) {
|
||||
if (element.getClassName().startsWith(IO_EBEAN)) {
|
||||
return true;
|
||||
}
|
||||
return element.getMethodName().startsWith("_ebean_");
|
||||
}
|
||||
|
||||
private CallOrigin createCallStack(StackTraceElement[] finalTrace) {
|
||||
return new CallStack(finalTrace, finalTrace[0].hashCode(), pathHash(finalTrace));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash code for the path excluding the first element.
|
||||
*/
|
||||
private int pathHash(StackTraceElement[] callStack) {
|
||||
|
||||
int hc = 0;
|
||||
for (int i = 1; i < callStack.length; i++) {
|
||||
hc = 92821 * hc + callStack[i].hashCode();
|
||||
}
|
||||
return hc;
|
||||
private <T> List<StackWalker.StackFrame> filter(Stream<StackWalker.StackFrame> frames) {
|
||||
return frames.filter(StackWalkFilter.filter())
|
||||
.limit(maxCallStack)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ final class NoopCallOriginFactory implements CallOriginFactory {
|
||||
|
||||
private static final StackTraceElement E0 = new StackTraceElement("none", "none", "none", 0);
|
||||
|
||||
private final CallOrigin COMMON = new CallStack(new StackTraceElement[]{E0}, 0, 0);
|
||||
private final CallOrigin COMMON = new CallStack(new Object[]{E0}, 0, 0);
|
||||
|
||||
@Override
|
||||
public CallOrigin createCallOrigin() {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.util.StackWalkFilter;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Default profile location that uses stack trace.
|
||||
*/
|
||||
class DProfileLocation implements ProfileLocation {
|
||||
|
||||
private static final String IO_EBEAN = "io.ebean";
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
private String fullLocation;
|
||||
@@ -88,14 +90,14 @@ class DProfileLocation implements ProfileLocation {
|
||||
}
|
||||
|
||||
private String create() {
|
||||
// relatively expensive but we only do it once per profile location
|
||||
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
|
||||
for (int i = 3; i < trace.length; i++) {
|
||||
if (!trace[i].getClassName().startsWith(IO_EBEAN)) {
|
||||
return withLineNumber(trace[i].toString());
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
return StackWalker.getInstance().walk(this::filter);
|
||||
}
|
||||
|
||||
private String filter(Stream<StackWalker.StackFrame> frames) {
|
||||
return frames.filter(StackWalkFilter.filter())
|
||||
.findFirst()
|
||||
.map(line -> withLineNumber(line.toString()))
|
||||
.orElse(UNKNOWN);
|
||||
}
|
||||
|
||||
private String withLineNumber(String traceLine) {
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
/**
|
||||
* Make DefaultCallOriginFactory accessible to tests.
|
||||
*/
|
||||
public class HelpDefaultCallOriginFactory {
|
||||
|
||||
public static CallOriginFactory create(int maxStack) {
|
||||
return new DefaultCallOriginFactory(maxStack);
|
||||
}
|
||||
}
|
||||
+3
-14
@@ -54,21 +54,10 @@ class BasicProfileLocationTest {
|
||||
void obtain() {
|
||||
DProfileLocation loc = new DTimedProfileLocation(12, "foo", MetricFactory.get().createTimedMetric("junk"));
|
||||
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
assertThat(loc.obtain()).isTrue();
|
||||
if (javaVersion.startsWith("1.8")) {
|
||||
assertThat(loc.fullLocation()).endsWith("invoke0(Native Method:12)");
|
||||
assertThat(loc.location()).isEqualTo("sun.reflect.NativeMethodAccessorImpl.invoke0");
|
||||
assertThat(loc.label()).isEqualTo("NativeMethodAccessorImpl.invoke0");
|
||||
} else if (javaVersion.startsWith("18") || javaVersion.startsWith("19") || javaVersion.startsWith("20")){
|
||||
assertThat(loc.fullLocation()).endsWith("jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)");
|
||||
assertThat(loc.location()).isEqualTo("java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke");
|
||||
assertThat(loc.label()).isEqualTo("DirectMethodHandleAccessor.invoke");
|
||||
} else if (javaVersion.startsWith("11") || javaVersion.startsWith("17")) {
|
||||
assertThat(loc.fullLocation()).endsWith("jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method:12)");
|
||||
assertThat(loc.location()).isEqualTo("java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0");
|
||||
assertThat(loc.label()).isEqualTo("NativeMethodAccessorImpl.invoke0");
|
||||
}
|
||||
assertThat(loc.fullLocation()).endsWith("org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)");
|
||||
assertThat(loc.location()).isEqualTo("org.junit.platform.commons.util.ReflectionUtils.invokeMethod");
|
||||
assertThat(loc.label()).isEqualTo("ReflectionUtils.invokeMethod");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.tests.server;
|
||||
|
||||
import io.ebean.bean.CallOrigin;
|
||||
import io.ebeaninternal.server.core.CallOriginFactory;
|
||||
import io.ebeaninternal.server.core.HelpDefaultCallOriginFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TestDefaultCallOriginFactory {
|
||||
|
||||
private final CallOriginFactory factory = HelpDefaultCallOriginFactory.create(2);
|
||||
|
||||
@Test
|
||||
void createCallOrigin() {
|
||||
CallOrigin callOrigin = inner();
|
||||
String topElement = callOrigin.getTopElement();
|
||||
assertThat(topElement).contains("org.tests.server.TestDefaultCallOriginFactory.inner(TestDefaultCallOriginFactory.java:23)");
|
||||
assertThat(callOrigin.getFullDescription()).contains("TestDefaultCallOriginFactory.java:16");
|
||||
}
|
||||
|
||||
private CallOrigin inner() {
|
||||
return factory.createCallOrigin();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ProfileLocationTest {
|
||||
class ProfileLocationTest {
|
||||
|
||||
private static final ProfileLocation loc = ProfileLocation.create(12, "foo");
|
||||
private static final ProfileLocation locB = ProfileLocation.create();
|
||||
@@ -17,7 +17,7 @@ public class ProfileLocationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_obtain() {
|
||||
void test_obtain() {
|
||||
assertThat(doIt()).isTrue();
|
||||
assertThat(loc.fullLocation()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt(ProfileLocationTest.java:16)");
|
||||
assertThat(loc.location()).isEqualTo("org.tests.profile.ProfileLocationTest.doIt");
|
||||
@@ -30,13 +30,12 @@ public class ProfileLocationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_add() {
|
||||
void test_add() {
|
||||
loc.add(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_constructor() {
|
||||
|
||||
void test_constructor() {
|
||||
Other other = new Other();
|
||||
other.hashCode();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user