/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.btrace.util.templates.impl;
import com.sun.btrace.util.Interval;
import com.sun.btrace.instr.MethodTracker;
import com.sun.btrace.annotations.Sampled;
import com.sun.btrace.org.objectweb.asm.Label;
import com.sun.btrace.org.objectweb.asm.Opcodes;
import com.sun.btrace.org.objectweb.asm.Type;
import com.sun.btrace.runtime.Assembler;
import static com.sun.btrace.runtime.Constants.*;
import com.sun.btrace.util.MethodID;
import com.sun.btrace.util.templates.BaseTemplateExpander;
import com.sun.btrace.util.templates.Template;
import com.sun.btrace.util.templates.TemplateExpanderVisitor;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This expander takes care of macros related to all the sampling and timing functionality
* @author Jaroslav Bachorik
*/
public class MethodTrackingExpander extends BaseTemplateExpander {
/**
* Will provide necessary calls to enable sampling and timing
* the method or method call.
* <p>
* Accepts the following tags
* <ul>
* <li>{@code $TIMED} - enables the timing support</li>
* <li>{@code $SAMPLER=[Const | Adaptive]} - selects a sampler, if any</li>
* <li>{@code $MEAN=<mean>} - only when sampling; the mean number of hits between samples</li>
* <li>{@code $METHODID=<id>} - id generated by {@linkplain MethodID#getMethodId(java.lang.String, java.lang.String, java.lang.String)} </li>
* <li>{@code $LEVEL=<cond>} - level match condition</li>
* </ul>
*/
public static final Template ENTRY = new Template("mc$entry", "()V");
/**
* Will insert the code to obtain the execution duration.
*/
public static final Template DURATION = new Template("mc$dur", "()J");
/**
* Will generate the branching logic (if sampling) and/or retrieve the timestamp
* for the end of execution.
* <p>
* Accepts the following tags
* <ul>
* <li>{@code $TIMED} - enables the timing support<li>
* <li>{@code $METHODID=<id>} - id generated by {@linkplain MethodID#getMethodId(java.lang.String, java.lang.String, java.lang.String)} </li>
* </ul>
*/
public static final Template TEST_SAMPLE = new Template("mc$test", "()V");
/**
* Will create the jump target for the else part of the condition
* generated by {@link MethodTrackingExpander#TEST_SAMPLE} template.
*/
public static final Template ELSE_SAMPLE = new Template("mc$else", "()V");
/**
* This must be inserted at the exit point when using adaptive sampling.
*/
public static final Template EXIT = new Template("mc$exit", "()V");
/**
* Will reset the expander state - useful for multiple return points.
*/
public static final Template RESET = new Template("mc$reset", "()V");
public static final String $TIMED = "timed";
public static final String $MEAN = "mean";
public static final String $SAMPLER = "sampler";
public static final String $METHODID = "methodid";
public static final String $LEVEL = "level";
private static final String METHOD_COUNTER_CLASS = "com/sun/btrace/instr/MethodTracker";
private boolean isTimed = false;
private boolean isSampled = false;
private Sampled.Sampler samplerKind = Sampled.Sampler.None;
private int samplerMean = -1;
private final int methodId;
private int entryTsVar = Integer.MIN_VALUE;
private int sHitVar = Integer.MIN_VALUE;
private int durationVar = Integer.MIN_VALUE;
private int globalLevelVar = Integer.MIN_VALUE;
private boolean durationComputed = false;
private final Collection<Interval> levelIntervals = new LinkedList<>();
private Label elseLabel = null;
private Label samplerLabel = null;
public MethodTrackingExpander(int methodId) {
super(ENTRY, DURATION, TEST_SAMPLE, ELSE_SAMPLE, EXIT, RESET);
this.methodId = methodId;
}
@Override
protected void recordTemplate(Template t) {
if (ENTRY.equals(t)) {
Map<String, String> m = t.getTagMap();
isTimed = m.containsKey($TIMED);
String sKind = m.get($SAMPLER);
String sMean = m.get($MEAN);
String levelStr = m.get($LEVEL);
if (levelStr != null && !levelStr.isEmpty()) {
Interval itv = Interval.fromString(levelStr);
levelIntervals.add(itv);
}
if (sKind != null) {
if (samplerMean != 0) {
int mean = sMean != null ? Integer.parseInt(sMean) : Sampled.MEAN_DEFAULT;
// The average mean sampler has the highest precedence
if (samplerKind != Sampled.Sampler.Const) {
samplerKind = Sampled.Sampler.valueOf(sKind);
}
if (samplerMean == -1) {
samplerMean = mean;
} else if (samplerMean > 0) {
samplerMean = Math.min(samplerMean, mean);
}
isSampled = (samplerKind != null && samplerMean > 0);
}
} else {
// hitting a method in non-sampled mode means that no
// sampling will be done even though other scripts request it
samplerMean = 0;
isSampled = false;
}
}
}
@Override
protected Result expandTemplate(TemplateExpanderVisitor v, Template t) {
int localMethodId = methodId;
String sMethodId = t.getTagMap().get($METHODID);
if (sMethodId != null) {
localMethodId = Integer.parseInt(sMethodId);
}
final int mid = localMethodId;
if (tryExpandEntry(t, mid, v) ||
tryExpandTest(t, mid, v) ||
tryExpandElse(t, v) ||
tryExpandDuration(t, v) ||
tryExpandExit(t, mid, v) ||
tryExpandReset(t, v)) return Result.EXPANDED;
return Result.IGNORED;
}
private boolean tryExpandEntry(Template t, final int mid, TemplateExpanderVisitor v) {
if (ENTRY.equals(t)) {
if (isSampled) {
MethodTracker.registerCounter(mid, samplerMean);
if (isTimed) {
v.expand(new TimingSamplerEntry(mid));
} else {
v.expand(new SamplerEntry(mid));
}
} else {
if (isTimed) {
v.expand(new TimingEntry());
}
}
return true;
}
return false;
}
private boolean tryExpandTest(Template t, final int mid, TemplateExpanderVisitor v) {
if (TEST_SAMPLE.equals(t)) {
samplerLabel = new Label();
boolean collectTime = t.getTagMap().containsKey($TIMED);
if (isSampled) {
v.expand(new SamplerTest());
if (isTimed && collectTime) {
v.expand(new TimingSamplerTest(mid));
}
} else {
if (isTimed && collectTime) {
v.expand(new TimingTest());
}
}
v.asm().label(samplerLabel);
samplerLabel = null;
return true;
}
return false;
}
private boolean tryExpandElse(Template t, TemplateExpanderVisitor v) {
if (ELSE_SAMPLE.equals(t)) {
v.expand(new Else());
return true;
}
return false;
}
private boolean tryExpandDuration(Template t, TemplateExpanderVisitor v) {
if (DURATION.equals(t)) {
v.expand(new Duration());
return true;
}
return false;
}
private boolean tryExpandExit(Template t, final int mid, TemplateExpanderVisitor v) {
if (EXIT.equals(t)) {
v.expand(new Exit(mid));
return true;
}
return false;
}
private boolean tryExpandReset(Template t, TemplateExpanderVisitor v) {
if (RESET.equals(t)) {
entryTsVar = Integer.MIN_VALUE;
sHitVar = Integer.MIN_VALUE;
globalLevelVar = Integer.MIN_VALUE;
durationComputed = false;
return true;
}
return false;
}
@Override
public void resetState() {
durationComputed = false;
}
private class TimingSamplerEntry implements Consumer<TemplateExpanderVisitor> {
private final int mid;
public TimingSamplerEntry(int mid) {
this.mid = mid;
}
@Override
public void consume(final TemplateExpanderVisitor e) {
final Assembler asm = e.asm();
// initialize variables used in the coniditional code
if (durationVar == Integer.MIN_VALUE) {
asm.ldc(0L);
durationVar = e.storeNewLocal(Type.LONG_TYPE);
}
if (sHitVar == Integer.MIN_VALUE && entryTsVar == Integer.MIN_VALUE) {
Label skipTarget = addLevelChecks(e, new Runnable() {
@Override
public void run() {
if (entryTsVar == Integer.MIN_VALUE) {
asm.ldc(0L);
entryTsVar = e.storeNewLocal(Type.LONG_TYPE);
}
if (sHitVar == Integer.MIN_VALUE) {
asm.ldc(0);
sHitVar = e.storeNewLocal(Type.INT_TYPE);
}
}
});
asm.ldc(mid);
switch (samplerKind) {
case Const: {
asm.invokeStatic(
METHOD_COUNTER_CLASS,
"hitTimed", "(I)J"
);
break;
}
case Adaptive: {
asm.invokeStatic(
METHOD_COUNTER_CLASS,
"hitTimedAdaptive", "(I)J"
);
break;
}
default:
// do nothing
}
asm.dup2();
if (entryTsVar == Integer.MIN_VALUE) {
entryTsVar = e.storeNewLocal(Type.LONG_TYPE);
} else {
asm.storeLocal(Type.LONG_TYPE, entryTsVar);
}
e.visitInsn(Opcodes.L2I);
if (sHitVar == Integer.MIN_VALUE) {
sHitVar = e.storeNewLocal(Type.INT_TYPE);
} else {
asm.storeLocal(Type.INT_TYPE, sHitVar);
}
if (skipTarget != null) {
asm.label(skipTarget);
}
}
}
}
private class SamplerEntry implements Consumer<TemplateExpanderVisitor> {
private final int mid;
public SamplerEntry(int mid) {
this.mid = mid;
}
@Override
public void consume(final TemplateExpanderVisitor e) {
final Assembler asm = e.asm();
if (sHitVar == Integer.MIN_VALUE) {
Label skipTarget = addLevelChecks(e, new Runnable() {
@Override
public void run() {
if (sHitVar == Integer.MIN_VALUE) {
asm.ldc(0);
sHitVar = e.storeNewLocal(Type.INT_TYPE);
}
}
});
asm.ldc(mid);
switch (samplerKind) {
case Const: {
asm.invokeStatic(
METHOD_COUNTER_CLASS,
"hit", "(I)Z"
);
break;
}
case Adaptive: {
asm.invokeStatic(
METHOD_COUNTER_CLASS,
"hitAdaptive", "(I)Z"
);
break;
}
default:
// do nothing
}
if (sHitVar == Integer.MIN_VALUE) {
sHitVar = e.storeNewLocal(Type.INT_TYPE);
} else {
asm.storeLocal(Type.INT_TYPE, sHitVar);
}
if (skipTarget != null) {
asm.label(skipTarget);
}
}
}
}
private class TimingEntry implements Consumer<TemplateExpanderVisitor> {
@Override
public void consume(final TemplateExpanderVisitor e) {
final Assembler asm = e.asm();
if (entryTsVar == Integer.MIN_VALUE) {
if (durationVar == Integer.MIN_VALUE) {
asm.ldc(0L);
durationVar = e.storeNewLocal(Type.LONG_TYPE);
}
Label skipTarget = addLevelChecks(e, new Runnable() {
@Override
public void run() {
asm.ldc(0L);
entryTsVar = e.storeNewLocal(Type.LONG_TYPE);
}
});
asm.invokeStatic(
"java/lang/System",
"nanoTime", "()J"
);
if (entryTsVar == Integer.MIN_VALUE) {
entryTsVar = e.storeNewLocal(Type.LONG_TYPE);
} else {
asm.storeLocal(Type.LONG_TYPE, entryTsVar);
}
if (skipTarget != null) {
asm.label(skipTarget);
}
}
}
}
private class SamplerTest implements Consumer<TemplateExpanderVisitor> {
@Override
public void consume(TemplateExpanderVisitor e) {
if (sHitVar != Integer.MIN_VALUE) {
elseLabel = new Label();
addLevelChecks(e, samplerLabel);
e.asm()
.loadLocal(Type.INT_TYPE, sHitVar)
.jump(Opcodes.IFEQ, elseLabel);
}
}
}
private class TimingSamplerTest implements Consumer<TemplateExpanderVisitor> {
private final int mid;
public TimingSamplerTest(int mid) {
this.mid = mid;
}
@Override
public void consume(TemplateExpanderVisitor e) {
if (!durationComputed) {
if (entryTsVar != Integer.MIN_VALUE) {
e.asm()
.ldc(mid)
.invokeStatic(
METHOD_COUNTER_CLASS,
"getEndTs", "(I)J")
.loadLocal(Type.LONG_TYPE, entryTsVar)
.sub(Type.LONG_TYPE);
} else {
e.asm().ldc(0L);
}
e.asm().storeLocal(Type.LONG_TYPE, durationVar);
durationComputed = true;
}
}
}
private class TimingTest implements Consumer<TemplateExpanderVisitor> {
@Override
public void consume(TemplateExpanderVisitor e) {
if (!durationComputed) {
addLevelChecks(e, samplerLabel);
if (entryTsVar != Integer.MIN_VALUE) {
e.asm()
.invokeStatic(
"java/lang/System",
"nanoTime", "()J")
.loadLocal(Type.LONG_TYPE, entryTsVar)
.sub(Type.LONG_TYPE);
} else {
e.asm()
.ldc(0L);
}
e.asm().storeLocal(Type.LONG_TYPE, durationVar);
durationComputed = true;
}
}
}
private class Else implements Consumer<TemplateExpanderVisitor> {
@Override
public void consume(TemplateExpanderVisitor e) {
if (elseLabel != null) {
e.asm().label(elseLabel);
elseLabel = null;
}
}
}
private class Exit implements Consumer<TemplateExpanderVisitor> {
private final int mid;
public Exit(int mid) {
this.mid = mid;
}
@Override
public void consume(TemplateExpanderVisitor e) {
if (samplerKind == Sampled.Sampler.Adaptive) {
Label l = new Label();
e.asm()
.loadLocal(Type.INT_TYPE, sHitVar)
.jump(Opcodes.IFEQ, l)
.ldc(mid)
.invokeStatic(
METHOD_COUNTER_CLASS,
"updateEndTs", "(I)V")
.label(l);
}
}
}
private class Duration implements Consumer<TemplateExpanderVisitor> {
@Override
public void consume(TemplateExpanderVisitor e) {
if (!durationComputed) {
e.asm().ldc(0L);
} else {
e.asm().loadLocal(Type.LONG_TYPE, durationVar);
}
}
}
private Label addLevelChecks(TemplateExpanderVisitor e) {
return addLevelChecks(e, null, null);
}
private Label addLevelChecks(TemplateExpanderVisitor e, Runnable initializer) {
return addLevelChecks(e, null, initializer);
}
private Label addLevelChecks(TemplateExpanderVisitor e, Label skip) {
return addLevelChecks(e, skip, null);
}
private Label addLevelChecks(TemplateExpanderVisitor e, Label skip, Runnable initializer) {
Label skipTarget = null;
if (!levelIntervals.isEmpty()) {
Assembler asm = new Assembler(e);
List<Interval> optimized = Interval.invert(levelIntervals);
boolean generateBranch = true;
if (optimized.size() == 1) {
Interval i = optimized.get(0);
if (i.isNone() || (i.getA() == Integer.MIN_VALUE && i.getB() == -1)) {
// level check will always pass
generateBranch = false;
}
}
if (generateBranch) {
if (initializer != null) {
// initialize variables used in the conditional code
initializer.run();
}
skipTarget = skip != null ? skip : new Label();
for(Interval i : optimized) {
Label nextCheck = new Label();
if (globalLevelVar == Integer.MIN_VALUE) {
asm.getStatic(e.getClassName(), BTRACE_LEVEL_FLD, INT_DESC)
.dup();
globalLevelVar = e.storeNewLocal(Type.INT_TYPE);
} else {
asm.loadLocal(Type.INT_TYPE, globalLevelVar);
}
boolean stackConsumed = false;
if (i.getA() > Integer.MIN_VALUE) {
stackConsumed = true;
if (i.getA() == 0) {
asm.jump(Opcodes.IFLT, nextCheck);
} else {
asm.ldc(i.getA())
.jump(Opcodes.IF_ICMPLT, nextCheck);
}
}
if (i.getB() < Integer.MAX_VALUE) {
if (stackConsumed) {
asm.loadLocal(Type.INT_TYPE, globalLevelVar);
}
if (i.getB() == 0) {
asm.jump(Opcodes.IFLE, skipTarget);
} else {
asm.ldc(i.getB())
.jump(Opcodes.IF_ICMPLE, skipTarget);
}
} else {
asm.jump(Opcodes.GOTO, skipTarget);
}
asm.label(nextCheck);
}
}
}
return skipTarget;
}
}