Skip to content

Commit 5c9cd52

Browse files
authored
Merge pull request #48693 from angelozerr/qute_trace
Track start/end template + before/after node resolve
2 parents 3c22778 + c47714d commit 5c9cd52

File tree

13 files changed

+666
-10
lines changed

13 files changed

+666
-10
lines changed

independent-projects/qute/core/src/main/java/io/quarkus/qute/Engine.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.function.Predicate;
77

88
import io.quarkus.qute.TemplateLocator.TemplateLocation;
9+
import io.quarkus.qute.trace.TraceListener;
910

1011
/**
1112
* Represents a central point for template management.
@@ -182,6 +183,45 @@ default Template parse(String content, Variant variant) {
182183
*/
183184
boolean removeStandaloneLines();
184185

186+
/**
187+
* Returns the {@link TraceManager} responsible for managing trace listeners and
188+
* firing trace events during template rendering.
189+
*
190+
* @return the trace manager instance or {@code null} if tracing is disabled
191+
* @see EngineBuilder#enableTracing(boolean)
192+
*/
193+
TraceManager getTraceManager();
194+
195+
/**
196+
* Registers a new {@link TraceListener} to receive trace events.
197+
* <p>
198+
* The listener will be notified of template rendering and resolution events.
199+
*
200+
* @param listener the trace listener to add; must not be {@code null}
201+
*/
202+
default void addTraceListener(TraceListener listener) {
203+
TraceManager manager = getTraceManager();
204+
if (manager == null) {
205+
throw new IllegalStateException("Tracing not enabled");
206+
}
207+
manager.addTraceListener(listener);
208+
}
209+
210+
/**
211+
* Unregisters a previously registered {@link TraceListener}.
212+
* <p>
213+
* After removal, the listener will no longer receive trace events.
214+
*
215+
* @param listener the trace listener to remove; must not be {@code null}
216+
*/
217+
default void removeTraceListener(TraceListener listener) {
218+
TraceManager manager = getTraceManager();
219+
if (manager == null) {
220+
throw new IllegalStateException("Tracing not enabled");
221+
}
222+
manager.removeTraceListener(listener);
223+
}
224+
185225
/**
186226
* Initializes a new {@link EngineBuilder} instance from this engine.
187227
* <p>
@@ -191,4 +231,5 @@ default Template parse(String content, Variant variant) {
191231
* @return a new builder instance initialized from this engine
192232
*/
193233
EngineBuilder newBuilder();
234+
194235
}

independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineBuilder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public final class EngineBuilder {
4343
long timeout;
4444
boolean useAsyncTimeout;
4545
final List<EngineListener> listeners;
46+
boolean enableTracing;
4647

4748
EngineBuilder() {
4849
this.sectionHelperFactories = new HashMap<>();
@@ -336,6 +337,19 @@ public EngineBuilder addEngineListener(EngineListener listener) {
336337
return this;
337338
}
338339

340+
/**
341+
* If set to {@code true} then trace listeners that enable logging, profiling, or building interactive debugging tools, can
342+
* be registered with the {@link TraceManager}.
343+
*
344+
* @param value
345+
* @return self
346+
* @see Engine#getTraceManager()
347+
*/
348+
public EngineBuilder enableTracing(boolean value) {
349+
this.enableTracing = value;
350+
return this;
351+
}
352+
339353
/**
340354
*
341355
* @return a new engine instance

independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class EngineImpl implements Engine {
3939
final boolean removeStandaloneLines;
4040
private final long timeout;
4141
private final boolean useAsyncTimeout;
42+
final TraceManagerImpl traceManager;
4243

4344
EngineImpl(EngineBuilder builder) {
4445
this.sectionHelperFactories = Map.copyOf(builder.sectionHelperFactories);
@@ -55,6 +56,7 @@ class EngineImpl implements Engine {
5556
this.initializers = ImmutableList.copyOf(builder.initializers);
5657
this.timeout = builder.timeout;
5758
this.useAsyncTimeout = builder.useAsyncTimeout;
59+
this.traceManager = builder.enableTracing ? new TraceManagerImpl() : null;
5860
}
5961

6062
@Override
@@ -236,4 +238,9 @@ private Reader ensureBufferedReader(Reader reader) {
236238
reader);
237239
}
238240

241+
@Override
242+
public TraceManagerImpl getTraceManager() {
243+
return traceManager;
244+
}
245+
239246
}

independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.util.function.Supplier;
1414
import java.util.stream.Collectors;
1515

16+
import io.quarkus.qute.trace.ResolveEvent;
17+
1618
public final class Results {
1719

1820
public static final CompletedStage<Object> FALSE = CompletedStage.of(false);
@@ -43,18 +45,19 @@ public static CompletionStage<Object> notFound() {
4345
return CompletedStage.of(NotFound.EMPTY);
4446
}
4547

46-
static CompletionStage<ResultNode> resolveAndProcess(List<TemplateNode> nodes, ResolutionContext context) {
48+
static CompletionStage<ResultNode> resolveAndProcess(List<TemplateNode> nodes, ResolutionContext context,
49+
EngineImpl engine) {
4750
int nodesCount = nodes.size();
4851
if (nodesCount == 1) {
4952
// Single node in the block
50-
return resolveWith(nodes.get(0), context);
53+
return resolveWith(nodes.get(0), context, engine);
5154
}
5255
@SuppressWarnings("unchecked")
5356
Supplier<ResultNode>[] allResults = new Supplier[nodesCount];
5457
List<CompletableFuture<ResultNode>> asyncResults = null;
5558
int idx = 0;
5659
for (TemplateNode templateNode : nodes) {
57-
final CompletionStage<ResultNode> result = resolveWith(templateNode, context);
60+
final CompletionStage<ResultNode> result = resolveWith(templateNode, context, engine);
5861
if (result instanceof CompletedStage) {
5962
// No async computation needed
6063
allResults[idx++] = (CompletedStage<ResultNode>) result;
@@ -99,7 +102,29 @@ private static CompletionStage<ResultNode> toCompletionStage(Supplier<ResultNode
99102
* This method is trying to speed-up the resolve method which could become a virtual dispatch, harming
100103
* the performance of trivial implementations like TextNode::resolve, which is as simple as a field access.
101104
*/
102-
private static CompletionStage<ResultNode> resolveWith(TemplateNode templateNode, ResolutionContext context) {
105+
private static CompletionStage<ResultNode> resolveWith(TemplateNode templateNode, ResolutionContext context,
106+
EngineImpl engine) {
107+
TraceManagerImpl traceManager = engine.traceManager;
108+
if (traceManager == null) {
109+
return doResolveWith(templateNode, context);
110+
}
111+
112+
// Notify trace listeners before resolving the template node.
113+
final ResolveEvent event = new ResolveEvent(templateNode, context, engine);
114+
traceManager.fireBeforeResolveEvent(event);
115+
116+
return doResolveWith(templateNode, context).whenComplete((result, error) -> {
117+
// Notify trace listeners after resolving the template node.
118+
event.resolve(result, error);
119+
traceManager.fireAfterResolveEvent(event);
120+
});
121+
}
122+
123+
/**
124+
* This method is trying to speed-up the resolve method which could become a virtual dispatch, harming
125+
* the performance of trivial implementations like TextNode::resolve, which is as simple as a field access.
126+
*/
127+
private static CompletionStage<ResultNode> doResolveWith(TemplateNode templateNode, ResolutionContext context) {
103128
if (templateNode instanceof TextNode textNode) {
104129
return textNode.resolve(context);
105130
}

independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionNode.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ static Builder builder(String helperName, Origin origin, Parser parser,
3131
final SectionHelper helper;
3232
private final Origin origin;
3333
private final boolean traceLevel;
34+
private final EngineImpl engine;
3435

35-
SectionNode(String name, List<SectionBlock> blocks, SectionHelper helper, Origin origin) {
36+
SectionNode(String name, List<SectionBlock> blocks, SectionHelper helper, Origin origin, EngineImpl engine) {
3637
this.name = name;
3738
this.blocks = blocks;
3839
this.helper = helper;
3940
this.origin = origin;
4041
this.traceLevel = LOG.isTraceEnabled();
42+
this.engine = engine;
4143
}
4244

4345
public CompletionStage<ResultNode> resolve(ResolutionContext context, Map<String, Object> params) {
@@ -46,19 +48,20 @@ public CompletionStage<ResultNode> resolve(ResolutionContext context, Map<String
4648
}
4749
if (traceLevel && !Parser.ROOT_HELPER_NAME.equals(name)) {
4850
LOG.tracef("Resolve {#%s} started:%s", name, origin);
49-
return helper.resolve(new SectionResolutionContextImpl(context, params)).thenApply(r -> {
51+
return helper.resolve(new SectionResolutionContextImpl(context, params, engine)).thenApply(r -> {
5052
LOG.tracef("Resolve {#%s} completed:%s", name, origin);
5153
return r;
5254
});
5355
}
54-
return helper.resolve(new SectionResolutionContextImpl(context, params));
56+
return helper.resolve(new SectionResolutionContextImpl(context, params, engine));
5557
}
5658

5759
@Override
5860
public CompletionStage<ResultNode> resolve(ResolutionContext context) {
5961
return resolve(context, null);
6062
}
6163

64+
@Override
6265
public Origin getOrigin() {
6366
return origin;
6467
}
@@ -212,7 +215,7 @@ SectionNode build(Supplier<Template> currentTemlate) {
212215
return new SectionNode(helperName, blocks,
213216
factory.initialize(
214217
new SectionInitContextImpl(engine, blocks, errorInitializer, currentTemlate, helperName)),
215-
origin);
218+
origin, engine);
216219
}
217220

218221
}
@@ -221,10 +224,13 @@ class SectionResolutionContextImpl implements SectionResolutionContext {
221224

222225
private final Map<String, Object> params;
223226
private final ResolutionContext resolutionContext;
227+
private final EngineImpl engine;
224228

225-
public SectionResolutionContextImpl(ResolutionContext resolutionContext, Map<String, Object> params) {
229+
public SectionResolutionContextImpl(ResolutionContext resolutionContext, Map<String, Object> params,
230+
EngineImpl engine) {
226231
this.resolutionContext = resolutionContext;
227232
this.params = params;
233+
this.engine = engine;
228234
}
229235

230236
@Override
@@ -238,7 +244,7 @@ public CompletionStage<ResultNode> execute(SectionBlock block, ResolutionContext
238244
// Use the main block
239245
block = blocks.get(0);
240246
}
241-
return Results.resolveAndProcess(block.nodes, context);
247+
return Results.resolveAndProcess(block.nodes, context, engine);
242248
}
243249

244250
@Override

independent-projects/qute/core/src/main/java/io/quarkus/qute/TemplateImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.jboss.logging.Logger;
2424

25+
import io.quarkus.qute.trace.TemplateEvent;
2526
import io.smallrye.mutiny.Multi;
2627
import io.smallrye.mutiny.Uni;
2728

@@ -273,6 +274,11 @@ private CompletionStage<Void> renderData(Object data, Consumer<String> consumer)
273274
ResolutionContext rootContext = new ResolutionContextImpl(data,
274275
engine.getEvaluator(), null, this);
275276
setAttribute(DataNamespaceResolver.ROOT_CONTEXT, rootContext);
277+
TemplateEvent event = engine.traceManager != null ? new TemplateEvent(this, engine) : null;
278+
if (event != null) {
279+
// Notify trace listeners that template rendering has started.
280+
engine.getTraceManager().fireStartTemplate(event);
281+
}
276282
// Async resolution
277283
root.resolve(rootContext).whenComplete((r, t) -> {
278284
if (t != null) {
@@ -297,6 +303,11 @@ private CompletionStage<Void> renderData(Object data, Consumer<String> consumer)
297303

298304
}
299305
}
306+
if (event != null) {
307+
// Notify trace listeners that template rendering has ended.
308+
event.done();
309+
engine.getTraceManager().fireEndTemplate(event);
310+
}
300311
});
301312
return result;
302313
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.quarkus.qute;
2+
3+
import io.quarkus.qute.trace.TraceListener;
4+
5+
/**
6+
* Manager that holds and dispatches events to registered
7+
* {@link TraceListener}s.
8+
* <p>
9+
* Each {@link Engine} instance has its own {@code TraceManager} to coordinate
10+
* tracing callbacks during template rendering.
11+
*/
12+
public interface TraceManager {
13+
14+
/**
15+
* Registers a new {@link TraceListener} to receive trace events.
16+
* <p>
17+
* The listener will be notified of template rendering and resolution events.
18+
*
19+
* @param listener the trace listener to add; must not be {@code null}
20+
*/
21+
void addTraceListener(TraceListener listener);
22+
23+
/**
24+
* Unregisters a previously registered {@link TraceListener}.
25+
* <p>
26+
* After removal, the listener will no longer receive trace events.
27+
*
28+
* @param listener the trace listener to remove; must not be {@code null}
29+
*/
30+
void removeTraceListener(TraceListener listener);
31+
32+
/**
33+
* Returns {@code true} if there are any trace listeners currently registered,
34+
* {@code false} otherwise.
35+
* <p>
36+
* Trace listeners monitor and react to template rendering events.
37+
*
38+
* @return {@code true} if at least one trace listener is registered, {@code false} otherwise
39+
*/
40+
boolean hasTraceListeners();
41+
42+
}

0 commit comments

Comments
 (0)