JProfiler帮助文档Download

嵌入式探针


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

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

开发环境

开发环境与注入式探针相同,不同之处在于工件名称是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+,lambda或方法引用使得此方法调用非常简洁。

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还提供了带有RunnableCallable参数的execute()方法,可以通过一次调用执行拆分。

遥测

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

嵌入式和注入式探针API之间的另一个并行功能是使用ThreadState类修改线程状态的能力。同样,该类在两个API中都存在,只是包不同。

部署

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

PayloadProbe.register(FooPayloadProbe.class);

对于有效负载探针和

SplitProbe.register(FooSplitProbe.class);

对于拆分探针。

您可能会考虑是否有条件地调用PayloadSplit的方法,可能由命令行开关控制以最小化开销。然而,这通常不是必需的,因为方法体是空的。没有附加分析代理,除了构建有效负载字符串之外,不会产生任何开销。考虑到探针事件不应在微观尺度上生成,它们将相对较少地创建,因此构建有效负载字符串应该是一个相对不显著的工作。

对于容器的另一个担忧可能是您不希望在类路径上暴露外部依赖项。您的容器用户也可以使用嵌入式探针API,这将导致冲突。在这种情况下,您可以将嵌入式探针API遮蔽到您自己的包中。JProfiler仍然会识别遮蔽的包并正确地对API类进行检测。如果构建时遮蔽不切实际,您可以提取源代码存档并将类作为项目的一部分。