WMI 與 Chart 控制項之應用例:取得並顯示系統資訊

有關 WMI 的文章和範例已經很多了,重點記錄一下就好。包括:如何使用 WMI 來做即時的系統監控,以及使用 Chart 控制項來顯示即時監控資訊。 (嗯...我也希望能夠直接用現成的工具,但是....)


2012-07-16 更新:後來,在重構我的 WMI 類別與介面時,我將本文中的靜態類別 WmiQueryHelper 改成了非靜態類別 WmiQuery,並且進一步用 C# 定義一些類別來包裝 WMI classes。結果後來又在網路上找到一篇文章,同時發現,自已的想法跟那篇文章還蠻像的。文章在此:How To: (Almost) Everything In WMI via C# - Part 3: Hardware

我的作法跟那篇文章的主要差別在於,我的 Reader 實作類別有很多個,而且是明白定義自己要的屬性(例如文中範例的 TotalMemoryKBytes 屬性),此作法的優點是寫 code 時會有 IntelliSense 提示,且各屬性為強型別,使用時無須再經過一次轉型或資料轉換。那篇文章則是以一個通用的 WMIReader 類別來讀取所有 WMI 類別的欄位,並且將全部的欄位塞入一個 IList<string> 中保存;此作法的好處就是更為「通用」,而且可以少寫許多自訂 WMI 類別屬性的程式碼;而其缺點則是由於欄位資料皆以字串來儲存,取用時可能需要自行轉換成適當的資料型別。此外,那篇文章所包裝的 WMI 類別將近 30 個,比我目前實作完的多太多了。需要使用 WMI 偵測系統各項資訊的朋友不妨參考該文,可能更省時間。

下圖是我的初版範例程式的執行結果:



如果你打開 Windows 的資源監視器,也會看到類似的東西。此折線圖的 X 軸分成 60 個單位,代表 60 秒,因為我要每秒取一次 CPU 和記憶體的使用率。第一次抓取的 CPU 和記憶體使用率會從折線圖的最右方出現,然後每隔一秒再取一次 sample,連成線段。於是,折線圖會每隔一秒整個往左移動一個單位。

這裡的圖表是用 .NET 4 的 Chart 控制項來呈現。用法就不細說了,請參考官方文件之入門教學,以及 Spline ChartSpline Area Chart

如果不喜歡這個控制項,或者想要自己動手寫一個,可以參考這篇文章:Simple Performance Chart

WMI

透過 WMI,你可以查詢到幾乎所有你想要知道的系統資訊,舉凡系統資源、網路通訊、磁碟檔案、MSMQ…等,什麼碗糕都有。

WMI 提供的 API 也很容易使用,你可以利用類似 SQL 的語法--叫做 WQL--來查詢你要的資訊。然而,就像對關連式資料庫查詢一樣,如果資料庫裏面的 table 數量有好幾百個,你得先知道 table 的名稱,才有辦法查到你想要的資料。WMI 也是這樣,你得知道哪些 WMI Classes有你需要的資訊。而且,查詢結果可能包含很多欄位,所以你還得知道那些欄位是你想要的。取得欄位值之後,又可能得做些運算,才能得出有意義的數值,例如 CPU 的總使用率。

所以,如果你也需要使用 WMI,底下這幾個工具和資源都會很有用:

知道有甚麼工具可用,以及去哪裡找資源,剩下寫程式的部分就容易多了。底下是一個簡易的輔助工具類別,可向某主機(hostName 參數)查詢指定的 WMI class( className 參數)。使用前,專案要先加入組件參考:System.Management.dll,程式碼要引用命名空間 System.Management。

    public static class WmiQueryHelper
    {
        public static void Run(string hostName, string userName, string password, IWmiQueryResultReader reader)
        {
            StringBuilder sb = new StringBuilder();

            if (String.IsNullOrEmpty(hostName))
            {
                hostName = "localhost";
            }

            string scopeString = String.Format(@"\\{0}\root\cimv2", hostName);

            ManagementScope scope = new ManagementScope(scopeString);

            if ("127.0.0.1".Equals(hostName) == false && "localhost".Equals(hostName) == false)
            {
                ConnectionOptions co = new ConnectionOptions();
                co.Username = userName;
                co.Password = password;
                co.Authentication = AuthenticationLevel.None;
                co.Impersonation = ImpersonationLevel.Impersonate;
                scope.Options = co;
            }

            scope.Connect();

            ObjectQuery query = new ObjectQuery(reader.QueryString);

            using (ManagementObjectSearcher moSearcher = new ManagementObjectSearcher(scope, query))
            {
                ManagementObjectCollection results = moSearcher.Get();
                reader.Read(results);
            }           
        }
    }


其中的 Run 方法需要傳入四個參數。前三個參數都很容易明白,就不多解釋。最後一個參數的型別是 IWmiQueryResultReader 介面,目的是為了將讀取查詢結果的動作抽離出去。也就是說,我們可以有好多個實作此介面的類別,不同的類別負責解讀不同的查詢結果,並提供相關的屬性,例如 CPU 的實作類別可能就會提供 CpuID、Name、NumberOfCores 等屬性。

IWmiQueryResultReader 介面的原型如下:

    public interface IWmiQueryResultReader
    {
        string QueryString
        { 
            get; 
        }

        void Read(ICollection results);
    }

然後,我們可以為各種 WMI Classes 撰寫不同的 reader 實作類別。例如解讀 CPU 資訊的 WmiProcessor,取得記憶體的 WmiMemory,取得 MSMQ 狀態的,也許就命名為 WmiMsmq。

底下僅列出 WmiMemory 類別的部分程式碼:

    public class WmiMemory : IWmiQueryResultReader
    {
        public string QueryString
        {
            get { return "select TotalVisibleMemorySize, TotalVisibleMemorySize from Win32_OperatingSystem"; }
        }

        public void Read(ICollection results)
        {
            foreach (ManagementObject mo in results)
            {
                TotalMemoryKBytes = Convert.ToInt64(mo["TotalVisibleMemorySize"]);
                AvailableMemoryKBytes = Convert.ToInt64(mo["FreePhysicalMemory"]);
            }
        }

        public long TotalMemoryKBytes
        {
            get;
            private set;
        }

        public long AvailableMemoryKBytes
        {
            get;
            private set;
        }

        public int Usage
        {
            get
            {
                return (int) UsedMemoryKBytes * 100 / TotalMemoryKBytes;
            }
        }
    }

這樣的設計不見得很好,簡單的場合或作為參考範例還算堪用。

喔對了,抓取資料(透過 WMI)和顯示資料(利用 Chart 控制項)這兩項工作,我是用不同的執行緒來分別處理。如果用同一條執行緒,UI 會變得遲緩,尤其當你查詢的目標是遠端主機的時候,可能更明顯。

順便附上取得 CPU 總負載率的 WQL:

select PercentProcessorTime from Win32_PerfFormattedData_PerfOS_Processor where Name='_Total'


延伸閱讀

沒有留言:

技術提供:Blogger.
回頂端⬆️