"dotnet-dump ps" 명령어가 닷넷 프로세스를 찾는 방법
"dotnet-dump ps" 명령어는 현재 시스템에 실행 중인 닷넷 프로세스를 열거합니다.
C:\temp> dotnet-dump ps
5604 ConsoleApp5 ase\netcoreapp3.0\win-x64\publish\ConsoleApp5.exe ase\netcoreapp3.0\win-x64\publish\ConsoleApp5.exe
그런데, 어떻게 찾는 걸까요? ^^ 방법을 알기 위해 dotnet-dump 도구의 github repo를 보면,
dotnet/diagnostics
; https://github.com/dotnet/diagnostics
GetPublishedProcesses 메서드를 찾을 수 있습니다.
// diagnostics/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
// ; https://github.com/dotnet/diagnostics/blob/main/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
public static IEnumerable GetPublishedProcesses()
{
// ...[생략: GetAllPublishedProcesses 로컬 함수]...
try
{
string[] files = Directory.GetFiles(PidIpcEndpoint.IpcRootPath);
return GetAllPublishedProcesses(files).Distinct();
}
catch (UnauthorizedAccessException ex)
{
if (PidIpcEndpoint.IpcRootPath.StartsWith(@"\\.\pipe"))
{
throw new DiagnosticsClientException($"Enumerating {PidIpcEndpoint.IpcRootPath} is not authorized", ex);
}
else
{
throw;
}
}
}}
위의 코드에서, 윈도우 환경인 경우 PidIpcEndpoint.IpcRootPath 값은 @"\\.\pipe"입니다. 그러니까, 시스템에 열려 있는
파이프 자원을 모두 열거하는 것으로 시작합니다. 간단하게 이 출력 결과를 "Command Prompt"에서 dir 명령어를 이용해 확인할 수도 있습니다.
c:\temp> dir \\.\pipe\\
Volume in drive \\.\pipe is NamedPipe
Directory of \\.\pipe
1601-01-01 오전 09:00 3 InitShutdown
1601-01-01 오전 09:00 5 lsass
1601-01-01 오전 09:00 3 ntsvcs
1601-01-01 오전 09:00 1 Winsock2\CatalogChangeListener-5f8-0
1601-01-01 오전 09:00 1 Winsock2\CatalogChangeListener-70c-0
1601-01-01 오전 09:00 3 epmapper
1601-01-01 오전 09:00 1 Winsock2\CatalogChangeListener-55c-0
1601-01-01 오전 09:00 1 Winsock2\CatalogChangeListener-73c-0
...[생략]...
보는 바와 같이 파이프 자원의 이름이 함께 출력됩니다. 이 중에서 닷넷 프로세스가 열어놓은 자원을 구분하는 방법은 파이프 이름을 보고 판단하게 됩니다. 이를 위해 dotnet-dump는 정규 표현식을 이용하는데요,
public static string DiagnosticsPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnet-diagnostic-(\d+)$" : @"^dotnet-diagnostic-(\d+)-(\d+)-socket$";
간단하게 예시를 들면 이런 식입니다.
[윈도우 - 파이프 이름]
$"dotnet-diagnostic-{pid}"
[리눅스/MAC - Unix Domain 소켓 파일 이름]
$"dotnet-diagnostic-dsrouter-{pid}-*-socket"
위의 원칙을 적용하면 dir 명령어로도 닷넷 프로세스가 열어 놓은 파이프를 찾을 수 있습니다.
c:\temp> dir \\.\pipe\\ | findstr dotnet-diagnostic
1601-01-01 오전 09:00 1 dotnet-diagnostic-5348
1601-01-01 오전 09:00 1 dotnet-diagnostic-51504
1601-01-01 오전 09:00 1 dotnet-diagnostic-15192
1601-01-01 오전 09:00 1 dotnet-diagnostic-66636
1601-01-01 오전 09:00 1 dotnet-diagnostic-27928
1601-01-01 오전 09:00 1 dotnet-diagnostic-18296
1601-01-01 오전 09:00 1 dotnet-diagnostic-26864
...[생략]...
그럼 이야기는 끝난 것 같군요. ^^ "dotnet-diagnostic-" 이후의 숫자는 Process ID이므로 해당 프로세스를 열어 이름과 경로를 알아내면 dotnet-dump ps 명령어와 동일한 출력을 얻을 수 있습니다.
그렇다면 리눅스/MAC의 경우는 어떨까요? 단지 Pipe가 아닌
Unix Domain 소켓을 사용하는 차이를 빼고는 방식은 거의 같습니다.
public static string IpcRootPath { get; } =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"\\.\pipe\" : Path.GetTempPath();
string[] files = Directory.GetFiles(PidIpcEndpoint.IpcRootPath); // IpcRootPath == "/tmp" (기본값)
return GetAllPublishedProcesses(files).Distinct();
결국 임시 디렉터리에 소켓 파일을 사용하게 됩니다. 따라서 "/tmp" 디렉터리에 "dotnet-diagnostic-dsrouter-{pid}-*-socket" 이름의 파일을 열거해 프로세스 ID를 찾을 수 있습니다.
재미있게도, 이런 과정을 조사하다 지난번에 겪었던 오류에 대한 원인을 알게 됐습니다.
Unhandled Exception: Microsoft.Diagnostics.NETCore.Client.ServerNotAvailableException: Unable to connect to Process
; https://www.sysnet.pe.kr/2/0/13674
위의 메시지를 발생하는 코드는 아래와 같은데요,
// .\Microsoft.Diagnostics.NETCore.Client\DiagnosticsIpc\IpcTransport.cs
public static string GetDefaultAddress(int pid)
{
try
{
Process process = Process.GetProcessById(pid);
}
catch (ArgumentException)
{
throw new ServerNotAvailableException($"Process {pid} is not running.");
}
catch (InvalidOperationException)
{
throw new ServerNotAvailableException($"Process {pid} seems to be elevated.");
}
if (!TryGetDefaultAddress(pid, out string defaultAddress))
{
string msg = $"Unable to connect to Process {pid}.";
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
msg += $" Please verify that {IpcRootPath} is writable by the current user. "
+ "If the target process has environment variable TMPDIR set, please set TMPDIR to the same directory. "
+ "Please see https://aka.ms/dotnet-diagnostics-port for more information";
}
throw new ServerNotAvailableException(msg);
}
return defaultAddress;
}
private static bool TryGetDefaultAddress(int pid, out string defaultAddress)
{
defaultAddress = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// ...[생략]...
}
else
{
try
{
defaultAddress = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{pid}-*-socket") // Try best match.
.OrderByDescending(f => new FileInfo(f).LastWriteTime)
.FirstOrDefault();
// ...[생략]...
}
catch { }
}
return !string.IsNullOrEmpty(defaultAddress);
}
정확히 저 오류 메시지와 일치합니다. 그러니까, WriteDump를 호출하는 시점에 대상 닷넷 프로세스는 로딩이 완료돼 Process.GetProcessById 코드까지는 성공한 것입니다. 하지만, /tmp 디렉터리에 아직 $"dotnet-diagnostic-{pid}-*-socket" 형식의 이름을 갖는 Unix Domain 소켓 파일을 생성하지 못한 시점이었고, 결국, sleep 명령어로 지연시켜 대상 프로세스의 닷넷 런타임이 해당 소켓 파일을 생성할 때까지 대기하는 수밖에 없었던 것입니다. (혹은 그 파일이 생성될 때까지 polling 하거나!)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]