Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Ubuntu(16.04) 환경에서 .NET Core 애플리케이션에서 대한 CPU Profiling은 기존의 Windows 환경에서 사용했던 PerfView라는 툴을 이용할 수 있다.
CPU profiling을 위해서 우선 perfcollect 라는 툴을 Ubuntu 환경에 설치해야 한다.
curl -OL https://aka.ms/perfcollect
sudo chmod +x perfcollect
sudo ./perfcollect install
설치가 완료 되면, 다음과 같은 순서로 CPU sampling을 할 수 있다.
애플리케이션을 수행할 terminal 창에서 아래를 수행한다.
export COMPlus_PerfMapEnabled=1
export COMPlus_EnableEventLog=1
이후에 perfcollect를 수행할 terminal 창을 하나 더 연후에 아래를 수행하여 CPU sampling을 시작한다.
sudo ./perfcollect collect hicputrace
수행과 더불어 sampling은 시작된다.
이후에는 애플리케이션 수행창에서 애플리케이션을 수행한다. 그리고 일정 시간이 흐른 후에 perfcollect가 호출되었던 창에서 ctrl+c를 눌러 중지하면, 현 directory에 hicputrace.trace.zip 파일이 생성된다. 해당 trace 파일은 windows 환경에서 perfview (https://aka.ms/perfview) 툴을 이용하여 볼 수 있다.
Perfview.exe 가 위치한 폴더에 Ubuntu에서 수집된 trace 파일을 위치하고 perfview를 실행하면 아래와 같은 화면을 볼 수 있다.
이후에 해당 zip파일을 click 하면, 아래와 같이 profile 정보를 볼 수 있다.
특히, 보고자 하는 것이 .NET core 애플리케이션의 CPU 점유 call stack을 확인하는 것이므로, CallTree 메뉴에서 dotnet process를 check 하고 tree를 확장하면, CPU를 점유하고 있는 call stack을 볼 수 있다.
아쉽게도 PerfCollect 툴은 현재 Memory Profiling은 제공하지 않는 다. 만일, Memory Profiling과 같은 정보를 추출하려면 core dump를 통해서 .NET managed memory사용량을 확인할 수 있다. 그 방법에 대해서는 다음과 같다.
먼저, Core dump를 수집하는 방법은 여러가지가 있으나, dump의 size를 고려해 볼 때, 아래의 방법을 생각해 볼 수 있다. 우선 애플리케이션의 수행에 앞서서 아래의 명령을 수행하여 충분한 size의 덤프를 생성할 수 있도록 한다.
ulimit -c Unlimited
그리고, 애플리케이션을 수행하여 메모리가 충분히 누수가 되는 시점에 새로운 terminal 창을 오픈하여 아래와 같이 애플리케이션을 중지시키면 해당 시점에 덤프가 떨어진다.
sudo kill -4 <pid>
덤프는 애플리케이션 수행시점의 현재폴더 혹은 /var/crash 폴더에서 확인할 수 있다.
core덤프를 수집하는 일반적인 방법은 다음과 같다. Gdb를 이용하는 방법이다.
sudo apt-get install gdb
sudo gdb
이후에 ps -efH 를 이용하여 pid값을 얻은 후에 gdb를 attach 한다.
attach <pid>
Gdb가 attach 된 이후에 적절한 시점에 generate-core-file 명령을 통해서 core file을 얻을 수 있다.
generate-core-file <core file path>
이후 덤프분석을 위해 해당 덤프를 lldb 디버거를 통해서 오픈 할 수 있다. Gdb에서 사용할 수 있는 .NET Core plugin이 존재하지 않기 때문에 lldb 디버거를 사용해야 한다.
아래는 “core” 라는 이름의 core file을 lldb 디버거에서 오픈한다.
그리고, libsosplugin.so 파일을 load 한다.
plugin load /usr/share/dotnet/shared/Microsoft.NETCore.App/2.0.0/libsosplugin.so
setclrpath /usr/share/dotnet/shared/Microsoft.NETCore.App/2.0.0
먼저, Libsosplugin.so 에서 제공하는 “eeheap –gc” 명령을 통해서 해당 프로세스가 사용하고 있는 전체 managed memory의 size를 알 수 있다.
“dumpheap –stat” 명령은 해당 managed memory 영역내에서 개별적으로 메모리를 사용하는 오브젝트들의 정보를 알 수 있다.
메모리 누수에 대해서 검토한다면, count와 TotalSize가 큰 오브젝트들이 사실은 관심대상이다. 아래를 보면, System.Byte[]가 그 중 많은 메모리를 점유하고 있다.
System.Byte[]의 MethodTable의 정보는 00007f8138ba1210 이며, 10565개의 동일한 타입의 오브젝트가 존재하는 것을 알 수 있다. “dumpheap -mt” 명령어는 System.Byte[]타입의 오브젝트들을 나열해준다.
dumpheap –mt 00007f8138ba1210
상위의 명령을 수행하면, <MT> <Address> <Size> 의 배열로 나열된 정보를 확인할 수 있다. 그리고, “dumpobj” 명령은 “dumpheap -mt” 명령의 결과값에 존재하는 address 값을 parameter로 사용하여 개별적인 오브젝트의 정보를 출력한다.
사실 중요한 것은 해당 오브젝트를 사용하는 코드이다. 그 정보를 확인할 수 있는 명령이 gcroot 인데, 아래와 같이 덤프에서는 정상적으로 출력이 되지 않는다.
하지만, lldb(3.6)를 문제가 발생하고 있는 애플리케이션에 직접 붙여서 확인한다면, 아래와 같은 정보를 확인할 수 있다.
memleakdemo.Program.ManagedLeak 메소드 안에 존재하는 ArrayList가 System.Byte[]를 참조하고 있다. 그러므로, ArrayList의 Size 가 얼마나 큰지, 그리고, 언제 Byte[]를 release 하는 지 등을 검토함으로써 Memory 문제여부를 isolation할 수 있을 것 같다.
// memleakdemo.Program.ManagedLeak (System.Object)
private static void ManagedLeak(object s)
{
State state = (State)s;
ArrayList list = new ArrayList();
for (int i = 0; i < state._iterations; i++)
{
if (i % 100 == 0)
Console.WriteLine(string.Format("Allocated: {0}", state._size));
System.Threading.Thread.Sleep(10);
list.Add(new byte[state._size]);
}
}
그러기 위해서는 결국 얼마나 많은 오브젝트를 살펴보느냐가 필요한데, 이것보다는 이러한 부분을 직관적으로 살펴볼 수 있는 memory profiler가 제공되면 좋을 듯 하다.