.NET PerformanceCounter求得到錯誤的Memory數值

這篇是要討論如何使用.NET Framework來取得運行主機上面的記憶體狀態,看到這裡可能會有很多人第一印象就是,這還用講嗎?利用System.Diagnostics.PerformanceCounter來取得不是兩三行就寫完了嗎?這種想法也對也錯,如果你只是使用Console隨便POC一下,你可能會覺得數據貼近你在工作管理員(Task Manager)中看到的數據,因此就覺得這樣就是正確了。但是事實不然,如果你將這些程式碼移到Web端環境(例如ASP.NET),那你就會發現表現出來的數字遠遠與工作管理員背道而馳。

先示範一下常見的PerformanceCounter取得方式

using (System.Diagnostics.PerformanceCounter oRAM = new System.Diagnostics.PerformanceCounter("Memory", "% Committed Bytes in Use"))
{
	//System.Diagnostics.PerformanceCounter oRAM = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes");
	fRAM = oRAM.NextValue();
}

如果你細心一點的將這些Code轉到非Console本機運行的環境,例如使用Web端脫離Administrator最高權限的運行,你取得到的數據會變得非常的不正確,無論是「% Committed Bytes in Use」或是「Available MBytes」皆然。不信你自己部屬到正式網站後,遠端開啟Task Manager,一面去刷新該台WebPage,你就會看到你取得到的這些數字根本是笑話。

那要如何取用到正確的系統記憶體狀態

沒錯,只能回到Windows API來取得,Windows API才是王道。下面是我建立的一個類別,該類別會傳回極度貼近工作管理員的記憶體數據。

public class PerformanceInfo
{
	[System.Runtime.InteropServices.DllImport("psapi.dll", SetLastError = true)]
	[return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
	public static extern bool GetPerformanceInfo([System.Runtime.InteropServices.Out] out PerformanceInformation PerformanceInformation, [System.Runtime.InteropServices.In] int Size);

	[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
	public struct PerformanceInformation
	{
		public int Size;
		public System.IntPtr CommitTotal;
		public System.IntPtr CommitLimit;
		public System.IntPtr CommitPeak;
		public System.IntPtr PhysicalTotal;
		public System.IntPtr PhysicalAvailable;
		public System.IntPtr SystemCache;
		public System.IntPtr KernelTotal;
		public System.IntPtr KernelPaged;
		public System.IntPtr KernelNonPaged;
		public System.IntPtr PageSize;
		public int HandlesCount;
		public int ProcessCount;
		public int ThreadCount;
	}

	//Item1:記憶體總數MB、Item2:剩餘多少可用記憶體MB
	public static System.Tuple<decimal, decimal> GetRamState()
	{
		PerformanceInformation oPI = new PerformanceInformation();
		if (GetPerformanceInfo(out oPI, System.Runtime.InteropServices.Marshal.SizeOf(oPI)))
		{
			return new System.Tuple<decimal, decimal>
			(
				System.Convert.ToDecimal((oPI.PhysicalTotal.ToInt64() * oPI.PageSize.ToInt64() / 1048576)),
				System.Convert.ToDecimal((oPI.PhysicalAvailable.ToInt64() * oPI.PageSize.ToInt64() / 1048576))
			);
		}
		else
		{ return new System.Tuple<decimal, decimal>(0, 0); }
	}

}

調用方式很簡單,請看下列程式碼:

static void Main(string[] args)
{
	System.Tuple<decimal, decimal> oRAM = PerformanceInfo.GetRamState();
	WriteLine(string.Format(
		"總記憶體數:{0}\n可用記憶體數:{1}\n使用中記憶體數:{2}\n記憶體佔用率:{3}%",
		oRAM.Item1,
		oRAM.Item2,
		oRAM.Item1 - oRAM.Item2,
		string.Format("{0:0%}", 1 - (oRAM.Item2 / oRAM.Item1))
	));

	Read();
}
System.Diagnostics.PerformanceCounter TaskManager WrongRamUsage ErrorRamUsage WrongValue