システムの保守とかしてると、サーバのCPUとかメモリとかシステムのプロセスとかディスクの容量とか統計を取りたくなる。WindowsOSの話。パフォーマンスカウンタでいけるみたいな話はありますが、なんか使い勝手が悪いのと裏でずっと動かしてていいのかなんか微妙。あと結果のファイルもなんか微妙。

とゆーわけで自分でサービスを作った話。

パフォーマンスカウンタ使うには、最初にSystem.Managementを参照に追加しないとだめ。そしたら↓の感じで見たいカウンタ(項目)別に設定する。

//using System.Management;

//CPUの使用率
PerformanceCounter pc = new PerformanceCounter("Processor", "% Processor Time", "_Total", true);
Console.WriteLine(pc.NextValue());

で、どんな指定が出来るのかの一覧があんまない。環境によるってことかしらね。なので、何が使えるのか最初に↓の感じで一覧作ってCSVあたりにはいておく。

//PerformanceCounterCategoryを使う場合は↓のusingが必要
//using System.Diagnostics;
public static void ListUpCounters()
{
    using (StreamWriter wr = new StreamWriter(@".\パフォーマンスカウンタ一覧.csv", false, Encoding.GetEncoding(932)))
    {
        //カテゴリの一覧
        PerformanceCounterCategory[] pccates = PerformanceCounterCategory.GetCategories();
        wr.WriteLine(String.Format("カテゴリ,タイプ,オブジェ,単位,説明"));//一覧用ヘッダ
        foreach (PerformanceCounterCategory tmpcate in pccates)
        {
            //カテゴリのオブジェクト名
            wr.WriteLine(String.Format("\"{0}\",,,,\"{1}\"", tmpcate.CategoryName, tmpcate.CategoryHelp));
            try
            {
                //カテゴリのインスタンス一覧を取得
                string[] instnames = tmpcate.GetInstanceNames();
                //インスタンス名を出力
                foreach (string instanceName in instnames)
                {
                    wr.WriteLine(String.Format("{0},INSTANCE,\"{1}\"", tmpcate.CategoryName, instanceName));
                }

                //カウンタの一覧
                PerformanceCounter[] counters;
                if (instnames.Length > 0) counters = tmpcate.GetCounters(instnames[0]);
                else counters = tmpcate.GetCounters();

                //パフォーマンスカウンタ名を表示
                foreach (PerformanceCounter cnt in counters)
                {
                    wr.WriteLine(String.Format("{0},COUNTER,{1},\"{2}\",\"{3}\"", tmpcate.CategoryName, cnt.CounterName, cnt.CounterType, cnt.CounterHelp));
                }
            }
            //インスタンス情報なしはInvalidOperationExceptionらしい
            catch (InvalidOperationException)
            {
                wr.WriteLine(String.Format("インスタンス情報なし"));
            }
        }
    }

}

動かしたときのプロセスとかスレッドとかも全部出てくるので結構多いかも。。。

基本的にはCPUとメモリとディスクあたりを見る感じかしら。

カテゴリ カウンタ インスタンス 中身
Processor % Processor Time _Total CPU使用率(%)
Memory Available Bytes 利用可能メモリ(B)
Memory Pages/sec ページ数
LogicalDisk % Free Space _Total ディスク全体空率(%)
LogicalDisk Free Megabytes _Total ディスク全体空容量(MB)
LogicalDisk Current Disk Queue Length _Total ディスク待ちキュー数

インスタンスないやつは指定するときは引数無しで。

あと、カテゴリで「Process」の奴は個別のプロセスのCPU使用率とかも見れる。 インスタンスはプロセスの名前。同じ名前で何個もできる場合は「プロセス名#2」みたいな番号のつけ方される。固定で名称指定は無理だと思うので、↓の感じで、そのカテゴリのインスタンスを全部取って、プロセス名が含まれてたら使うみたいなやり方。

public static void proccessval()
{
    var catelist = PerformanceCounterCategory.GetCategories().ToList();
    foreach (PerformanceCounterCategory tmp in catelist.Where(n=>n.CategoryName=="Process"))
    {
        try
        {
            string[] instnames = tmp.GetInstanceNames();
            foreach (string instanceName in instnames)
            {
                if (instanceName.Contains("w3wp"))//例えばIIS
                {
                    PerformanceCounter pc = new PerformanceCounter("Process", "% Processor Time", instanceName, true);//CPU使用率(%)
                    Console.WriteLine(pc.NextValue());
                    pc = new PerformanceCounter("Process", "Working Set", instanceName, true);//使用メモリ(B)
                    Console.WriteLine(pc.NextValue());
                }
            }
        }
        catch
        { }
    }
}

ディスクについてはドライブ単位に見たい場合も↑と同じノリで出来る。あと、プロセスの場合、そのプロセスのコマンドラインが欲しかったりする場合がある。その場合は↑の奴に↓の感じで追加。

public static void proccessval()
{
    var catelist = PerformanceCounterCategory.GetCategories().ToList();
    foreach (PerformanceCounterCategory tmp in catelist.Where(n=>n.CategoryName=="Process"))
    {
        try
        {
            string[] instnames = tmp.GetInstanceNames();
            foreach (string instanceName in instnames)
            {
                if (instanceName.Contains("w3wp"))
                {
                    PerformanceCounter pc = new PerformanceCounter("Process", "% Processor Time", instanceName, true);//CPU使用率(%)
                    Console.WriteLine(pc.NextValue());
                    pc = new PerformanceCounter("Process", "Working Set", instanceName, true);//使用メモリ(B)
                    Console.WriteLine(pc.NextValue());

                    //プロセスIDをとってコマンドライン取得する
                    PerformanceCounter tmppc = new PerformanceCounter("Process", "ID Process", instanceName, true);
                    tmppc.NextValue();
                    Process ps = Process.GetProcessById(Convert.ToInt32(tmppc.NextValue()));
                    string proccessid = Convert.ToInt32(tmppc.NextValue()).ToString();

                    ManagementClass mc = new ManagementClass(new ManagementPath("Win32_Process"));
                    foreach (ManagementObject obj in mc.GetInstances())
                    {
                        if (obj["ProcessId"] != null && obj["CommandLine"] != null)
                        {
                            if (obj["ProcessId"].ToString() == proccessid)
                            {
                                string commandline = obj["CommandLine"].ToString();
                                string proccaption = obj["Caption"].ToString();
                            }
                        }
                    }

                }
            }
        }
        catch
        { }
    }
}

で、あとはこれをWindowsサービスで作って、とりたいカウンタの値を10秒単位とか1分単位とかでログファイルに書くみたいな感じ。

あーあと、毎回新しくカウンタ作るでもいいかもだけど重そう。。。最初に初期化だけして使いまわす場合、最初は存在したプロセスが途中でいなくなったりすると例外で落ちるので、その場合は初期化からまたやり直しみたいなロジック入れないとダメかな。あと途中でプロセス増えた場合もダメなので、定期的に初期化し直すように組まないとだめかしらね。