JProfiler 도움말

Injected 프로브

script 프로브와 유사하게, injected 프로브는 선택된 메소드에 대한 인터셉션 핸들러를 정의합니다. 하지만, injected 프로브는 JProfiler GUI 외부에서 IDE를 통해 개발되며, JProfiler에서 제공하는 injected 프로브 API에 의존합니다. 이 API는 Apache License 2.0의 관대한 라이선스 하에 제공되어 관련 아티팩트의 배포가 용이합니다.

injected 프로브를 시작하는 가장 좋은 방법은 JProfiler 설치 디렉터리의 api/samples/simple-injected-probe 예제를 참고하는 것입니다. 해당 디렉터리에서 ../gradlew를 실행하여 컴파일 및 실행할 수 있습니다. gradle 빌드 파일 build.gradle에는 샘플에 대한 추가 정보가 포함되어 있습니다. 또한 api/samples/advanced-injected-probe의 예제에서는 제어 객체 등 프로브 시스템의 더 다양한 기능을 보여줍니다.

개발 환경

injected 프로브 개발에 필요한 프로브 API는 다음 maven 좌표를 가진 단일 아티팩트에 포함되어 있습니다.

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

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

Jar, 소스, javadoc 아티팩트는 Maven Central 저장소에 배포되어 있습니다. Gradle이나 Maven과 같은 빌드 도구로 개발 클래스패스에 프로브 API를 추가하거나, JAR 파일

api/jprofiler-probe-injected.jar 

을 JProfiler 설치 디렉터리에서 사용할 수 있습니다.

overview에서 com.jprofiler.api.probe. 패키지의 API를 탐색하는 데 좋은 출발점이 될 수 있습니다.

프로브 구조

injected 프로브는 com.jprofiler.api.probe.injected.Probe 어노테이션이 붙은 클래스입니다. 해당 어노테이션의 속성들은 전체 프로브에 대한 설정 옵션을 제공합니다. 예를 들어, 개별적으로 확인할 필요가 없는 많은 프로브 이벤트를 생성하는 경우, events 속성을 통해 프로브 이벤트 뷰를 비활성화하여 오버헤드를 줄일 수 있습니다.

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

프로브 클래스에는 인터셉션 핸들러를 정의하기 위해 특별한 어노테이션이 붙은 static 메소드를 추가합니다. PayloadInterception 어노테이션은 페이로드를 생성하고, SplitInterception 어노테이션은 호출 트리를 분할합니다. 핸들러의 반환값은 어노테이션에 따라 페이로드 또는 분할 문자열로 사용됩니다. script 프로브와 마찬가지로 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(); 
    } 
} 

위 예제에서 볼 수 있듯이, 두 어노테이션 모두 method 속성을 통해 MethodSpec으로 인터셉트할 메소드를 정의합니다. script 프로브와 달리, MethodSpec의 클래스 이름을 비워두면 특정 시그니처를 가진 모든 메소드가 클래스 이름과 상관없이 인터셉트됩니다. 또는 MethodSpecsubtypes 속성을 사용하여 전체 클래스 계층을 인터셉트할 수도 있습니다.

script 프로브에서는 모든 파라미터가 자동으로 제공되지만, injected 프로브의 핸들러 메소드에서는 필요한 값만 파라미터로 선언합니다. 각 파라미터는 com.jprofiler.api.probe.injected.parameter 패키지의 어노테이션으로 지정되어, 프로파일링 에이전트가 어떤 객체 또는 기본값을 메소드에 전달해야 하는지 알 수 있습니다. 예를 들어, 핸들러 메소드의 파라미터에 @Parameter(0)을 지정하면 인터셉트된 메소드의 첫 번째 파라미터가 주입됩니다.

인터셉트된 메소드의 파라미터는 모든 인터셉션 타입에서 사용할 수 있습니다. 페이로드 인터셉션에서는 @ReturnValue로 반환값을, @ExceptionValue로 예외를 접근할 수 있습니다. 이 경우, 메소드의 entry가 아닌 exit에서 인터셉트하도록 프로파일링 에이전트에 알려야 하며, PayloadInterception 어노테이션의 invokeOn 속성으로 지정합니다.

script 프로브와 달리, injected 프로브의 핸들러는 인터셉트된 메소드의 재귀 호출에도 호출될 수 있습니다. 이를 위해 인터셉션 어노테이션의 reentrant 속성을 true로 설정하면 됩니다. 핸들러 메소드에 ProbeContext 타입의 파라미터를 선언하면, ProbeContext.getOuterPayload() 또는 ProbeContext.restartTiming()을 호출하여 중첩 호출에 대한 프로브 동작을 제어할 수 있습니다.

고급 인터셉션

때로는 하나의 인터셉션만으로는 프로브 문자열을 구성하는 데 필요한 모든 정보를 수집할 수 없습니다. 이럴 때는 페이로드나 분할을 생성하지 않는 Interception 어노테이션이 붙은 여러 개의 인터셉션 핸들러를 프로브에 포함할 수 있습니다. 정보는 프로브 클래스의 static 필드에 저장할 수 있습니다. 멀티스레드 환경에서의 스레드 안전성을 위해 참조 타입은 ThreadLocal 인스턴스를, 카운터 유지에는 java.util.concurrent.atomic 패키지의 atomic 숫자 타입을 사용하는 것이 좋습니다.

경우에 따라 메소드 entry와 exit 모두에서 인터셉션이 필요할 수 있습니다. 예를 들어, inMethodCall과 같은 상태 변수를 유지하여 다른 인터셉션의 동작을 제어하는 경우가 있습니다. entry 인터셉션(기본값)에서 inMethodCalltrue로 설정하고, 그 아래에 @AdditionalInterception(invokeOn = InvocationType.EXIT) 어노테이션이 붙은 또 다른 static 메소드를 정의하여 exit 시 inMethodCallfalse로 설정할 수 있습니다. 이때 인터셉트할 메소드는 위의 인터셉션 핸들러에서 가져오므로 다시 지정할 필요가 없습니다.

... 

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에서 확인할 수 있습니다.

제어 객체

제어 객체 뷰는 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); 
    } 

    ... 

} 

제어 객체는 명확한 생명주기를 가지며, 프로브 뷰에서는 타임라인과 제어 객체 뷰에 open/close 시점을 기록합니다. 가능하다면 ProbeContext.openControlObject()ProbeContext.closeControlObject()를 호출하여 명시적으로 제어 객체를 열고 닫아야 합니다. 그렇지 않은 경우, @ControlObjectName 어노테이션이 붙은 static 메소드를 선언하여 제어 객체의 표시 이름을 반환하도록 해야 합니다.

핸들러 메소드가 String 대신 Payload 인스턴스를 반환하면 프로브 이벤트를 제어 객체와 연결할 수 있습니다. ProbeContext.createPayload() 메소드는 제어 객체와 프로브 이벤트 타입을 인자로 받습니다. 허용되는 이벤트 타입이 정의된 enum은 Probe 어노테이션의 customTypes 속성에 등록해야 합니다.

제어 객체는 시간 측정의 시작, 즉 메소드 entry에서 지정해야 합니다. 경우에 따라 페이로드 문자열의 이름이 반환값이나 다른 인터셉션에 따라 메소드 exit에서만 결정될 수 있습니다. 이럴 때는 ProbeContext.createPayloadWithDeferredName()을 사용하여 이름 없는 페이로드 객체를 생성할 수 있습니다. 그 아래에 @AdditionalInterception(invokeOn = InvocationType.EXIT) 어노테이션이 붙은 인터셉션 핸들러를 정의하고, 해당 메소드에서 String을 반환하면 자동으로 페이로드 문자열로 사용됩니다.

스레드 상태 오버라이드

데이터베이스 드라이버나 외부 리소스에 대한 네이티브 커넥터의 실행 시간을 측정할 때, 특정 메소드를 다른 스레드 상태로 지정해야 할 필요가 있습니다. 예를 들어, 데이터베이스 호출을 "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(); 
} 

... 

배포

injected 프로브를 배포하는 방법은 두 가지가 있으며, 클래스패스에 올릴지 여부에 따라 다릅니다. VM 파라미터

-Djprofiler.probeClassPath=... 

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

프로브 클래스를 클래스패스에 두는 것이 더 쉽다면, VM 파라미터

-Djprofiler.customProbes=... 
를 완전한 클래스 이름의 콤마(,)로 구분된 목록으로 지정하면 됩니다. 각 클래스 이름에 대해 프로파일링 에이전트가 injected 프로브를 로드하려고 시도합니다.