Skip to content

Commit

Permalink
PR #89 TemplateProcessingTracer reworked:
Browse files Browse the repository at this point in the history
- Instead of passing several arguments to the TemplateProcessingTracer method, only pass a single TracedElement object. This allow use to add new methods later without breaking backward compatibility.
- Added TracedElement.getDescription() to give a single-line canonical description of the element (same format that we use in FTL stack taces)
- Expose the Environment object to TemplateProcessingTracer methods
- Do not expose the TracedElement to exitElement, because we can't always ensure that it's available because of some internal tricks
- Added special branch to iterator directives and if-elseif-else, as because of the element stack tricks they apply normally, some elements were hidden from the tracer
- Some minor renaming.
- Added getter to Environment.
- Extended test coverage, and fixed quotation logic in the test (it was broken for example for ?interpret-ed snippets)
- Added more documentation
  • Loading branch information
ddekany committed Dec 18, 2023
1 parent d5c8c76 commit 52992c3
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 68 deletions.
32 changes: 20 additions & 12 deletions src/main/java/freemarker/core/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public final class Environment extends Configurable {

private boolean fastInvalidReferenceExceptions;

private TemplateProcessingTracer currentTracer;
private TemplateProcessingTracer templateProcessingTracer;

/**
* Retrieves the environment object associated with the current thread, or {@code null} if there's no template
Expand Down Expand Up @@ -2882,10 +2882,22 @@ public TemplateModel get(String key) throws TemplateModelException {
}

/**
* Sets the tracer to use for this environment.
* Sets the {@link TemplateProcessingTracer} to use for this {@link Environment};
* can be {@code null} to not have one. The default is also {@code null}.
*
* @since 2.3.33
*/
public void setTemplateProcessingTracer(TemplateProcessingTracer templateProcessingTracer) {
this.templateProcessingTracer = templateProcessingTracer;
}

/**
* Getter pair of {@link #setTemplateProcessingTracer(TemplateProcessingTracer)}. Can be {@code null}.
*
* @since 2.3.33
*/
public void setTracer(TemplateProcessingTracer tracer) {
currentTracer = tracer;
public TemplateProcessingTracer getTemplateProcessingTracer() {
return templateProcessingTracer;
}

private void pushElement(TemplateElement element) {
Expand All @@ -2900,19 +2912,15 @@ private void pushElement(TemplateElement element) {
this.instructionStack = instructionStack;
}
instructionStack[newSize - 1] = element;
if (currentTracer != null) {
currentTracer.enterElement(element.getTemplate(),
element.getBeginColumn(), element.getBeginLine(),
element.getEndColumn(), element.getEndLine(), element.isLeaf());
if (templateProcessingTracer != null) {
templateProcessingTracer.enterElement(this, element);
}
}

private void popElement() {
if (currentTracer != null) {
if (templateProcessingTracer != null) {
TemplateElement element = instructionStack[instructionStackSize - 1];
currentTracer.exitElement(element.getTemplate(),
element.getBeginColumn(), element.getBeginLine(),
element.getEndColumn(), element.getEndLine());
templateProcessingTracer.exitElement(this);
}
instructionStackSize--;
}
Expand Down
24 changes: 18 additions & 6 deletions src/main/java/freemarker/core/IfBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,24 @@ void addBlock(ConditionalBlock block) {
@Override
TemplateElement[] accept(Environment env) throws TemplateException, IOException {
int ln = getChildCount();
for (int i = 0; i < ln; i++) {
ConditionalBlock cblock = (ConditionalBlock) getChild(i);
Expression condition = cblock.condition;
env.replaceElementStackTop(cblock);
if (condition == null || condition.evalToBoolean(env)) {
return cblock.getChildBuffer();
if (env.getTemplateProcessingTracer() == null) {
for (int i = 0; i < ln; i++) {
ConditionalBlock cblock = (ConditionalBlock) getChild(i);
Expression condition = cblock.condition;
env.replaceElementStackTop(cblock);
if (condition == null || condition.evalToBoolean(env)) {
return cblock.getChildBuffer();
}
}
} else {
for (int i = 0; i < ln; i++) {
ConditionalBlock cblock = (ConditionalBlock) getChild(i);
Expression condition = cblock.condition;
env.replaceElementStackTop(cblock);
if (condition == null || condition.evalToBoolean(env)) {
env.visit(cblock);
return null;
}
}
}
return null;
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/freemarker/core/ListElseContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,24 @@ public ListElseContainer(IteratorBlock listPart, ElseOfList elsePart) {

@Override
TemplateElement[] accept(Environment env) throws TemplateException, IOException {
if (listPart.acceptWithResult(env)) {
boolean hadItems;

TemplateProcessingTracer templateProcessingTracer = env.getTemplateProcessingTracer();
if (templateProcessingTracer == null) {
hadItems = listPart.acceptWithResult(env);
} else {
templateProcessingTracer.enterElement(env, listPart);
try {
hadItems = listPart.acceptWithResult(env);
} finally {
templateProcessingTracer.exitElement(env);
}
}

if (hadItems) {
return null;
}

return new TemplateElement[] { elsePart };
}

Expand Down
8 changes: 4 additions & 4 deletions src/main/java/freemarker/core/TemplateElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* it.
*/
@Deprecated
abstract public class TemplateElement extends TemplateObject {
abstract public class TemplateElement extends TemplateObject implements TemplateProcessingTracer.TracedElement {

private static final int INITIAL_REGULATED_CHILD_BUFFER_CAPACITY = 6;

Expand Down Expand Up @@ -89,9 +89,9 @@ abstract public class TemplateElement extends TemplateObject {
* One-line description of the element, that contains all the information that is used in
* {@link #getCanonicalForm()}, except the nested content (elements) of the element. The expressions inside the
* element (the parameters) has to be shown. Meant to be used for stack traces, also for tree views that don't go
* down to the expression-level. There are no backward-compatibility guarantees regarding the format used ATM, but
* it must be regular enough to be machine-parseable, and it must contain all information necessary for restoring an
* AST equivalent to the original.
* down to the expression-level. There are no backward-compatibility guarantees regarding the format used, although
* it shouldn't change unless to fix a bug. It must be regular enough to be machine-parseable, and it must contain
* all information necessary for restoring an AST equivalent to the original.
*
* This final implementation calls {@link #dump(boolean) dump(false)}.
*
Expand Down
24 changes: 18 additions & 6 deletions src/main/java/freemarker/core/TemplateObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,35 @@ void setLocation(Template template, int beginColumn, int beginLine, int endColum
this.endColumn = endColumn;
this.endLine = endLine;
}

public final int getBeginColumn() {
return beginColumn;
}

/**
* 1-based index of the line (row) of the first character of the element in the template.
*/
public final int getBeginLine() {
return beginLine;
}

public final int getEndColumn() {
return endColumn;
/**
* 1-based index of the column of the first character of the element in the template.
*/
public final int getBeginColumn() {
return beginColumn;
}

/**
* 1-based index of the line (row) of the last character of the element in the template.
*/
public final int getEndLine() {
return endLine;
}

/**
* 1-based index of the column of the last character of the element in the template.
*/
public final int getEndColumn() {
return endColumn;
}

/**
* Returns a string that indicates
* where in the template source, this object is.
Expand Down
65 changes: 52 additions & 13 deletions src/main/java/freemarker/core/TemplateProcessingTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,77 @@

package freemarker.core;

import freemarker.ext.util.IdentityHashMap;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateTransformModel;
import freemarker.template.utility.ObjectFactory;

/**
* Run-time tracer plug-in. This may be * used to implement profiling, coverage analytis, execution tracing,
* Hooks to monitor as templates run. This may be used to implement profiling, coverage analysis, execution tracing,
* and other on-the-fly debugging mechanisms.
* <p>
* Use {@link Environment#setTracer(TemplateProcessingTracer)} to configure a tracer for the current environment.
* Use {@link Environment#setTemplateProcessingTracer(TemplateProcessingTracer)} to set a tracer for the current
* environment.
*
* @since 2.3.33
*/
public interface TemplateProcessingTracer {

/**
* Invoked by {@link Environment} whenever it starts processing a new template element. {@code
* isLeafElement} indicates whether this element is a leaf, or whether the tracer should expect
* to receive lower-level elements within the context of this one.
* Invoked by {@link Environment} whenever it starts processing a new template element. A template element is a
* directive call, an interpolation (like <code>${...}</code>), a comment block, or static text. Expressions
* are not template elements.
*
* @since 2.3.23
*/
void enterElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine,
boolean isLeafElement);
void enterElement(Environment env, TracedElement tracedElement);

/**
* Invoked by {@link Environment} whenever it completes processing a new template element.
*
* @see #enterElement(Environment, TracedElement)
*
* @since 2.3.23
*/
void exitElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine);
void exitElement(Environment env);

/**
* Information about the template element that we enter of exit.
*/
interface TracedElement {
/**
* The {@link Template} that contains this element.
*/
Template getTemplate();

/**
* 1-based index of the line (row) of the first character of the element in the template.
*/
int getBeginLine();

/**
* 1-based index of the column of the first character of the element in the template.
*/
int getBeginColumn();

/**
* 1-based index of the line (row) of the last character of the element in the template.
*/
int getEndColumn();

/**
* 1-based index of the column of the last character of the element in the template.
*/
int getEndLine();

/**
* If this is an element that has no nested elements.
*/
boolean isLeaf();

/**
* One-line description of the element, that also contains the parameter expressions, but not the nested content
* (child elements). There are no hard backward-compatibility guarantees regarding the format used, although
* it shouldn't change unless to fix a bug.
*/
String getDescription();
}

}
5 changes: 3 additions & 2 deletions src/main/java/freemarker/template/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,8 @@ public void addImport(LibraryLoad ll) {
*
* @param beginColumn the first column of the requested source, 1-based
* @param beginLine the first line of the requested source, 1-based
* @param endColumn the last column of the requested source, 1-based
* @param endColumn the last column of the requested source, 1-based. If this is beyond the last character of the
* line, it assumes that you want to whole line.
* @param endLine the last line of the requested source, 1-based
*
* @see freemarker.core.TemplateObject#getSource()
Expand All @@ -787,7 +788,7 @@ public String getSource(int beginColumn,
}
}
int lastLineLength = lines.get(endLine).toString().length();
int trailingCharsToDelete = lastLineLength - endColumn - 1;
int trailingCharsToDelete = endColumn < lastLineLength ? lastLineLength - endColumn - 1 : 0;
buf.delete(0, beginColumn);
buf.delete(buf.length() - trailingCharsToDelete, buf.length());
return buf.toString();
Expand Down
15 changes: 15 additions & 0 deletions src/manual/en_US/book.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30140,6 +30140,21 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
</listitem>
</itemizedlist>
</listitem>

<listitem>
<para><link
xlink:href="https://github.com/apache/freemarker/pull/89">GitHub
PR 89</link>: Added <literal>TemplateProcessingTracer</literal>
mechanism, that can be used to monitor coverage, and performance
<emphasis>inside</emphasis> templates as they are being
processed. For example, you could construct a heat map for how
often the different parts run, or finding the performance hot
spots. (There can be other creative uses, like watching for a
variable to have a certain value.) Use
<literal>Environment.setTemplateProcessingTracer(TemplateProcessingTracer)</literal>
to enable this kind of monitoring. (See the API docs for
more.)</para>
</listitem>
</itemizedlist>
</section>

Expand Down
Loading

0 comments on commit 52992c3

Please sign in to comment.