JProfiler帮助文档

嵌入式探针


如果你控制你的探针目标软件组件的源代码,你应该编写一个嵌入式探针而不是注入式探针。

在编写注入式探针时,最初的大部分工作都用于指定被拦截方法和选择作为处理器方法参数的注入对象。 对于嵌入式探针,就不需要指定这些,因为你可以直接从被监控的方法中调用嵌入式探针API。 嵌入式探针的另一个优点是自动部署,探针与你的软件一起发布,并当该应用程序被分析时会出现在JProfiler UI中。 你唯一需要依赖并一起发布的是一个基于Apache 2.0许可授权的小JAR文件,它主要由空的方法体组成,作为分析代理的钩子。

开发环境

开发环境与注入式探针相同,不同之处在于构建名称为jprofiler-probe-embedded , 而不是jprofiler-probe-injected ,并且你要将该JAR文件与应用程序一起发布, 而不是在单独的项目中开发探针。将嵌入式探针添加到软件组件所需的探针API都包含在这一个JAR构建中。 在javadoc中,当你探索API时,请从com.jprofiler.api.probe.embedded 的包概览开始。

就像注入式探针,也有两个嵌入式探针的例子。在api/samples/simple-embedded-probe中, 有一个例子可以让你对写一个嵌入式探针入门。在该目录下执行../gradlew ,编译并运行它, 并研究gradle构建文件build.gradle ,了解执行环境。关于更多功能,包括控制对象, 请到api/samples/advanced-embedded-probe 中的例子。

有效负载探针

与注入式探针类似,你仍然需要一个探针类来进行配置。 根据你的探针是收集负载还是拆分调用树,探针类必须继承 com.jprofiler.api.probe.embedded.PayloadProbecom.jprofiler.api.probe.embedded.SplitProbe。 注入式探针API,对于收集有效负载和拆分你需要在处理器方法上使用不同注解。而嵌入式探针API没有处理器方法, 需要将这种配置转移到探针类本身。

public class FooPayloadProbe extends PayloadProbe {
    @Override
    public String getName() {
        return "Foo queries";
    }

    @Override
    public String getDescription() {
        return "Records foo queries";
    }
}

注入式探针使用注解进行配置,而配置嵌入式探针通过重写探针基类方法来完成。有效负载探针, 唯一的抽象方法是getName() ,其他方法都有一个默认的实现,如果需要的话,你可以重写。 例如,如果你想禁用事件视图以减少开销,你可以重写isEvents() ,返回false

对于收集有效负载并测量其相关的时间,你可以使用一对调用Payload.enter()Payload.exit()

public void measuredCall(String query) {
    Payload.enter(FooPayloadProbe.class);
    try {
        performWork();
    } finally {
        Payload.exit(query);
    }
}

Payload.enter() 调用接受探针类作为参数,这样分析代理知道哪个探针是调用的目标, Payload.exit() 调用会自动关联到同一个探针,并接受有效负载字符串作为参数。 如果缺失退出调用,调用树将被破坏,所以应该始终将退出调用放在try块的finally子句中。

如果测量的代码块不产生任何值,你可以调用Payload.execute 方法来代替, 该方法接受负载字符串和一个Runnable 。在Java 8+中,lambdas或方法引用使得该方法调用非常简洁。

public void measuredCall(String query) {
  Payload.execute(FooPayloadProbe.class, query, this::performWork);
}

在这种情况下,必须事先知道有效负载字符串。还有一个版本的execute ,它可以接受一个Callable

public QueryResult measuredCall(String query) throws Exception {
    return Payload.execute(PayloadProbe.class, query, () -> query.execute());
}

使用Callable 有一个关于签名的问题是,Callable.call() 会抛出一个受检Exception , 所以你必须捕获它或者在包含方法上声明它。

控制对象

有效负载探针可以通过调用Payload 类中的相应方法来打开和关闭控制对象。 通过将它们传递给Payload.enter()Payload.execute() 方法, 将其与探针事件相关联,这两个方法接收一个控制对象和一个自定义事件类型。

public void measuredCall(String query, Connection connection) {
    Payload.enter(FooPayloadProbe.class, connection, MyEventTypes.QUERY);
    try {
        performWork();
    } finally {
        Payload.exit(query);
    }
}

必须在探针配置中明确启用控制对象视图,自定义事件类型也必须在探针类中被注册。

public class FooPayloadProbe extends PayloadProbe {
    @Override
    public String getName() {
        return "Foo queries";
    }

    @Override
    public String getDescription() {
        return "Records foo queries";
    }

    @Override
    public boolean isControlObjects() {
        return true;
    }

    @Override
    public Class<? extends Enum> getCustomTypes() {
        return Connection.class;
    }
}

如果你没有显式打开和关闭你的控制对象,探针类必须重写getControlObjectName , 以便为所有控制对象解析显示名称。

拆分探针

拆分探针基类没有抽象方法,因为它只是用来拆分调用树而不添加探针视图。在这种情况下,最小化探针定义仅如下所示

public class FooSplitProbe extends SplitProbe {}

拆分探针的一个重要配置是是否应该重入。默认情况下,只有顶层调用会被拆分。如果你想把递归调用也拆分, 可以覆盖isReentrant() ,返回true 。 如果你在探针类中重写isPayloads() ,返回true ,那么拆分探针也可以创建探针视图, 并将拆分字符串作为有效负载发布。

要执行拆分,执行调用对Split.enter()Split.exit()

public void splitMethod(String parameter) {
    Split.enter(FooSplitProbe.class, parameter);
    try {
        performWork(parameter);
    } finally {
        Split.exit();
    }
}

与负载收集相反,拆分字符串必须和探针类一起传递给Split.enter() 方法。 同样,可靠地调用Split.exit() 很重要,所以它应该放在 try 块的 finally 子句中。 Split 还提供了execute() 方法, 该方法带有RunnableCallable 参数,只需一次调用就可以执行拆分。

遥测

发布嵌入式探针的遥测特别方便,因为在同一个类路径中,你可以直接访问应用程序中的所有静态方法。和注入式探针类似, 在探针配置类中用@Telemetry 注解静态public方法并返回一个数值。 更多信息请参见探针概念一章。 嵌入式和注入式探针API的@Telemetry 注解是等效的,它们只是在不同的包中。

嵌入式和注入式探针API之间的另一个并行功能是可以用ThreadState 类来修改线程状态。 同样,这个类存在于这两个API不同的包中。

部署

在使用JProfiler UI进行分析时,不需要特殊的步骤来启用嵌入式探针。然而, 只有在第一次调用PayloadSplit 时,探针才会被注册。 只有在这时,才会在JProfiler中创建相关的探针视图。如果你希望这些探针视图从一开始就可见, 就像内置和注入式探针,对于

有效负载探针你可以调用

PayloadProbe.register(FooPayloadProbe.class);

对于拆分探针你可以调用

SplitProbe.register(FooSplitProbe.class);

你可能会考虑是否可以有条件地调用PayloadSplit的方法, 比如或许用命令行开关来控制,以便将开销降到最低。然而,这一般是不必要的,因为方法体是空的。 在不Attach分析代理的情况下,除了构造有效载荷字符串外,不会产生任何开销。 考虑到探针事件不应该在微观尺度上生成,它们将相对较少被创建,因此构建有效载荷字符串工作的开销应该是微乎其微。

关于容器的另一个问题是可能你不想在类路径上暴露外部依赖关系。你的容器的用户可能也会使用嵌入式探针API,这将导致冲突。 在这种情况下,你可以将嵌入式探针API隐藏到你自己的包中。JProfiler仍然会识别隐藏的包,并正确地检测API类。 如果无法在构建时隐藏,你可以提取源代码包到你自己的项目中。