JProfiler 도움말Download

Injected Probes


스크립트 프로브와 유사하게, 인젝션된 프로브는 선택된 메서드에 대한 인터셉션 핸들러를 정의합니다. 그러나, 인젝션된 프로브는 JProfiler GUI 외부의 IDE에서 개발되며, JProfiler에서 제공하는 인젝션된 프로브 API에 의존합니다. 이 API는 Apache License, version 2.0 하에 허가되어 있어 관련 아티팩트를 쉽게 배포할 수 있습니다.

인젝션된 프로브를 시작하는 가장 좋은 방법은 JProfiler 설치의 api/samples/simple-injected-probe 디렉토리에 있는 예제를 공부하는 것입니다. 해당 디렉토리에서 ../gradlew를 실행하여 컴파일하고 실행하세요. Gradle 빌드 파일 build.gradle에는 샘플에 대한 추가 정보가 포함되어 있습니다. api/samples/advanced-injected-probe에 있는 예제는 제어 객체를 포함한 프로브 시스템의 더 많은 기능을 보여줍니다.

Development environment

인젝션된 프로브를 개발하기 위해 필요한 프로브 API는 다음과 같은 Maven 좌표를 가진 단일 아티팩트에 포함되어 있습니다.

group: com.jprofiler
artifact: jprofiler-probe-injected
version: <JProfiler version>

이 매뉴얼에 해당하는 JProfiler 버전은 15.0입니다.

Jar, 소스 및 javadoc 아티팩트는 다음 저장소에 게시됩니다.

https://maven.ej-technologies.com/repository

Gradle 또는 Maven과 같은 빌드 도구를 사용하여 개발 클래스 경로에 프로브 API를 추가하거나 JAR 파일을 사용할 수 있습니다.

api/jprofiler-probe-injected.jar

JProfiler 설치에서.

Javadoc을 보려면 다음으로 이동하세요.

api/javadoc/index.html

해당 javadoc은 JProfiler에서 게시한 모든 API의 javadoc을 결합한 것입니다. com.jprofiler.api.probe.injected 패키지의 개요는 API를 탐색하기 위한 좋은 출발점입니다.

Probe structure

인젝션된 프로브는 com.jprofiler.api.probe.injected.Probe로 주석이 달린 클래스입니다. 해당 주석의 속성은 전체 프로브에 대한 구성 옵션을 노출합니다. 예를 들어, 개별 검사가 필요 없는 많은 프로브 이벤트를 생성하는 경우, events 속성을 사용하여 프로브 이벤트 뷰를 비활성화하고 오버헤드를 줄일 수 있습니다.

@Probe(name = "Foo", description = "Shows foo server requests", events = "false")
public class FooProbe {
    ...
}

프로브 클래스에는 인터셉션 핸들러를 정의하기 위해 특별히 주석이 달린 정적 메서드를 추가합니다. PayloadInterception 주석은 페이로드를 생성하고 SplitInterception 주석은 호출 트리를 분할합니다. 핸들러의 반환 값은 주석에 따라 페이로드 또는 분할 문자열로 사용됩니다. 스크립트 프로브와 마찬가지로 null을 반환하면 인터셉션에 아무런 효과가 없습니다. 타이밍 정보는 인터셉션된 메서드에 대해 자동으로 계산됩니다.

@Probe(name = "FooBar")
public class FooProbe {
    @PayloadInterception(
        invokeOn = InvocationType.ENTER,
        method = @MethodSpec(className = "com.bar.Database",
                             methodName = "processQuery",
                             parameterTypes = {"com.bar.Query"},
                             returnType = "void"))
    public static String fooRequest(@Parameter(0) Query query) {
        return query.getVerbose();
    }

    @SplitInterception(
        method = @MethodSpec(className = "com.foo.Server",
                             methodName = "handleRequest",
                             parameterTypes = {"com.foo.Request"},
                             returnType = "void"))
    public static String barQuery(@Parameter(0) Request request) {
        return request.getPath();
    }
}

위의 예에서 볼 수 있듯이, 두 주석 모두 MethodSpec을 사용하여 인터셉션된 메서드를 정의하는 method 속성을 가지고 있습니다. 스크립트 프로브와 달리, MethodSpec은 클래스 이름이 비어 있을 수 있으며, 특정 서명을 가진 모든 메서드가 클래스 이름에 상관없이 인터셉션됩니다. 또는 MethodSpecsubtypes 속성을 사용하여 전체 클래스 계층 구조를 인터셉션할 수 있습니다.

스크립트 프로브와 달리 모든 매개변수가 자동으로 사용 가능한 경우, 핸들러 메서드는 관심 있는 값을 요청하기 위해 매개변수를 선언합니다. 각 매개변수는 com.jprofiler.api.probe.injected.parameter 패키지의 주석으로 주석이 달려 있어 프로파일링 에이전트가 메서드에 전달해야 할 객체 또는 기본 값을 알 수 있습니다. 예를 들어, 핸들러 메서드의 매개변수에 @Parameter(0)를 주석으로 달면 인터셉션된 메서드의 첫 번째 매개변수가 주입됩니다.

인터셉션된 메서드의 메서드 매개변수는 모든 인터셉션 유형에 대해 사용 가능합니다. 페이로드 인터셉션은 @ReturnValue 또는 @ExceptionValue를 사용하여 반환 값을 액세스할 수 있으며, 메서드의 출구를 인터셉션하도록 프로파일링 에이전트에 지시한 경우 예외를 던질 수 있습니다. 이는 PayloadInterception 주석의 invokeOn 속성을 사용하여 수행됩니다.

스크립트 프로브와 달리, 인젝션된 프로브 핸들러는 인터셉션된 메서드의 재귀 호출에 대해 호출될 수 있으며, 인터셉션 주석의 reentrant 속성을 true로 설정한 경우에만 가능합니다. 핸들러 메서드에 ProbeContext 유형의 매개변수를 사용하여 ProbeContext.getOuterPayload() 또는 ProbeContext.restartTiming()을 호출하여 중첩 호출에 대한 프로브의 동작을 제어할 수 있습니다.

Advanced interceptions

때로는 단일 인터셉션으로 프로브 문자열을 작성하는 데 필요한 모든 정보를 수집하기에 충분하지 않을 수 있습니다. 이를 위해, 프로브는 페이로드나 분할을 생성하지 않는 Interception으로 주석이 달린 임의의 수의 인터셉션 핸들러를 포함할 수 있습니다. 정보는 프로브 클래스의 정적 필드에 저장할 수 있습니다. 멀티스레드 환경에서 스레드 안전성을 위해, 참조 유형을 저장하기 위해 ThreadLocal 인스턴스를 사용하고, 카운터를 유지하기 위해 java.util.concurrent.atomic 패키지의 원자 숫자 유형을 사용해야 합니다.

특정 상황에서는 메서드 진입과 메서드 종료 모두에 대한 인터셉션이 필요할 수 있습니다. 일반적인 경우는 inMethodCall과 같은 상태 변수를 유지하여 다른 인터셉션의 동작을 수정하는 경우입니다. 기본 인터셉션 유형인 진입 인터셉션에서 inMethodCalltrue로 설정할 수 있습니다. 이제 해당 인터셉션 바로 아래에 정적 메서드를 정의하고 @AdditionalInterception(invokeOn = InvocationType.EXIT)으로 주석을 달아야 합니다. 인터셉션 핸들러 위에서 가져온 인터셉션된 메서드이므로 다시 지정할 필요가 없습니다. 메서드 본문에서 inMethodCall 변수를 false로 설정할 수 있습니다.

...

private static final ThreadLocal<Boolean> inMethodCall =
    ThreadLocal.withInitial(() -> Boolean.FALSE);

@Interception(
    invokeOn = InvocationType.ENTER,
    method = @MethodSpec(className = "com.foo.Server",
                         methodName = "internalCall",
                         parameterTypes = {"com.foo.Request"},
                         returnType = "void"))
public static void guardEnter() {
    inMethodCall.set(Boolean.TRUE);
}

@AdditionalInterception(InvocationType.EXIT)
public static void guardExit() {
    inMethodCall.set(Boolean.FALSE);
}

@SplitInterception(
      method = @MethodSpec(className = "com.foo.Server",
                           methodName = "handleRequest",
                           parameterTypes = {"com.foo.Request"},
                           returnType = "void"),
      reentrant = true)
public static String splitRequest(@Parameter(0) Request request) {
    if (!inMethodCall.get()) {
        return request.getPath();
    } else {
        return null;
    }
}

...

이 사용 사례의 작동 예제는 api/samples/advanced-injected-probe/src/main/java/AdvancedAwtEventProbe.java에서 볼 수 있습니다.

Control objects

Probe 주석의 controlObjects 속성이 true로 설정되지 않으면 제어 객체 뷰는 보이지 않습니다. 제어 객체를 사용하려면 핸들러 메서드에 해당 유형의 매개변수를 선언하여 ProbeContext를 얻어야 합니다. 아래 샘플 코드는 제어 객체를 열고 프로브 이벤트와 연결하는 방법을 보여줍니다.

@Probe(name = "Foo", controlObjects = true, customTypes = MyEventTypes.class)
public class FooProbe {
    @Interception(
            invokeOn = InvocationType.EXIT,
            method = @MethodSpec(className = "com.foo.ConnectionPool",
                         methodName = "createConnection",
                         parameterTypes = {},
                         returnType = "com.foo.Connection"))
    public static void openConnection(ProbeContext pc, @ReturnValue Connection c) {
        pc.openControlObject(c, c.getId());
    }

    @PayloadInterception(
            invokeOn = InvocationType.EXIT,
            method = @MethodSpec(className = "com.foo.ConnectionPool",
                         methodName = "createConnection",
                         parameterTypes = {"com.foo.Query", "com.foo.Connection"},
                         returnType = "com.foo.Connection"))
    public static Payload handleQuery(
        ProbeContext pc, @Parameter(0) Query query, @Parameter(1) Connection c) {
        return pc.createPayload(query.getVerbose(), c, MyEventTypes.QUERY);
    }

    ...

}

제어 객체는 정의된 수명을 가지며, 프로브 뷰는 타임라인과 제어 객체 뷰에서 열고 닫는 시간을 기록합니다. 가능하면 ProbeContext.openControlObject()ProbeContext.closeControlObject()를 호출하여 제어 객체를 명시적으로 열고 닫아야 합니다. 그렇지 않으면 제어 객체의 표시 이름을 해석하는 @ControlObjectName으로 주석이 달린 정적 메서드를 선언해야 합니다.

프로브 이벤트는 핸들러 메서드가 String 대신 Payload 인스턴스를 반환하는 경우 제어 객체와 연결될 수 있습니다. ProbeContext.createPayload() 메서드는 제어 객체와 프로브 이벤트 유형을 받습니다. 허용된 이벤트 유형을 가진 열거형은 Probe 주석의 customTypes 속성에 등록되어야 합니다.

제어 객체는 시간 측정의 시작 시점에 지정되어야 하며, 이는 메서드 진입에 해당합니다. 경우에 따라 페이로드 문자열의 이름은 반환 값이나 다른 인터셉션에 따라 달라지기 때문에 메서드 종료 시에만 사용할 수 있습니다. 이 경우 ProbeContext.createPayloadWithDeferredName()을 사용하여 이름 없이 페이로드 객체를 생성할 수 있습니다. @AdditionalInterception(invokeOn = InvocationType.EXIT)으로 주석이 달린 인터셉션 핸들러를 바로 아래에 정의하고 해당 메서드에서 String을 반환하면 자동으로 페이로드 문자열로 사용됩니다.

Overriding the thread state

데이터베이스 드라이버 또는 외부 리소스에 대한 네이티브 커넥터의 실행 시간을 측정할 때, JProfiler에 일부 메서드를 다른 스레드 상태로 설정하도록 지시해야 할 때가 있습니다. 예를 들어, 데이터베이스 호출을 "Net I/O" 스레드 상태에 두는 것이 유용합니다. 통신 메커니즘이 표준 Java I/O 기능을 사용하지 않고 네이티브 메커니즘을 사용하는 경우, 이는 자동으로 적용되지 않습니다.

ThreadState.NETIO.enter()ThreadState.exit() 호출 쌍을 사용하여 프로파일링 에이전트는 스레드 상태를 적절히 조정합니다.

...

@Interception(invokeOn = InvocationType.ENTER, method = ...)
public static void enterMethod(ProbeContext probeContext, @ThisValue JComponent component) {
    ThreadState.NETIO.enter();
}

@AdditionalInterception(InvocationType.EXIT)
public static void exitMethod() {
    ThreadState.exit();
}

...

Deployment

인젝션된 프로브를 배포하는 방법에는 클래스패스에 넣을지 여부에 따라 두 가지 방법이 있습니다. VM 매개변수

-Djprofiler.probeClassPath=...

별도의 프로브 클래스 경로가 프로파일링 에이전트에 의해 설정됩니다. 프로브 클래스패스는 디렉토리와 클래스 파일을 포함할 수 있으며, Windows에서는 ';'로, 다른 플랫폼에서는 ':'로 구분됩니다. 프로파일링 에이전트는 프로브 클래스패스를 스캔하여 모든 프로브 정의를 찾습니다.

프로브 클래스를 클래스패스에 배치하는 것이 더 쉬운 경우, VM 매개변수를 설정할 수 있습니다.

-Djprofiler.customProbes=...

쉼표로 구분된 완전한 클래스 이름 목록으로. 이러한 클래스 이름 각각에 대해, 프로파일링 에이전트는 인젝션된 프로브를 로드하려고 시도합니다.