Skip to content

Commit 31c9f6e

Browse files
authored
Merge pull request #51 from gdgib/G2-1756-ThreadTrace
G2-1756 Support multi-thread tracing
2 parents 3ff675e + 9cc6be7 commit 31c9f6e

File tree

8 files changed

+179
-47
lines changed

8 files changed

+179
-47
lines changed

ha-trace/src/main/java/com/g2forge/habitat/trace/StackTraceAnalyzer.java renamed to ha-trace/src/main/java/com/g2forge/habitat/trace/AStackTraceAnalyzer.java

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,30 @@
88
import java.util.stream.Collectors;
99
import java.util.stream.Stream;
1010

11-
import lombok.Builder;
12-
import lombok.Data;
1311
import lombok.EqualsAndHashCode;
1412
import lombok.Getter;
15-
import lombok.RequiredArgsConstructor;
16-
17-
@Data
18-
@Builder(toBuilder = true)
19-
@RequiredArgsConstructor
20-
public class StackTraceAnalyzer implements IStackTraceAnalyzer {
21-
protected final Object context;
22-
23-
protected final Throwable throwable;
2413

14+
public abstract class AStackTraceAnalyzer implements IStackTraceAnalyzer {
2515
@Getter(lazy = true)
2616
@EqualsAndHashCode.Exclude
2717
private final List<? extends ISmartStackTraceElement> elements = computeElements();
2818

29-
public StackTraceAnalyzer() {
30-
this(new Throwable());
31-
}
32-
33-
public StackTraceAnalyzer(Object context) {
34-
this(context, new Throwable());
35-
}
36-
37-
public StackTraceAnalyzer(Throwable throwable) {
38-
this(throwable, throwable);
39-
}
40-
4119
protected List<? extends ISmartStackTraceElement> computeElements() {
4220
final ClassLoader classLoader = getContextClassloader();
43-
return Stream.of(getThrowable().getStackTrace()).map(e -> new SmartStackTraceElement(e, classLoader)).collect(Collectors.toList());
21+
return Stream.of(getStackTrace()).map(e -> new SmartStackTraceElement(e, classLoader)).collect(Collectors.toList());
4422
}
4523

4624
@Override
4725
public Executable getCaller() {
48-
return getExecutable(0, 2);
49-
}
50-
51-
protected ClassLoader getContextClassloader() {
52-
final ClassLoader retVal = getContext().getClass().getClassLoader();
53-
if (retVal == null) return Thread.currentThread().getContextClassLoader();
54-
return retVal;
26+
return getExecutable(0, getInvisibles());
5527
}
5628

29+
protected abstract ClassLoader getContextClassloader();
30+
5731
@Override
5832
public Executable getEntrypoint(Set<EntrypointFilter> filters) {
5933
final List<? extends ISmartStackTraceElement> elements = this.getElements();
60-
final List<? extends ISmartStackTraceElement> limited = new ArrayList<>(elements.subList(2, elements.size()));
34+
final List<? extends ISmartStackTraceElement> limited = new ArrayList<>(elements.subList(getInvisibles(), elements.size()));
6135
Collections.reverse(limited);
6236
final Set<String> prefixes = filters.stream().flatMap(filter -> Stream.of(filter.getPrefixes())).collect(Collectors.toSet());
6337
final ISmartStackTraceElement element = limited.stream().filter(e -> {
@@ -68,7 +42,7 @@ public Executable getEntrypoint(Set<EntrypointFilter> filters) {
6842
}
6943

7044
protected Executable getExecutable(int offset, int invisible) {
71-
final StackTraceElement[] stackTrace = getThrowable().getStackTrace();
45+
final StackTraceElement[] stackTrace = getStackTrace();
7246

7347
final int actual, pretendDepth = stackTrace.length - invisible;
7448
if (offset >= 0) {
@@ -82,8 +56,12 @@ protected Executable getExecutable(int offset, int invisible) {
8256
return new SmartStackTraceElement(stackTrace[actual], getContextClassloader()).getExecutable();
8357
}
8458

59+
protected abstract int getInvisibles();
60+
8561
@Override
8662
public Executable getMain() {
87-
return getExecutable(-1, 2);
63+
return getExecutable(-1, getInvisibles());
8864
}
65+
66+
protected abstract StackTraceElement[] getStackTrace();
8967
}

ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.lang.reflect.Executable;
44
import java.util.Set;
5+
import java.util.stream.Stream;
56

67
import com.g2forge.alexandria.java.core.marker.Helpers;
78

@@ -10,12 +11,26 @@
1011
@UtilityClass
1112
@Helpers
1213
public class HTrace {
14+
protected static ThrowableStackTraceAnalyzer createSTA() {
15+
return new ThrowableStackTraceAnalyzer(new Throwable(), 2);
16+
}
17+
18+
protected static Thread[] enumerateThreads() {
19+
Thread[] threads = new Thread[(int) (Thread.activeCount() * 1.5)];
20+
while (true) {
21+
final int count = Thread.enumerate(threads);
22+
if (count <= threads.length) break;
23+
threads = new Thread[(int) (count * 1.5)];
24+
}
25+
return threads;
26+
}
27+
1328
public static Executable getCaller() {
14-
return new StackTraceAnalyzer().getCaller();
29+
return createSTA().getCaller();
1530
}
1631

1732
public static Executable getEntrypoint(Set<EntrypointFilter> filters) {
18-
return new StackTraceAnalyzer().getEntrypoint(filters);
33+
return createSTA().getEntrypoint(filters);
1934
}
2035

2136
/**
@@ -26,10 +41,14 @@ public static Executable getEntrypoint(Set<EntrypointFilter> filters) {
2641
* @return
2742
*/
2843
public static Executable getExecutable(int offset) {
29-
return new StackTraceAnalyzer().getExecutable(offset, 2);
44+
return createSTA().getExecutable(offset, 2);
3045
}
3146

3247
public static Executable getMain() {
33-
return new StackTraceAnalyzer().getMain();
48+
return createSTA().getMain();
49+
}
50+
51+
public static Thread getMainThread() {
52+
return Stream.of(enumerateThreads()).filter(t -> t.getId() == 1).findAny().get();
3453
}
3554
}

ha-trace/src/main/java/com/g2forge/habitat/trace/SmartStackTraceElement.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
import java.io.InputStream;
55
import java.lang.reflect.Executable;
66
import java.lang.reflect.Field;
7+
import java.lang.reflect.Method;
78
import java.util.Map;
89
import java.util.stream.Stream;
910

11+
import com.g2forge.alexandria.java.core.helpers.HCollector;
1012
import com.g2forge.alexandria.java.core.helpers.HStream;
1113

1214
import lombok.AccessLevel;
@@ -59,8 +61,14 @@ protected Executable computeExecutable() {
5961
final String descriptor = lineMap.get(element.getLineNumber());
6062

6163
// Find the method with the correct name and descriptor
62-
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> HTraceInternal.getDescriptor(c).equals(descriptor)));
63-
else return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> HTraceInternal.getDescriptor(m).equals(descriptor)));
64+
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> (descriptor == null) || HTraceInternal.getDescriptor(c).equals(descriptor)));
65+
else {
66+
try {
67+
return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> (descriptor == null) || HTraceInternal.getDescriptor(m).equals(descriptor)));
68+
} catch (Throwable thowable) {
69+
throw new RuntimeException(String.format("Failed to find method %1$s.%2$s with descriptor %3$s, possible methods are: %4$s", getDeclaringClass().getName(), element.getMethodName(), descriptor, Stream.of(getDeclaringClass().getDeclaredMethods()).map(Method::getName).collect(HCollector.joining(", ", ", & "))), thowable);
70+
}
71+
}
6472
}
6573

6674
protected Field computeInitialized() {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.g2forge.habitat.trace;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
import lombok.EqualsAndHashCode;
6+
import lombok.RequiredArgsConstructor;
7+
8+
@Data
9+
@Builder(toBuilder = true)
10+
@RequiredArgsConstructor
11+
@EqualsAndHashCode(callSuper = false)
12+
public class ThreadStackTraceAnalyzer extends AStackTraceAnalyzer {
13+
protected final ClassLoader contextClassloader;
14+
15+
protected final StackTraceElement[] stackTrace;
16+
17+
protected final int invisibles;
18+
19+
public ThreadStackTraceAnalyzer(Thread thread, int invisibles) {
20+
this.contextClassloader = thread.getContextClassLoader();
21+
this.stackTrace = thread.getStackTrace();
22+
this.invisibles = invisibles;
23+
}
24+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.g2forge.habitat.trace;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
import lombok.EqualsAndHashCode;
6+
import lombok.RequiredArgsConstructor;
7+
8+
@Data
9+
@Builder(toBuilder = true)
10+
@RequiredArgsConstructor
11+
@EqualsAndHashCode(callSuper = false)
12+
public class ThrowableStackTraceAnalyzer extends AStackTraceAnalyzer {
13+
protected final Object context;
14+
15+
protected final Throwable throwable;
16+
17+
protected final int invisibles;
18+
19+
public ThrowableStackTraceAnalyzer() {
20+
this(new Throwable(), 1);
21+
}
22+
23+
public ThrowableStackTraceAnalyzer(Object context) {
24+
this(context, new Throwable(), 1);
25+
}
26+
27+
public ThrowableStackTraceAnalyzer(Throwable throwable, int invisibles) {
28+
this(throwable, throwable, invisibles);
29+
}
30+
31+
@Override
32+
protected ClassLoader getContextClassloader() {
33+
final ClassLoader retVal = getContext().getClass().getClassLoader();
34+
if (retVal == null) return Thread.currentThread().getContextClassLoader();
35+
return retVal;
36+
}
37+
38+
@Override
39+
protected StackTraceElement[] getStackTrace() {
40+
return getThrowable().getStackTrace();
41+
}
42+
}

ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
import org.junit.Test;
88

9+
import com.g2forge.alexandria.java.core.helpers.HCollection;
910
import com.g2forge.alexandria.test.HAssert;
1011

1112
public class TestHTrace {
1213
public static class InitializerInline {
1314
protected final int a = 0;
14-
protected final IStackTraceAnalyzer field = new StackTraceAnalyzer(this);
15+
protected final IStackTraceAnalyzer field = new ThrowableStackTraceAnalyzer(this);
1516
protected final int b = 0;
1617
}
1718

@@ -22,7 +23,7 @@ public static class InitializerMethod {
2223

2324
public InitializerMethod() {
2425
a = 0;
25-
field = new StackTraceAnalyzer(this);
26+
field = new ThrowableStackTraceAnalyzer(this);
2627
b = 0;
2728
}
2829
}
@@ -31,7 +32,7 @@ public static class InitializerMultiline {
3132
protected final int a = 0;
3233
// @formatter:off
3334
protected final IStackTraceAnalyzer field =
34-
new StackTraceAnalyzer(
35+
new ThrowableStackTraceAnalyzer(
3536
this);
3637
// @formatter:on
3738
protected final int b;
@@ -45,11 +46,11 @@ public static class InitializerMultiple {
4546
protected final IStackTraceAnalyzer field;
4647

4748
public InitializerMultiple(boolean b) {
48-
field = new StackTraceAnalyzer(this);
49+
field = new ThrowableStackTraceAnalyzer(this);
4950
}
5051

5152
public InitializerMultiple(int x) {
52-
field = new StackTraceAnalyzer(this);
53+
field = new ThrowableStackTraceAnalyzer(this);
5354
}
5455
}
5556

@@ -72,8 +73,9 @@ public void caller() {
7273

7374
@Test
7475
public void entrypoint() {
75-
final Executable entrypoint = HTrace.getEntrypoint(EntrypointFilter.ALL);
76-
HAssert.assertEquals(getClass(), entrypoint.getDeclaringClass());
76+
final Executable executable = HTrace.getEntrypoint(EntrypointFilter.ALL);
77+
HAssert.assertEquals("entrypoint", executable.getName());
78+
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
7779
}
7880

7981
@Test
@@ -108,6 +110,19 @@ public void initializerMultiple() {
108110
HAssert.assertEquals("field", field.getName());
109111
}
110112

113+
@Test
114+
public void mainThread() {
115+
final Thread mainThread = HTrace.getMainThread();
116+
final Thread currentThread = Thread.currentThread();
117+
// If the main thread is current, there's nothing more to test
118+
if (mainThread != currentThread) {
119+
// Main thread isn't current, so make sure we're starting form Thread.run
120+
final Executable entrypoint = new ThreadStackTraceAnalyzer(currentThread, 0).getEntrypoint(HCollection.emptySet());
121+
HAssert.assertEquals(Thread.class, entrypoint.getDeclaringClass());
122+
HAssert.assertEquals("run", entrypoint.getName());
123+
}
124+
}
125+
111126
@Test
112127
public void object() {
113128
HAssert.assertEquals(Integer.class, new Multiple().m((Integer[]) null).getParameterTypes()[0].getComponentType());
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.g2forge.habitat.trace;
2+
3+
import java.lang.reflect.Executable;
4+
5+
import org.junit.Test;
6+
7+
import com.g2forge.alexandria.test.HAssert;
8+
9+
public class TestThreadStackTraceAnalyzer {
10+
@Test
11+
public void caller() {
12+
final Executable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getCaller();
13+
HAssert.assertEquals("caller", executable.getName());
14+
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
15+
}
16+
17+
@Test
18+
public void entrypoint() {
19+
final Executable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getEntrypoint(EntrypointFilter.ALL);
20+
HAssert.assertEquals("entrypoint", executable.getName());
21+
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.g2forge.habitat.trace;
2+
3+
import java.lang.reflect.Executable;
4+
5+
import org.junit.Test;
6+
7+
import com.g2forge.alexandria.test.HAssert;
8+
9+
public class TestThrowableStackTraceAnalyzer {
10+
@Test
11+
public void caller() {
12+
final Executable executable = new ThrowableStackTraceAnalyzer().getCaller();
13+
HAssert.assertEquals("caller", executable.getName());
14+
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
15+
}
16+
17+
@Test
18+
public void entrypoint() {
19+
final Executable executable = new ThrowableStackTraceAnalyzer().getEntrypoint(EntrypointFilter.ALL);
20+
HAssert.assertEquals("entrypoint", executable.getName());
21+
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
22+
}
23+
}

0 commit comments

Comments
 (0)