Microsoft MVP성태의 닷넷 이야기
.NET Framework: 475. ETW(Event Tracing for Windows)를 C#에서 사용하는 방법 [링크 복사], [링크+제목 복사]
조회: 20910
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 5개 있습니다.)
(시리즈 글이 11개 있습니다.)
.NET Framework: 475. ETW(Event Tracing for Windows)를 C#에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1804

.NET Framework: 483. 코드로 살펴 보는 ETW의 활성화 시점
; https://www.sysnet.pe.kr/2/0/1815

.NET Framework: 915. ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용
; https://www.sysnet.pe.kr/2/0/12244

.NET Framework: 923. C# - ETW(Event Tracing for Windows)를 이용한 Finalizer 실행 감시
; https://www.sysnet.pe.kr/2/0/12255

.NET Framework: 932. C# - ETW 관련 Win32 API 사용 예제 코드 (1)
; https://www.sysnet.pe.kr/2/0/12292

.NET Framework: 933. C# - ETW 관련 Win32 API 사용 예제 코드 (2) NT Kernel Logger
; https://www.sysnet.pe.kr/2/0/12296

.NET Framework: 934. C# - ETW 관련 Win32 API 사용 예제 코드 (3) ETW Consumer 구현
; https://www.sysnet.pe.kr/2/0/12299

.NET Framework: 935. C# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer
; https://www.sysnet.pe.kr/2/0/12300

.NET Framework: 936. C# - ETW 관련 Win32 API 사용 예제 코드 (5) - Private Logger
; https://www.sysnet.pe.kr/2/0/12302

개발 환경 구성: 504. ETW - 닷넷 프레임워크 기반의 응용 프로그램을 위한 명령행 도구 etrace 소개
; https://www.sysnet.pe.kr/2/0/12303

.NET Framework: 994. C# - (.NET Core 2.2부터 가능한) 프로세스 내부에서 CLR ETW 이벤트 수신
; https://www.sysnet.pe.kr/2/0/12474




ETW(Event Tracing for Windows)를 C#에서 사용하는 방법

윈도우 환경에서 사용할 수 있는 고속 로깅 방법이 있습니다.

Application Analysis with Event Tracing for Windows (ETW)
; http://www.codeproject.com/Articles/570690/Application-Analysis-with-Event-Tracing-for-Window

오늘은 위의 글을 따라하면서 ETW를 C#에서 어떻게 사용할 수 있는지 간략하게 정리해 보겠습니다. ^^

우선, ETW 이벤트를 생산하는 측(Producer)을 Provider라 하고, ETW 이벤트를 사용하는 측(Consumer)과 해당 공급자의 로깅 기능을 on/off하라는 지시를 내리는 Controller가 있습니다. 여기서 Provider 작성은 2가지로 나뉘는데,

- Classic provider: XP 윈도우도 지원

- Manifest-based provider: 비스타 이상에서만 지원
    Writing Manifest-based Events
    ; https://learn.microsoft.com/en-us/windows/win32/etw/writing-manifest-based-events

이 글에서는 비스타 이상에서만 가능한 매니페스트 기반의 제공자(provider) 유형으로 ETW를 사용해 볼텐데, 그 전에 준비물은 다음과 같습니다.

  • Visual Studio 2012 이상 (이 글에서는 2013으로 실습)
  • Windows SDK 8.0 이상 (해당 SDK 내에 있는 Windows Performance Toolkit 설치)

자, 그럼 시작해 볼까요? ^^




우선, 우리가 실습할 ETW 이벤트 유형은 단순히 메서드의 수행 시간을 남기는 것으로 하겠습니다. 대략 로그에 남길 속성을 정의하면 이 정도입니다.

  • [string] Context
  • [string] MethodName
  • [long] ExecutionTime
  • [string] ParameterInfo

물론, 여러분 나름대로 더 정의해서 써도 됩니다.

로그를 남길 사양을 준비했으니, 이를 명시해 주는 "Manifest" 파일을 작성해야 합니다. 일반적으로 확장자가 man으로 지정하는 XML 유형의 파일인데, 이를 손쉽게 GUI 상에서 만들어 줄 수 있는 도구가 "Windows Performance Toolkit"에 ecmangen.exe(Instrumentation manifest generation tool) 파일로 포함되어 있습니다.

C:\Program Files (x86)\Windows Kits\8.1\bin\x64\ecmangen.exe
C:\Program Files (x86)\Windows Kits\8.1\bin\x86\ecmangen.exe

ecmangen 프로그램으로 만들어진 manifest 파일을 한번 보면 좀 더 이해가 빠를 수 있는데요. "Application Analysis with Event Tracing for Windows (ETW)" 글의 소스코드를 다운로드 받고 "\appanalysis_src\appanalysis_src\Common" 폴더에 보면 "FirstETW.man" 파일이 있으므로 이것을 ecmangen.exe에 로드해서 살펴보면 됩니다.

ecmangen 프로그램에서 manifest 파일을 만드는 좀 더 자세한 설명은 그 프로그램의 "Help" / "Help Topic" 메뉴를 선택하면 "C:\Users\[사용자 계정]\AppData\Local\Temp\ECMangenReference.mht" 파일이 생성과 함께 열리면서 대략적인 실습 시나리오를 위주로 "Quick Start Guide"로 설명하고 있으니 이를 참고하시면 됩니다.

간단하게 우리가 원하는 메서드의 수행 시간을 로그로 남기는 manifest를 정의해 보면, "Events Section" 노드를 마우스 우측 버튼을 눌러 "New" / "Provider"를 선택해 다음의 정보를 입력합니다.

Name: MyETWSampleProvider

Symbol: Sample_Perf  (여기 입력한 이름은 나중에 C# 클래스 명으로 사용됨)

GUID: [이 예제에서는 {223f223d-b390-4126-a1c8-3926d1e5b891} 사용]

Decoding file locations:
    Resources: sample.exe
    Messages: sample.exe

저장하면 다음과 같이 Provider노드가 생성되고,

etw_sample_1.png

생성된 "MyETWSampleProvider" 노드를 다시 마우스 우측 버튼을 눌러 "New" / "Template"을 선택해 다음의 정보를 입력하고 저장합니다.

Name: Template_MethodPerfTrace

Field attributes:
    Name: Context
    InType: win:UnicodeString
    OutType: xs:string

    Name: MethodName
    InType: win:UnicodeString
    OutType: xs:string

    Name: ExecutionTime
    InType: win:UInt64
    OutType: xs:unsignedLong

    Name: ParameterInfo
    InType: win:UnicodeString
    OutType: xs:string

템플릿을 만들었으니, 이에 기반한 이벤트를 만드는데 역시 "MyETWSampleProvider" 노드를 마우스 우측 버튼을 눌러 "New" / "Event"를 선택해 다음의 정보를 입력하고 저장합니다.

Symbol: Event_MethodPerfTrace

Event ID: (임의의 값: 여기서는 1을 입력)

Channel: [default 또는 적정한 값 선택, 예제에서는 default]

Level: [default 또는 적정한 값 선택, 예제에서는 Informational]

Task: [default 또는 적정한 값 선택, 예제에서는 default]

Opcode: [default 또는 적정한 값 선택, 예제에서는 default]

Template: [Provider에 포함된 Template 값, 여기서는 이전에 생성해 둔 Template_MethodPerfTrace 선택]

manifest에 정의될 수 있는 값들의 관계를 간단하게 정리해 보면,

  • Provider 1개당 n개의 Template, Event 가능
  • Event는 1:1로 Template과 매칭
  • Event는 Template에 대한 매칭 뿐만 아니라, Channel, Level, Keywords, Opcodes를 할당할 수 있는데 그 정보들 역시 Provider에서 사용자 정의 가능

여기서는 실습을 간단히 하기 위해 Channel, Level, Keywords, Opcodes는 생략하고 최소 정보인 Template과 Event만 정의해 본 것입니다.

여기까지 실습하고 etw_sample.man 파일로 저장하면 다음의 내용이 포함됩니다.

<?xml version="1.0" encoding="UTF-16"?>
<instrumentationManifest xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events eventman.xsd" xmlns="http://schemas.microsoft.com/win/2004/08/events" xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:trace="http://schemas.microsoft.com/win/2004/08/events/trace">
    <instrumentation>
        <events>
            <provider name="MyETWSampleProvider" guid="{223F223D-B390-4126-A1C8-3926D1E5B891}" symbol="Sample_Perf" resourceFileName="sample.exe" messageFileName="sample.exe">
                <events>
                    <event symbol="Event_MethodPerfTrace" value="1" version="0" level="win:Informational" template="Template_MethodPerfTrace">
                    </event>
                </events>
                <levels>
                </levels>
                <templates>
                    <template tid="Template_MethodPerfTrace">
                        <data name="Context" inType="win:UnicodeString" outType="xs:string">
                        </data>
                        <data name="MethodName" inType="win:UnicodeString" outType="xs:string">
                        </data>
                        <data name="ExecutionTime" inType="win:UInt64" outType="xs:unsignedLong">
                        </data>
                        <data name="ParameterInfo" inType="win:UnicodeString" outType="xs:string">
                        </data>
                    </template>
                </templates>
            </provider>
        </events>
    </instrumentation>
    <localization>
        <resources culture="en-US">
            <stringTable>
                <string id="level.Informational" value="Information">
                </string>
            </stringTable>
        </resources>
    </localization>
</instrumentationManifest>

생성된 manifest 파일은 "Message Compiler"를 통해 컴파일할 수 있습니다.

mc etw_sample.man

위와 같이 실행하면, C/C++ 용의 헤더 파일과 함께 리소스 파일들이 생성됩니다.

etw_sample.h
etw_sample.rc
etw_sampleTEMP.BIN
MSG00001.bin

리소스 파일인 etw_sample.rc 파일은 내부적으로 etw_sampleTEMP.BIN, MSG00001.bin 파일을 포함합니다.

LANGUAGE 0x9,0x1
1 11 "MSG00001.bin"
1 WEVT_TEMPLATE "etw_sampleTEMP.BIN"

따라서, Resource Compiler를 이용해서 etw_sample.rc 파일을 컴파일하면 etw_sampleTEMP.BIN, MSG00001.bin 파일을 포함하는 바이너리 유형의 res 리소스 파일(여기서는 etw_sample.res)이 생성됩니다.

rc etw_sample.rc

하지만, 우리는 C/C++이 아닌 C#으로 실습할 것이기 때문에 "Message Compiler"에 "-css" 옵션을 추가해 C# 네임스페이스를 지정해 컴파일해 줍니다.

mc -css MyETWSampleProvider.PerfTrace etw_sample.man

그럼, etw_sample.h 파일 대신 etw_sample.cs 파일이 생성된다는 차이만 있을 뿐 그 외의 파일은 "-css" 옵션이 없을 때와 동일하게 생성됩니다.

etw_sample.cs
etw_sample.rc
etw_sampleTEMP.BIN
MSG00001.bin

생성된 cs 파일에는 친절하게도 ETW 로그를 남길 수 있는 public 접근 권한의 static 메서드를 포함하고 있습니다.

...[주석 영역 생략]...

namespace MyETWSampleProvider.PerfTrace
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Diagnostics.Eventing;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Security.Principal;

    public static class Sample_Perf
    {
        //
        // Provider MyETWSampleProvider Event Count 1
        //

        internal static EventProviderVersionTwo m_provider = new EventProviderVersionTwo(new Guid("223f223d-b390-4126-a1c8-3926d1e5b891"));
        //
        // Task :  eventGUIDs
        //

        //
        // Event Descriptors
        //
        private static EventDescriptor Event_MethodPerfTrace;


        static Sample_Perf()
        {
            unchecked
            {
                Event_MethodPerfTrace = new EventDescriptor(0x1, 0x0, 0x0, 0x4, 0x0, 0x0, (long)0x0);
            }
        }


        //
        // Event method for Event_MethodPerfTrace
        //
        public static bool EventWriteEvent_MethodPerfTrace(string Context, string MethodName, ulong ExecutionTime, string ParameterInfo)
        {

            if (!m_provider.IsEnabled())
            {
                return true;
            }

            return m_provider.TemplateTemplate_MethodPerfTrace(ref Event_MethodPerfTrace, Context, MethodName, ExecutionTime, ParameterInfo);
        }
    }

    internal class EventProviderVersionTwo : EventProvider
    {
         internal EventProviderVersionTwo(Guid id)
                : base(id)
         {}


        [StructLayout(LayoutKind.Explicit, Size = 16)]
        private struct EventData
        {
            [FieldOffset(0)]
            internal UInt64 DataPointer;
            [FieldOffset(8)]
            internal uint Size;
            [FieldOffset(12)]
            internal int Reserved;
        }



        internal unsafe bool TemplateTemplate_MethodPerfTrace(
            ref EventDescriptor eventDescriptor,
            string Context,
            string MethodName,
            ulong ExecutionTime,
            string ParameterInfo
            )
        {
            int argumentCount = 4;
            bool status = true;

            if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords))
            {
                byte* userData = stackalloc byte[sizeof(EventData) * argumentCount];
                EventData* userDataPtr = (EventData*)userData;

                userDataPtr[0].Size = (uint)(Context.Length + 1)*sizeof(char);

                userDataPtr[1].Size = (uint)(MethodName.Length + 1)*sizeof(char);

                userDataPtr[2].DataPointer = (UInt64)(&ExecutionTime);
                userDataPtr[2].Size = (uint)(sizeof(long)  );

                userDataPtr[3].Size = (uint)(ParameterInfo.Length + 1)*sizeof(char);

                fixed (char* a0 = Context, a1 = MethodName, a2 = ParameterInfo)
                {
                    userDataPtr[0].DataPointer = (ulong)a0;
                    userDataPtr[1].DataPointer = (ulong)a1;
                    userDataPtr[3].DataPointer = (ulong)a2;
                    status = WriteEvent(ref eventDescriptor, argumentCount, (IntPtr)(userData));
                }
            }

            return status;

        }

    }

}

생성된 코드에 unsafe 키워드가 있기 때문에 이 코드를 사용할 프로젝트의 속성 창에서 unsafe 예약어를 허용(Allow unsafe code)해야 합니다. 또한, 다음과 같이 리소스 파일로 etw_sample.res를 포함하는 것도 잊으면 안 됩니다.

etw_sample_2.png

이렇게 준비하면 남은 작업은 단지 로그를 남겨주는 정적 메서드를 호출하는 것으로 ETW 로깅을 수행할 수 있습니다.

using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            CallTestMethod(2000);
            CallTestMethod(300);
        }

        private static void CallTestMethod(int sleep)
        {
            Guid guidContext = Guid.NewGuid();
            Stopwatch st = new Stopwatch();
            st.Start();
            try
            {
                Thread.Sleep(sleep);
            }
            finally
            {
                st.Stop();
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ", " + AppDomain.GetCurrentThreadId() + ", " + st.ElapsedMilliseconds);
                MyETWSampleProvider.PerfTrace.Sample_Perf.EventWriteEvent_MethodPerfTrace(guidContext.ToString(), "CallTestMethod", (ulong)st.ElapsedMilliseconds, "");
            }
        }
    }
}




이 상태에서 예제 프로젝트를 실행하면 MyETWSampleProvider.PerfTrace.Sample_Perf.EventWriteEvent_MethodPerfTrace의 코드에서 IsEnabled 메서드의 반환값이 false가 나오면서 아무런 이벤트도 기록하지 않습니다.

public static bool EventWriteEvent_MethodPerfTrace(string Context, string MethodName, ulong ExecutionTime, string ParameterInfo)
{

    if (!m_provider.IsEnabled())
    {
        return true;
    }

    return m_provider.TemplateTemplate_MethodPerfTrace(ref Event_MethodPerfTrace, Context, MethodName, ExecutionTime, ParameterInfo);
}

즉, ETW 로깅 환경이 갖춰지지 않는 경우 응용 프로그램에 거의 부하가 없음을 의미합니다. ETW가 동작하려면 해당 EXE 프로그램이 동작하는 환경에 2가지 조건을 갖춰야 합니다.

우선, MyETWSampleProvider.PerfTrace.Sample_Perf 클래스를 사용하는 EXE 파일의 위치가 정확하게 설정된 etw_sample.man 파일이 필요합니다.
우선, etw_sample.rc 파일이 컴파일된 etw_sample.res 리소스를 포함하는 모듈의 위치가 정확하게 설정된 etw_sample.man 파일이 필요합니다. (이글에서는 etw_sample.res 리소스를 ConsoleApplication1.exe에 포함시켰기 때문에 그 파일의 경로를 지정합니다.)

<?xml version="1.0" encoding="UTF-16"?>
<instrumentationManifest ...[생략]...>
    <instrumentation>
        <events>
            <provider name="MyETWSampleProvider" guid="{223F223D-B390-4126-A1C8-3926D1E5B891}" symbol="Sample_Perf" 
                    resourceFileName="...[경로]\ConsoleApplication1.exe" 
                    messageFileName="...[경로]\ConsoleApplication1.exe">
                <events>
                    <event symbol="Event_MethodPerfTrace" value="1" version="0" level="win:Informational" template="Template_MethodPerfTrace">
                    </event>
                </events>
                ...[생략]...
            </provider>
        </events>
    </instrumentation>
    ...[생략]...
</instrumentationManifest>

ETW를 사용하는 프로그램은 설치 시에 man 파일을 보유하고 있다가 위의 resourceFileName/messageFileName의 값을 (리소스를 포함한) 모듈이 있는 경로로 바꿔준 후 (비스타 이후부터 기본 설치된) wevutil.exe 프로그램을 이용해 (관리자 권한으로) 다음과 같이 ETW Provider를 등록해야 합니다.

[등록]
wevtutil.exe im etw_sample.man

[확인]
xperf -providers | findstr "My"
223f223d-b390-4126-a1c8-3926d1e5b891 : MyETWSampleProvider

[제거]
wevtutil.exe um etw_sample.man  

이렇게 등록해도 소스코드의 m_provider.IsEnabled는 여전히 false를 반환합니다. 왜냐하면 ETW는 해당 이벤트를 수집하도록 윈도우 시스템에 알려야 하기 때문입니다. 즉, ETW Controller가 Provider에게 로그를 남기라는 on/off 지시를 해야 하는 것입니다. 이를 위해 "Performance Analyzer"인 xperf.exe 프로그램을 다음과 같은 옵션으로 실행할 수 있습니다.

xperf.exe -start [원하는 이름] -on [NameOfYourRegisteredProvider]

E:\temp>xperf.exe -start MyETW -on MyETWSampleProvider

이렇게 시작하면 기본적으로 xperf.exe가 수행된 드라이브의 루트 드라이브에 user.etl 파일이 생성됩니다. (참고로, user.etl 파일은 잠기지 않습니다.) 기본 경로를 변경하고 싶다면, -f 옵션과 함께 명시할 수 있습니다.

xperf.exe -start MyETW -on MyETWSampleProvider -f E:\temp\etw\user.etl

이렇게까지 해줘야 이제 비로소 "m_provider.IsEnabled" 코드는 true를 반환하게 되고 ETW에 로그를 남기게 됩니다. 그리고, ETW 로깅이 필요없어지면 다음과 같이 윈도우 시스템에 그 사실을 알려줍니다.

E:\ConsoleApplication1\bin\Debug>xperf -stop MyETW
The trace you have just captured "E:\temp\etw\user.etl" may
contain personally identifiable information, including but not necessarily limit
ed to paths to files accessed, paths to registry accessed and process names. Exa
ct information depends on the events that were logged. Please be aware of this w
hen sharing out this trace with other people.

생성된 etl 파일은 wpa.exe, (C:\Program Files (x86)\Windows Kits\8.0\Windows Performance Toolkit 폴더에 있는) xperfview.exe 등을 이용해 볼 수 있습니다.

여기서는 PerfView.exe를 이용할 텐데요. 다음의 경로에서 다운로드 받을 수 있습니다. (단일 exe 프로그램이므로 설치가 필요없습니다.)

PerfView (1.7.0)
; http://www.microsoft.com/en-us/download/details.aspx?id=28567

실행하고 user.etl 파일을 열면 다음과 같이 예제 프로그램에서 남긴 ETW 로그를 확인할 수 있습니다. ^^

etw_sample_3.png

(첨부 파일은 이 글의 실습 프로젝트를 포함합니다.)




기본 설치된 Vista 이상의 윈도우에는 xperf 프로그램이 없기 때문에, 이때는 간단하게 logman.exe를 사용할 수 있습니다. 다음은 logman.exe로 MyETWSampleProvider를 동작시키고 있습니다.

C:\>logman start my -p {223f223d-b390-4126-a1c8-3926d1e5b891} -o mytest.etl -ets

The command completed successfully.

끄는 건 이렇게 하고,

C:\>logman stop my -ets
The command completed successfully.

역시 기본 설치된 Vista 이상의 윈도우에는 wpa.exe나 perfview.exe가 없으므로 내장된 tracerpt.exe를 통해 다음과 같이 분석할 수 있습니다.

C:\temp>tracerpt mytest.etl

Input
----------------
File(s):
     mytest.etl

100.00%

Output
----------------
DumpFile:           dumpfile.xml
Summary:            summary.txt

The command completed successfully.

그럼, 대략적인 정보를 알 수 있는 summary.txt와 모든 이벤트 로그를 포함한 dumpfile.xml이 생성됩니다.




(지금부터는 시행착오에 대한 내용입니다.)

ETW 코드를 호출하는 프로그램을 Windows Server 2003에서 실행하는 경우 다음과 같은 오류가 발생한다는 점을 잊으시면 안됩니다.

D:\temp>ConsoleApplication1.exe
1, 4948, 1988

Unhandled Exception: System.TypeInitializationException: The type initializer for 'MyETWSampleProvider.PerfTrace.Sample_Perf' threw an exception. ---> System.PlatformNotSupportedException: This functionality is only supported in Windows Vista and above.
   at System.Diagnostics.Eventing.EventProvider.EtwRegister()
   at System.Diagnostics.Eventing.EventProvider..ctor(Guid providerGuid)
   at MyETWSampleProvider.PerfTrace.EventProviderVersionTwo..ctor(Guid id)
   at MyETWSampleProvider.PerfTrace.Sample_Perf..cctor()
   --- End of inner exception stack trace ---
   at MyETWSampleProvider.PerfTrace.Sample_Perf.EventWriteEvent_MethodPerfTrace(String Context, String MethodName, UInt64 ExecutionTime, String ParameterInfo)
   at ConsoleApplication1.Program.CallTestMethod(Int32 sleep)
   at ConsoleApplication1.Program.Main(String[] args)

자동 생성 코드인 Sample_Perf 클래스의 정적 생성자(cctor)에서 오류가 발생하기 때문에 2003에서도 실행되어야 하는 프로그램이라면 아예 Sample_Perf 자체를 접근하지 않도록 하는 예비 코드가 필요합니다.

아울러, 자동 생성 코드의 기반 클래스인 EventProvider 타입의 공식 지원은 .NET Framework 3.5부터라는 점도 잊지 마시고!

.NET Framework
Supported in: 4.5, 4, 3.5

.NET Framework Client Profile
Supported in: 4, 3.5 SP1




PerfView.exe를 통해 etl 내용을 확인할 때 만약 다음과 같이 필드 값이 나오지 않는다면? (DataLength로만 사용자 로그 데이터의 길이를 보여주고 있습니다.)

etw_sample_4.png

이것은 "wevtutil.exe" 프로그램을 "im" 옵션으로 *.man 파일 등록 시 resourceFileName, messageFileName의 값에 리소스 파일이 포함된 모듈의 경로를 올바르지 않게 등록한 경우입니다.

참고로, etw_sample.cs 파일을 EXE 프로젝트가 아닌 DLL 유형의 클래스 라이브러리로 분리하는 경우라면 어떤 경로를 resourceFileName, messageFileName의 값에 설정해야 할까요? 이런 경우에도 여전히 중요한 경로는 EXE 파일입니다. 다른 경로를 적으시면 안됩니다. 즉, 여러분이 ETW를 사용한 라이브러리 성격의 프레임워크를 만들었다면 man 파일의 resourceFileName, messageFileName은 나중에 EXE를 만드는 개발자가 별도로 설정해 줘야 합니다.




간혹 ETW 로그를 남기는 ConsoleApplication1.exe 파일이 프로세스가 (비정상 종료된 경우) 종료되었는데도 불구하고 잠기는 경우가 있습니다. 왜냐하면, man 파일 내에 provider의 resource/message 파일명으로 해당 EXE를 지정했기 때문에 ETW 서비스는 이 EXE를 로드하기 때문입니다.

ETW를 관장하는 서비스 프로세스는 윈도우에서 사용되는 svchost.exe이기 때문에 구분이 어려운데 그중에서 "EventLog(Windows Event Log)" 서비스를 호스팅하는 것을 찾으면 됩니다. (Process Explorer에서 찾는다면, 자식 프로세스로 audiodg.exe가 등록된 svchost.exe입니다.) 물론, 작업 관리자에서 강제 종료하지 마시고 그냥 NT 서비스 관리자에서 "Windows Event Log"를 재시작하시면 됩니다.




xperf로 provider의 등록 여부를 "xperf -providers | findstr "My"와 같이 실행하면 된다고 했는데요. Vista에서는 xperf.exe가 기본적으로 없으므로 이때는 내장 프로그램인 wevtutil.exe를 gp 옵션으로 사용하면 됩니다. 그런데, 다음과 같이 "Failed to..." 메시지가 나오면 정상적으로 등록되지 않은 것입니다.

C:\Users\testusr> wevtutil gp MyETWSampleProvider
name: MyETWSampleProvider
guid: 223f223d-b390-4126-a1c8-3926d1e5b891
helpLink:
resourceFileName: c:\temp\bin\ConsoleApplication1.exe
messageFileName: c:\temp\bin\ConsoleApplication1.exe
Failed to get message property. The system cannot find the file specified.

즉, *.man 파일에 지정된 resourceFileName과 messageFileName의 파일명 경로가 잘못된 것입니다. 다시 잘 확인하고, "wevtutil.exe im ...." 옵션으로 등록해 주시면 됩니다.




그 외에 참고할만한 링크를 정리해 봤습니다.

man 파일의 경로를 EXE 프로젝트마다 바꿔주어야 하는 불편함이 있는데, 아래의 글에서는 msbuild에 이 과정을 자동화해주는 방법을 소개하고 있습니다.

Creating Strongly-Typed C# Event Tracing for Windows (ETW) Assemblies with Visual Studio
; http://maximelabelle.wordpress.com/2012/09/05/creating-strongly-typed-c-event-tracing-for-windows-etw-assemblies-with-visual-studio/

ETW가 매력적인 것은, 다른 ETW Provider와 조합해서 로깅 데이터를 분석할 수 있다는 점인데요. 이에 대한 간단한 실습 과정이 아래의 글에 나와 있습니다. 자신의 provider와 함께 .NET Framework의 clr 관련 ETW Provider를 이용해 로그를 남긴 것을 합쳐서 분석하는 과정을 보여주고 있습니다.

Using .NET 4.0 Event Tracing for Windows (ETW) along with application ETW
; http://naveensrinivasan.com/2010/03/17/using-clr-4-0-event-tracing-for-windows-etw-along-with-application-etw/

아울러, NuGet 패키지로 등록된 EventTraceWatcher 클래스를 이용해서,

EventTraceWatcher
; http://nugetmusthaves.com/Package/EventTraceWatcher

실시간으로 ETW 이벤트를 받아 처리할 수 있는 프로그램을 만드는 방법을 소개하고 있습니다.

How to consume ETW events from C# (내부 링크)
; https://learn.microsoft.com/en-us/archive/blogs/danielvl/how-to-consume-etw-events-from-c

이 글의 실습을 해보고 나서, 아래의 글에 실린 "그림 1 ETW 아키텍처"를 보니 이제 좀 이해가 되는군요. ^^

ETW를 사용한 디버깅 및 성능 조정 개선
; https://learn.microsoft.com/ko-kr/archive/msdn-magazine/2007/april/event-tracing-improve-debugging-and-performance-tuning-with-etw




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/28/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2014-11-06 05시57분
[hyunkim] 역시 이 내용 정리하고 올리고 있을줄 알았음.
일일이 설명안해줘도 잘 찾아서 적응하니 다행이기는 한데 수정해서 주기로 한것부터 주고 자료정리하는게 어떨까?
[guest]
2014-11-06 06시20분
[hyunkim] resourceFileName, messageFileName 경로는 EXE가 우선순위가 아님.
NTEventLog에 EventSource를 등록하는 것과 마찬가지로 MessageCompiler가 생성한 리소스를 포함하고 있는 PE파일을 지정하면 되는 것이므로 dll인지 exe인지는 중요하지 않음.
[guest]
2014-11-06 11시47분
넵. ^^ 말씀하신 부분은 수정했습니다. (안 그래도 내심... 이게 뭔가 좀 이상하다 싶어 꺼림직했는데, 설명해 주신 방법이어서 얼마나 다행인지 모릅니다. ^^)
정성태
2014-11-11 12시56분
[Lyn] 음... 윈도우라는 OS가 참 기능이 더럽게 많군요 (...) 찾아서 쓰는것도 일이네요
[guest]
2014-11-11 02시13분
뭐... 그렇다고 더럽게까지야... ^^ 나름대로 자체 인력 리소스가 되니 뭘 더 개선할까 고민하면서 끝내는 기존 기능들의 성능을 높이기 위해 커널단으로 내려서 처리하는 듯합니다.
정성태
2014-11-11 02시51분
[Lyn] 저같은 사람은 공부하는 속도보다 새거 나오는 속도가 더 빠른거 같아 못쫒아가겠어요...
[guest]
2015-05-29 05시55분
provider가 등록되어 있는지 xperf를 이용해 확인하는데요.

xperf -providers | findstr "My"

xperf가 기본적으로 시스템에 설치되어 있지 않기 때문에 logman.exe를 이용하는 방법을 사용하는 것이 더 낫습니다. 방법도 다음과 같이 xperf와 거의 유사합니다.

logman providers | findstr "My"
정성태
2020-08-10 08시13분
WPT: WPRUI/WPR/Xperf: Capture high cpu, disk i/o, file, registry, networking, Private bytes, Virtual bytes, Paged Pool/Nonpaged pool and/or application slowness
; https://yongrhee.wordpress.com/2020/08/10/wpt-wprui-wpr-xperf-capture-high-cpu-disk-i-o-file-registry-networking-private-bytes-virtual-bytes-paged-pool-nonpaged-pool-and-or-application-slowness
정성태
2020-08-19 01시35분
정성태

1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13398정성태8/3/20234155스크립트: 55. 파이썬 - pyodbc를 이용한 SQL Server 연결 사용법
13397정성태7/23/20233659닷넷: 2134. C# - 문자열 연결 시 string.Create를 이용한 GC 할당 최소화
13396정성태7/22/20233348스크립트: 54. 파이썬 pystack 소개 - 메모리 덤프로부터 콜 스택 열거
13395정성태7/20/20233311개발 환경 구성: 685. 로컬에서 개발 중인 ASP.NET Core/5+ 웹 사이트에 대해 localhost 이외의 호스트 이름으로 접근하는 방법
13394정성태7/16/20233259오류 유형: 873. Oracle.ManagedDataAccess.Client - 쿼리 수행 시 System.InvalidOperationException
13393정성태7/16/20233424닷넷: 2133. C# - Oracle 데이터베이스의 Sleep 쿼리 실행하는 방법
13392정성태7/16/20233299오류 유형: 872. Oracle - ORA-01031: insufficient privileges
13391정성태7/14/20233370닷넷: 2132. C# - sealed 클래스의 메서드를 callback 호출했을 때 인라인 처리가 될까요?
13390정성태7/12/20233341스크립트: 53. 파이썬 - localhost 호출 시의 hang 현상
13389정성태7/5/20233324개발 환경 구성: 684. IIS Express로 호스팅하는 웹을 WSL 환경에서 접근하는 방법
13388정성태7/3/20233517오류 유형: 871. 윈도우 탐색기에서 열리지 않는 zip 파일 - The Compressed (zipped) Folder '[...].zip' is invalid. [1]파일 다운로드1
13387정성태6/28/20233536오류 유형: 870. _mysql - Commands out of sync; you can't run this command now
13386정성태6/27/20233604Linux: 61. docker - 원격 제어를 위한 TCP 바인딩 추가
13385정성태6/27/20233826Linux: 60. Linux - 외부에서의 접속을 허용하기 위한 TCP 포트 여는 방법
13384정성태6/26/20233569.NET Framework: 2131. C# - Source Generator로 해결하는 enum 박싱 문제파일 다운로드1
13383정성태6/26/20233314개발 환경 구성: 683. GPU 런타임을 사용하는 Colab 노트북 설정
13382정성태6/25/20233357.NET Framework: 2130. C# - Win32 API를 이용한 윈도우 계정 정보 (예: 마지막 로그온 시간)파일 다운로드1
13381정성태6/25/20233746오류 유형: 869. Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
13380정성태6/24/20233195스크립트: 52. 파이썬 3.x에서의 동적 함수 추가
13379정성태6/23/20233210스크립트: 51. 파이썬 2.x에서의 동적 함수 추가
13378정성태6/22/20233098오류 유형: 868. docker - build 시 "CANCELED ..." 뜨는 문제
13377정성태6/22/20236901오류 유형: 867. 파이썬 mysqlclient 2.2.x 설치 시 "Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env vars manually" 오류
13376정성태6/21/20233286.NET Framework: 2129. C# - Polly를 이용한 클라이언트 측의 요청 재시도파일 다운로드1
13375정성태6/20/20232986스크립트: 50. Transformers (신경망 언어모델 라이브러리) 강좌 - 2장 코드 실행 결과
13374정성태6/20/20233110오류 유형: 866. 파이썬 - <class 'AttributeError'> module 'flask.json' has no attribute 'JSONEncoder'
13373정성태6/19/20234399오류 유형: 865. 파이썬 - pymssql 설치 관련 오류 정리
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...