閒來無事,寫了一個 ASP.NET MVC 小專案當成休閒活動兼練手感,假想目標是在網站內嵌一小塊監看視窗,即時回報當下的 CPU % 及記憶體使用量。執行起來類似這樣:

簡單說一下做法。

要抓 CPU 與記憶體使用量,最簡單做法是用 PerformanceCounter,實作細節可參考舊文:快速列出 Windows 執行中程式 CPU%、記憶體用量與執行身分。在 ASP.NET Process.GetCurrentProcess() 可取得網頁程式所在的 IIS/IISExpress Process,用 ProcessId 查出 Process 對映的 Performance Counter Instance Name,再傳入 Instance Name 查詢 % Processor Time、Working Set - Private 效能參數,就是我們平常在工作管理員看到的 CPU% 及記憶體使用量。(註:% Process Time 是各核用量的總和,故要除以 CPU 核數 Environment.ProcessorCount )

public class SelfPerfMonitor
{
    //https://blog.darkthread.net/blog/get-task-manager-list-with-csharp/
    static PerformanceCounter cpu;
    static PerformanceCounter mem;
    public static string GetInstanceNameForProcess(Process process)
    {
        try
        {
            var processName = Path.GetFileNameWithoutExtension(process.ProcessName);
            var cat = new PerformanceCounterCategory("Process");
            var instances = cat.GetInstanceNames().Where(inst => inst.StartsWith(processName)).ToArray();

            foreach (string instance in instances)
            {
                using (PerformanceCounter cnt = new PerformanceCounter("Process", "ID Process", instance, true))
                {
                    if ((int)cnt.RawValue == process.Id) return instance;
                }
            }
        }
        catch
        {
            //ignore
        }
        return null;
    }
    static SelfPerfMonitor()
    {
        var instanceName = GetInstanceNameForProcess(Process.GetCurrentProcess());
        cpu = new PerformanceCounter("Process", "% Processor Time", instanceName);
        mem = new PerformanceCounter("Process", "Working Set - Private", instanceName);
    }
    public static string GetCpuUsage() => $"{(cpu.NextValue() / Environment.ProcessorCount):n0}%";
    public static string GetMemUsage() => $"{mem.NextValue() / 1024:n0} K";
}

cshtml 部分用 setInterval + jQuery.getJSON 每隔一秒取得 CPU 及記憶體數值顯示出來:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Web CPU & Memory</title>
    <link href="~/Content/webstats.css" rel="stylesheet" />
</head>
<body>
    <div class="frame">
        <div class="row">
            <span class="hdr">CPU</span> <span class="field" id="cpuLoad"></span>
        </div>
        <div class="row">
            <span class="hdr">RAM</span> <span class="field" id="memUsage"></span>
        </div>
    </div>
    <script src="~/lib/jquery/jquery.min.js"></script>
    <script>
        $(function () {
            //TODO 實際運用時改走 SignalR/WebSocket 較有效率
            function refresh() {
                $.getJSON("@Url.Content("~/WebStats/Stats")").done(function (res) {
                    var cpuCss = "";
                    var cpuValue = parseInt(res.CPU.replace("%"));
                    if (cpuValue > 85) cpuCss = "alert";
                    else if (cpuValue > 50) cpuCss = "warn";
                    $("#cpuLoad").text(res.CPU).attr("data-tag", cpuCss);
                    $("#memUsage").text(res.RAM);
                });
            }
            refresh();
            setInterval(refresh, 2000);
        });
        
    </script>
</body>
</html>

WebStatsController.cs 沒什麼,就只負責將 CPU 跟記憶體數字用 JSON 傳至前端:

public class WebStatsController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Stats()
    {
        return this.Json(new
        {
            CPU = SelfPerfMonitor.GetCpuUsage(),
            RAM = SelfPerfMonitor.GetMemUsage()
        }, JsonRequestBehavior.AllowGet);
    }
}

為了讓顯示介面精緻一點,雖然 CSS 技能笨拙,還是花了點時間調樣式並去找來字型檔(TTF)模擬 LCD 顯示效能。web.config 要配合調整以允許下載 .TTF 檔:

<system.webServer>
<staticContent>
  <remove fileExtension=".ttf"/>
  <mimeMap fileExtension=".ttf" mimeType="application/font-sfnt"/>
</staticContent>
</system.webServer>

彙整專案總共加入以下項目:

  • Contents\fonts\DS-DIGI.TTF
    LCD 字型檔
  • Contents\webstats.css
    Views\WebStats\Index.cshtml 搭配樣式
  • Controllers\WebStatsController.cs
    顯示頁 Controller 及 AJAX 伺服器端
  • Models\SelfPerMonitor.cs
    CPU、記憶體計數器讀取模組
  • Views\WebStats\Index.cshtml
    顯示器介面
  • web.config
    增加 .ttf MIME Type 設定

為了測試,我在 Views/Home/Index.cshtml 放了兩顆鈕結合後端 AJAX 呼叫,一顆跑迴圈吃光單核 CPU 五秒,另一顆負責消耗 8MB byte[],可以有效改變 CPU% 及記憶體用量。

[HttpPost]
public ActionResult BusyLoop()
{
    var timeout = DateTime.Now.AddSeconds(5);
    while (DateTime.Now.CompareTo(timeout) < 0)
    {
        var newGuid = Guid.NewGuid();
    }
    return Content("OK");
}

static List<byte[]> MemoryMonster = new List<byte[]>();

[HttpPost]
public ActionResult UseMemory()
{
    byte[] buff = new byte[8 * 1024 * 1024];
    for (var i = 0; i < buff.Length; i++) buff[i] = (byte)(i % 256);
    MemoryMonster.Add(buff);
    //Sleep 5 seconds to keep the memory space "active"
    Thread.Sleep(5000);
    return Content(MemoryMonster.Count().ToString());
}

以下展示靠跑迴圈讓 CPU 使用量衝到 80% 以上:

記憶體數字因為抓的是 Active Private Working Set 只看佔用實體記憶體部分,Windows 會視狀況動態調配,故不一定會百分之百反映程式耗用空間,但大致仍可看到數字以 8MB 為單位增加的趨勢:

另外補充一點,網站部署到 IIS 時會遇到 PerformanceCounter 存取被拒錯誤(Access to the registry key 'Global' is denied.),要將 IIS AppPool 帳號加入 Performance Monitor Users 群組並 IISRESET。

可執行程式碼已丟上 Github (放在 prototype 分支),有興趣的同學可以拉回去玩玩,Have Fun!

Sharing my simple ASP.NET MVC exmaple to show web application's CPU load and memory usage.


Comments

Be the first to post a comment

Post a comment