namespace Test
{
public struct GcHeapInfo
{
public int Generation { get; set; } // Note that this field is derived from the TotalPromotedSize* fields. If nothing was promoted, it is possible that this could give a number that is smaller than what GC/Start or GC/Stop would indicate.
public long TotalHeapSize { get; set; }
public long TotalPromoted { get; set; }
public long GenerationSize0 { get; set; } // The size, in bytes, of generation 0 memory.
public long TotalPromotedSize0 { get; set; } // The number of bytes that are promoted from generation 0 to generation 1.
public long GenerationSize1 { get; set; } // The size, in bytes, of generation 1 memory.
public long TotalPromotedSize1 { get; set; } // The number of bytes that are promoted from generation 1 to generation 2.
public long GenerationSize2 { get; set; } // The size, in bytes, of generation 2 memory.
public long TotalPromotedSize2 { get; set; } // The number of bytes that survived in generation 2 after the last collection
public long GenerationSize3 { get; set; } // The size, in bytes, of the large object heap.
public long TotalPromotedSize3 { get; set; } // The number of bytes that survived in the large object heap after the last collection.
public long GenerationSize4 { get; set; } // The size, in bytes, of the pinned object heap.
public long TotalPromotedSize4 { get; set; } // The number of bytes that survived in the pinned object heap after the last collection.
public int GCHandleCount { get; set; } // The number of garbage collection handles in use.
public int SinkBlockCount { get; set; } // The number of synchronization blocks in use.
public int PinnedObjectCount { get; set; } // The number of pinned (unmovable) objects.
public long FinalizationPromotedCount { get; set; } // The number of objects that are ready for finalization.
public long FinalizationPromotedSize { get; set; } // The total size, in bytes, of the objects that are ready for finalization.
public override string ToString()
{
return $"Generation: {Generation}, TotalHeapSize: {TotalHeapSize}, TotalPromoted: {TotalPromoted}, " +
$"GenerationSize0: {GenerationSize0}, TotalPromotedSize0: {TotalPromotedSize0}, " +
$"GenerationSize1: {GenerationSize1}, TotalPromotedSize1: {TotalPromotedSize1}, " +
$"GenerationSize2: {GenerationSize2}, TotalPromotedSize2: {TotalPromotedSize2}, " +
$"GenerationSize3: {GenerationSize3}, TotalPromotedSize3: {TotalPromotedSize3}, " +
$"GenerationSize4: {GenerationSize4}, TotalPromotedSize4: {TotalPromotedSize4}, " +
$"GCHandleCount: {GCHandleCount}, SinkBlockCount: {SinkBlockCount}, PinnedObjectCount: {PinnedObjectCount}, " +
$"FinalizationPromotedCount: {FinalizationPromotedCount}, FinalizationPromotedSize: {FinalizationPromotedSize}";
}
}
}
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Threading;
namespace Test
{
/// <summary>
/// GC 이벤트 수신용 클래스
/// 참고. EventPipe 를 사용하면 디버깅(breakpoint 사용) 시 IDE 에서 메시지 팝업을 주어 불편하다.
///
/// EventPipe 사용을 위한 nuget 설치
/// - Microsoft.Diagnostics.NETCore.Client 0.2.257301
/// - Microsoft.Diagnostics.Tracing.TraceEvent 2.0.74
///
/// ref.
/// - EventPipe: https://docs.microsoft.com/ko-kr/dotnet/core/diagnostics/eventpipe
/// - Event: https://docs.microsoft.com/ko-kr/dotnet/fundamentals/diagnostics/runtime-garbage-collection-events#gcsuspendeebegin_v1-event
/// </summary>
public class GcEventListener
{
private EventPipeSession _session;
private EventPipeEventSource _source;
// EventPipe 는 thread 를 점유하므로 별도 스레드로 실행한다.
private Thread _thread;
// 에러 수신용 콜백
private readonly Action<string> _onErrorCallback;
// GC Event 수신용 콜백
private readonly Action<GcInfo> _onGcInfoCallback;
// GC Heap 정보 수신용 콜백
private readonly Action<GcHeapInfo> _onGcHeapInfoCallback;
// event 수신할 pid
public int _processId;
/// <summary>
/// 생성자
/// </summary>
/// <param name="errorCallback">에러 수신용 콜백</param>
/// <param name="gcInfoCallback">GC Event 수신용 콜백</param>
/// <param name="gcHeapInfoCallback">GC Heap 정보 수신용 콜백</param>
public GcEventListener(
Action<string> errorCallback,
Action<GcInfo> gcInfoCallback,
Action<GcHeapInfo> gcHeapInfoCallback)
{
_processId = Process.GetCurrentProcess().Id;
_onErrorCallback = (errorCallback is null) ? OnErrorCallback : errorCallback;
_onGcInfoCallback = (gcInfoCallback is null) ? OnGcInfoCallback : gcInfoCallback;
_onGcHeapInfoCallback = (gcHeapInfoCallback is null) ? OnGcHeapInfoCallback : gcHeapInfoCallback;
}
/// <summary>
/// GC Event 수신 시작
/// </summary>
public void Start()
{
if (_thread is null)
{
_thread = new Thread(new ThreadStart(Run));
_thread.Start();
}
}
/// <summary>
/// GC Event 수신 종료
/// </summary>
public void Stop()
{
if (_source != null)
{
_source.StopProcessing();
}
if (_session != null)
{
_session.Dispose();
}
if (_thread != null)
{
_thread.Join();
_thread = null;
}
}
private void Run()
{
try
{
DiagnosticsClient client = new DiagnosticsClient(_processId);
_session = client.StartEventPipeSession(
new EventPipeProvider(
"Microsoft-Windows-DotNETRuntime",
EventLevel.Informational,
(long)ClrTraceEventParser.Keywords.GC
),
false
);
_source = new EventPipeEventSource(_session.EventStream);
GcInfo gcInfo = new GcInfo();
GcHeapInfo gcHeapInfo = new GcHeapInfo();
// Event 정보
// https://apisof.net/catalog/6410c1dc-7784-d05f-c107-93a10a97ae80
// https://docs.microsoft.com/en-us/dotnet/fundamentals/diagnostics/runtime-garbage-collection-events
// GCStart 후 GCStop 이 쌍으로 호출된다.
_source.Clr.GCStart += (GCStartTraceData evt) =>
{
gcInfo.Generation = evt.Depth;
gcInfo.TotalCount = evt.Count;
gcInfo.Reason = evt.Reason;
gcInfo.StartTime = evt.TimeStamp;
};
_source.Clr.GCStop += (GCEndTraceData evt) =>
{
gcInfo.Generation = evt.Depth;
gcInfo.TotalCount = evt.Count;
gcInfo.EndTime = evt.TimeStamp;
_onGcInfoCallback(gcInfo);
};
_source.Clr.GCHeapStats += (GCHeapStatsTraceData evt) =>
{
gcHeapInfo.Generation = evt.Depth;
gcHeapInfo.TotalHeapSize = evt.TotalHeapSize;
gcHeapInfo.TotalPromoted = evt.TotalPromoted;
gcHeapInfo.GenerationSize0 = evt.GenerationSize0;
gcHeapInfo.TotalPromotedSize0 = evt.TotalPromotedSize0;
gcHeapInfo.GenerationSize1 = evt.GenerationSize1;
gcHeapInfo.TotalPromotedSize1 = evt.TotalPromotedSize1;
gcHeapInfo.GenerationSize2 = evt.GenerationSize2;
gcHeapInfo.TotalPromotedSize2 = evt.TotalPromotedSize2;
gcHeapInfo.GenerationSize3 = evt.GenerationSize3;
gcHeapInfo.TotalPromotedSize3 = evt.TotalPromotedSize3;
gcHeapInfo.GenerationSize4 = evt.GenerationSize4;
gcHeapInfo.TotalPromotedSize4 = evt.TotalPromotedSize4;
gcHeapInfo.GCHandleCount = evt.GCHandleCount;
gcHeapInfo.SinkBlockCount = evt.SinkBlockCount;
gcHeapInfo.PinnedObjectCount = evt.PinnedObjectCount;
gcHeapInfo.FinalizationPromotedCount = evt.FinalizationPromotedCount;
gcHeapInfo.FinalizationPromotedSize = evt.FinalizationPromotedSize;
_onGcHeapInfoCallback(gcHeapInfo);
};
_source.Process();
}
catch (Exception e)
{
_onErrorCallback($"gc event error: {e}");
}
}
// 기본 콜백 메소드
private void OnErrorCallback(string _) { }
private void OnGcInfoCallback(GcInfo _) { }
private void OnGcHeapInfoCallback(GcHeapInfo _) { }
}
}
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using System;
namespace Test
{
public struct GcInfo
{
public int Generation { get; set; } // GC generation 0,1,2
public int TotalCount { get; set; } // GC 발생 횟수
public GCReason Reason { get; set; } // GC 발생 사유
public GCType Type { get; set; } // GC type
public DateTime StartTime { get; set; } // GC 시작 시간
public DateTime EndTime { get; set; } // GC 종료 시간
public double ElapsedTimeMsec => (EndTime - StartTime).TotalMilliseconds;
public override string ToString()
{
return $"Generation: {Generation}, TotalCount: {TotalCount}, ElapsedTimeMsec: {ElapsedTimeMsec}, " +
$"Reason: {Reason}, Type: {Type}, " +
$"StartTime: {StartTime:yyyy-MM-dd hh:mm:ss.fff}, EndTime: {EndTime:yyyy-MM-dd hh:mm:ss.fff}";
}
}
}
using System;
namespace Test
{
public static class GcMemory
{
public static string Get()
{
GCMemoryInfo gcMemoryInfo = GC.GetGCMemoryInfo();
return
$"FragmentedBytes: {gcMemoryInfo.FragmentedBytes}, " + // 마지막 가비지 수집이 발생한 총 조각화를 가져옵니다.
$"HeapSizeBytes: {gcMemoryInfo.HeapSizeBytes}, " + // 마지막 가비지 수집이 발생한 총 힙 크기를 가져옵니다.
$"HighMemoryLoadThresholdBytes: {gcMemoryInfo.HighMemoryLoadThresholdBytes}, " + // 마지막 가비지 수집이 발생했을 때의 높은 메모리 로드 임계값을 가져옵니다.
$"MemoryLoadBytes: {gcMemoryInfo.MemoryLoadBytes}, " + // 마지막 가비지 수집이 발생했을 때의 메모리 로드를 가져옵니다.
$"TotalAvailableMemoryBytes: {gcMemoryInfo.TotalAvailableMemoryBytes }"; // 가비지 수집기에서 마지막 가비지 수집이 발생했을 때 사용할 수 있는 총 메모리를 가져옵니다.
}
}
}
Performance Counter - cpu usage + @ 정보
using System;
using System.Diagnostics;
using System.Threading;
namespace Test
{
/// <summary>
/// cpu, mem, gc 정보 조회
///
/// nuget 설치: System.Diagnostics.PerformanceCounter
/// </summary>
public class ResourceMonitor
{
// 대상 프로세스 이름(# 포함)
private string _processName = "";
// cpu performance counter
private PerformanceCounter _cpu;
// process name getter
public string ProcessName
{
get
{
return _processName;
}
}
// GC
public class GcGenerationCount
{
public int Gen0 { get; set; }
public int Gen1 { get; set; }
public int Gen2 { get; set; }
}
public GcGenerationCount GcCount { get; private set; } = new GcGenerationCount();
// Thread Pool
public class ThreadPoolCount
{
// worker thread count
public int UseWorkerCount { get; set; }
public int MinWorkerCount { get; set; }
public int MaxWorkerCount { get; set; }
public int AvailWorkerCount { get; set; }
// completion port thread count
public int UseCompletionPortCount { get; set; }
public int MinCompletionPortCount { get; set; }
public int MaxCompletionPortCount { get; set; }
public int AvailCompletionPortCount { get; set; }
}
public ThreadPoolCount ThreadCount { get; private set; } = new ThreadPoolCount();
public ResourceMonitor()
{
UpdateForPerformanceCounter();
}
/// <summary>
/// 프로세스 명(#)이 변경될 수 있기 때문에 주기적으로 호출해야 한다.
/// </summary>
public void UpdateForPerformanceCounter()
{
// process name 이 변경될 수 있다.
// ex. (App, App#1, App#2) => (App, App#1)
string processName = GetProcessNameWithSharp(AppDomain.CurrentDomain.FriendlyName, Process.GetCurrentProcess().Id);
if (!_processName.Equals(processName))
{
_processName = processName;
_cpu = new PerformanceCounter("Process", "% Processor Time", processName, true);
}
}
public string GetMonitoringData()
{
UpdateForPerformanceCounter();
var cpu = GetCpu();
var mem = GetMemory();
var gc = GetGcCount();
var threads = GetThreadCount();
return
$"[Resource] cpu: {cpu} %, mem: {HumanReadableConverter.ByteSizeToString(mem)}, gc: {gc.Gen0}-{gc.Gen1}-{gc.Gen2}, " +
$"[Worker] use: {threads.UseWorkerCount} (min: {threads.MinWorkerCount}, max: {threads.MaxWorkerCount}, avail: {threads.AvailWorkerCount}), " +
$"[CP] use: {threads.UseCompletionPortCount} (min: {threads.MinCompletionPortCount}, max: {threads.MaxCompletionPortCount}, avail: {threads.AvailCompletionPortCount})";
}
/// <summary>
/// 동일 프로세스 명으로 여러개가 떠있을 수 있으므로
/// 퍼포먼스 카운트에서 사용하는 실제 이름을 구한다.
/// ex. App#1
/// </summary>
/// <param name="processNamePrefix">ex. chrome</param>
/// <param name="pid">ex. 1234</param>
/// <returns>ex. chrome#18</returns>
private string GetProcessNameWithSharp(string processNamePrefix, int pid)
{
string[] instances = new PerformanceCounterCategory("Process").GetInstanceNames();
foreach (string instance in instances)
{
try
{
using var counter = new PerformanceCounter("Process", "ID Process", instance, true);
if (!counter.InstanceName.StartsWith(processNamePrefix))
{
continue;
}
if (pid == (int)counter.RawValue)
{
return instance;
}
}
catch (Exception)
{
// nothing to do
}
}
// 현재 프로세스를 감지하므로 실제 빈 문자열을 주는 경우는 없다.
return "";
}
/// <summary>
/// 현재 프로세스 cpu 사용량 (%)
/// </summary>
/// <returns></returns>
public int GetCpu()
{
try
{
double usage = _cpu.NextValue() / Environment.ProcessorCount;
return (int)Math.Round(usage);
}
catch (Exception)
{
// 비정상적인 상황(서버 초기화시 발생 가능)
return -1;
}
}
/// <summary>
/// 메모리 사용량 (개인 작업 집합)
/// </summary>
/// <returns></returns>
public long GetMemory()
{
return GC.GetTotalMemory(false);
}
/// <summary>
/// 세대별 gc 발생 횟수 (누적치)
/// </summary>
/// <returns></returns>
public GcGenerationCount GetGcCount()
{
GcCount.Gen0 = GC.CollectionCount(0);
GcCount.Gen1 = GC.CollectionCount(1);
GcCount.Gen2 = GC.CollectionCount(2);
return GcCount;
}
/// <summary>
/// .net thread pool 사용 개수
/// </summary>
/// <returns></returns>
public ThreadPoolCount GetThreadCount()
{
ThreadPool.GetMinThreads(out int minWorker, out int minCP);
ThreadPool.GetMaxThreads(out int maxWorker, out int maxCP);
ThreadPool.GetAvailableThreads(out int availWorker, out int availCP);
ThreadCount.UseWorkerCount = maxWorker - availWorker;
ThreadCount.MinWorkerCount = minWorker;
ThreadCount.MaxWorkerCount = maxWorker;
ThreadCount.AvailWorkerCount = availWorker;
ThreadCount.UseCompletionPortCount = maxCP - availCP;
ThreadCount.MinCompletionPortCount = minCP;
ThreadCount.MaxCompletionPortCount = maxCP;
ThreadCount.AvailCompletionPortCount = availCP;
return ThreadCount;
}
}
}
'C#' 카테고리의 다른 글
c# HttpClient (0) | 2022.03.15 |
---|---|
c# channel (0) | 2022.03.15 |
object pool, object type pool (0) | 2022.03.11 |
GC TEST (0) | 2022.03.08 |
C# .net core 빌드 및 powershell 전송 (0) | 2022.03.01 |
c# stack size 확인 (0) | 2022.02.24 |
숫자 범위 추출 및 확장 (0) | 2021.11.03 |
C# LZ4 (0) | 2021.10.20 |