.Net Core performance monitor

  • 1117
  • 0
  • 2020-05-08

EventPipe + Prometheus

.Net Core 3.0以後,總算有官方的套件可以做 performance counter,

不管在 windows上還是 linux上,都可以用一致的方法來建立效能監控指標。

若要建立效能指標,我們需要先建立一個EventSource的實體,

這個類別在 System.Diagnostics.Tracing 中,

可以利用以下的程式碼定義

public class SampleEventSource : EventSource
{
    public SampleEventSource() : base("Sample")
    {
    }
}

定義好類別後,可以建立其實體,然後以這個實體來建立接下來的EventCounter,

EventCounter內建的有四種Counter,可以看MSDN的介紹

https://docs.microsoft.com/zh-tw/dotnet/api/system.diagnostics.tracing.diagnosticcounter?view=netcore-3.1

建立Counter需要傳入EventSource,建立的程式如下

public SampleEventHostedService(SampleEventSource eventSource)
{
    m_Counter = new IncrementingEventCounter("test-incrementing", eventSource);
}

有了Counter後,就可以使用來記錄效能指標,

m_Counter.Increment();

 

程式中把效能指標埋好後,接下來就是怎麼取得這些效能指標,

如果安裝了 dotnet-counters,在命令列下執行 

dotnet counters monitor -p <要監控的process id>

就可以看到預設的performance counters,

像這樣自定義的 counters,可以執行

dotnet counters monitor -p <要監控的process id> System.Runtime Sample

將要監控的指標以空白串起來放在命令的最後,

執行起來會看到類似這樣的畫面

不過這樣子雖然可以用來快速了解系統的狀態,感覺不容易讓目前常見的監控系統讀取,

我所參考的文章中,作者讀取的程式還沒有套件可以使用,後來微軟把這個套件釋出了,

所以只要加入nuget 套件 Microsoft.Diagnostics.NETCore.Client 就可以寫出像dotnet-counters這樣的程式來讀取效能指標,

我想建立一個monitor的站台,讀取出指標,並且產生 Prometheus 格式的內容來整合到監控軟體上,

目標的環境預計要跑在docker中,而linux docker的dotnet process剛好 pid 都是 1,

這樣一來 pid 的問題就能排除,

不過接下來是如果讓 monitor的 app 可以讀取到目標的 EventPipe 的資料,

這個資料在Linux上是使用 Domain Socket file來達成,而在 Windows上則是 NamePipe,

在 Linux 上這個檔案會放置在 /tmp 中,

所以只要讓兩個container之間可以共用 /tmp,就可以讀取到目標的效能指標,

另外,因為 dotnet 的 container預設執行的pid 是 1,監控的 app也將會 pid 是 1,

這樣資料會錯亂,在docker run 的時候,可以使用 --pid 來讓監控的 container 引用別的 container的 pid,如此,自己的pid 也會錯開,

monitor的程式碼會如下

var pid = 1;
var client = new DiagnosticsClient(pid);

var providerArguments = new Dictionary<string, string>
{
    ["EventCounterIntervalSec"] = "10"
};

var provider = new EventPipeProvider(
    "System.Runtime",
    System.Diagnostics.Tracing.EventLevel.Verbose,
    0xffffffff,
    providerArguments);
var customEventProvider = new EventPipeProvider(
    "Sample",
    System.Diagnostics.Tracing.EventLevel.Verbose,
    0xffffffff,
    providerArguments);

var session = client.StartEventPipeSession(new[] { provider, customEventProvider });

m_EventSource = new EventPipeEventSource(session.EventStream);

m_EventSource.Dynamic.All += ProcessEvents;

事件處理程式

private void ProcessEvents(TraceEvent obj)
{
	if (obj.EventName.Equals("EventCounters"))
	{
		try
		{
			IDictionary<string, object> payloadVal = (IDictionary<string, object>)(obj.PayloadValue(0));
			IDictionary<string, object> payloadFields = (IDictionary<string, object>)(payloadVal["Payload"]);

			Console.WriteLine(string.Join(", ", payloadFields.Select(p => $"{p.Key}=>{p.Value}")));

			var counterName = payloadFields["Name"].ToString();
			var displayName = payloadFields["DisplayName"].ToString();
			var displayUnits = payloadFields["DisplayUnits"].ToString();
			var value = 0D;

			if (payloadFields["CounterType"].Equals("Mean"))
			{
				value = (double)payloadFields["Mean"];
			}
			else if (payloadFields["CounterType"].Equals("Sum"))
			{
				var intervalSec = (float)payloadFields["IntervalSec"];
				var increment = (double)payloadFields["Increment"];
				value = increment / intervalSec;

				if (string.IsNullOrEmpty(displayUnits))
				{
					displayUnits = "count";
				}
				displayUnits += "/sec";
			}

			var gaugeName = $"{obj.ProviderName.Replace('.', ':')}:{counterName.Replace('-', '_')}";
			var gauge = GetGauge(gaugeName, displayName, displayUnits);

			gauge.Set(value);
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex.ToString());
		}
	}
}

完成後,可以看到成功取得內容

這個實作參考了兩篇文章才得以完成

https://medium.com/criteo-labs/net-core-counters-internals-how-to-integrate-counters-in-your-monitoring-pipeline-5354cd61b42e

https://im5tu.io/article/2020/01/diagnostics-in-.net-core-3-using-dotnet-counters-with-docker/

實作的程式碼放置在GitHub中

https://github.com/phoenix-chen-2016/NetCoreMonitorExample