パフォーマンス情報取得とCOMポート経由での通信の両方が動いたら、後は2つのプログラムを合体させれば目的のプログラムが完成である。ということで、2つを繋ぎ合わせて図1の様な構造のプログラムを作成してみた。こちらのソースをList 1に示す。

図1

もっとも素直に合体、といっても多少のテクニック(というか、策)は必要なので、そのあたりをちょっとご紹介したい。

includeに関しては、2つのプログラムで指定したものをそのまま取り込んでいるだけである。これは変数宣言もほぼ同じであるが、

  • unicodeBuf[]とかcharInLengthは無い : キーボード入力の必要はなくなったので、これらのバッファは必要ない
  • double procLoad[4]が追加 : これは後述
  • char transferChar[21][2]も追加 : これも後述

というあたりがちょっと変わっている点である。

さてプログラム自身は簡単なもので、COMポートのOpenや設定は377回で説明したままだし、パフォーマンスカウンタへの設定の仕方などは375回で説明した通りである。

ということで相違点であるが、まずはPdhCollectQueryData()を呼び出した後の処理である。375回で説明した方式だと、取得したCPU負荷をCPU別にPdhGetFormattedCounterValue()経由で変換、即画面表示しているが、今回の場合4つ分の負荷値をまとめて送ったほうが都合が良い(最後の'z'まで含めて都合5回、1バイトずつCOMポート経由で送るという実装もあるが、これはいかにも負荷が高そうだ)。そこで画面表示するのではなく一度配列に格納しておき、後でまとめて送る形にした。この一度格納するためのものがprocLoad[]である。実を言えば今回程度の精度だと、倍精度浮動小数点である意味は殆どなく、PDH_FMT_LONGを指定して整数にしてしまってもいいと気もするのだが、後述のプロトコルの処理の関係でこうしている。また格納後の丸めであるが、浮動小数点を最終的に整数にするときには多少の誤差が入ることもあり、100%の場合に100.0として後述の演算をしたとき、正しく21になるかどうか怪しいということで、少しだけ加減している。

さて、これを画面に表示する場合場合は単に_tprintf()で表示すれば済むのだが、COMポート経由に送るほうはもう一捻りする必要がある。通信プロトコルは370回で触れたとおりだから、実際の数値を文字コードに直す必要がある。これにはいくつか方法があるが、今回はtransferChar[]というテーブルを使うことにした。

まず各CPUの負荷に2.49(本当なら2.4999999...なのだが、まぁこの程度の精度で十分だろうと端折っている)を足して5.0で割ったものを整数化している。これによりCPU負荷が0~21の値に正規化される形だが、この値をIndexとしてtransferChar[]のテーブルを引くことで、送信すべき文字列を特定するという形になっている。プログラムの作りとしてはあまり美しくない(どうせならArduino側と同じように、オフセット値を足してASCIIコードに直接変換した方がオーバーヘッドは少ない)のだが、CPU能力やメモリ資源が限られているArduinoと、(Arduinoに比べると)資源が有り余るPCでは多少作法が異なっていても良いだろう。ということで、説明しやすさを優先してこんなコードとなっている。このあたり、気に入らないところはどんどん手を入れていただければと思う。

さて、これをビルドする際には376回で説明した通り、リンクのライブラリにPdh.libの指定を忘れずに行ってほしい。これさえ忘れなければ、そのままリンクが通る筈だ。実行すると、画面表示にあわせてLEDが点滅する筈だ。ただ負荷を掛けないと、表示が殆どなかったりするのでちょっとさびしい。デバッグの際には、1秒間待機の、

Sleep(1000);

を、

Sleep(100);

とかにして0.1秒間隔で表示すると、だいぶにぎやかになる(もっともこのあたりはPCによって変わってくるが)。

ということでとりあえずPC側のプログラムは完成といえば完成なのだが、いろいろやっつけ感があることは否めない。なにより、いまさらWin32コンソールプログラムはないだろ、という意見は当然あるだろう。ということでこのあたりをもう少し改善してみたいと思う。

(続く)

List 1:

// Display_Console.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#include <tchar.h>
#include <winperf.h>
#include <pdh.h>
#include <pdhmsg.h>
#include <string.h>

#define BUFSIZE 80
#define COMPORT _TEXT("COM6")
#define BAUDRATE 9600

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE          hCOM;               // COMポートアクセス用Handle
    char            ansiBuf[BUFSIZE];   // 転送用バッファ(ansi)
    unsigned long   charOutLength;      // 出力文字長
    HQUERY          hQuery;             // パフォーマンスカウンタ取得用Query
    HCOUNTER        hCounter[100];      // Queryの結果取得用バッファ
    PDH_FMT_COUNTERVALUE    FmtValue;   // Queryの結果変換用バッファ
    TCHAR           *cp;                // 作業用ポインタ
    TCHAR           *PathList;          // QueryのPath格納ポインタ
    DWORD           PathListSize;       // Queryのサイズ
    PDH_STATUS      status;             // Query取得ステータス
    int             numProcessor;       // プロセッサ数
    double          procLoad[4];        // 各プロセッサの負荷値を格納
    int             lpCnt;              // 汎用ループカウンタ
    char            transferChar[21][2] // 転送コード
                    = { "0","1","2","3","4","5","6","7","8","9",":",
                        ";","","?","@","A","B","C","D" };

    // RS232CポートへのHandle作成
    hCOM = CreateFile(COMPORT,
                      GENERIC_READ | GENERIC_WRITE,
                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                      NULL,
                      OPEN_EXISTING,
                      0,
                      NULL);
    if (hCOM == INVALID_HANDLE_VALUE)
    {
        CloseHandle(hCOM);
        _tprintf(_TEXT("COM port open error\n"));
        _exit(0);
    }

    // 現在のRS232Cポートの通信パラメータ取得
    DCB dcbBuf;
    dcbBuf.DCBlength = sizeof(DCB);

    if (!GetCommState(hCOM, &dcbBuf))
    {
        CloseHandle(hCOM);
        _tprintf(_TEXT("COM port access error\n"));
        _exit(0);
    }

    // パラメータ設定
    dcbBuf.BaudRate = BAUDRATE;     // 通信速度
    dcbBuf.ByteSize = 8;            // データ長
    dcbBuf.Parity = NOPARITY;       // パリティビット
    dcbBuf.StopBits = ONESTOPBIT;   // ストップビット

    // RS232Cポートにパラメータ設定
    if (!SetCommState(hCOM, &dcbBuf))
    {
        CloseHandle(hCOM);
        _tprintf(_TEXT("COM port access error\n"));
        _exit(0);
    }

    // 2秒ほど待機
    Sleep(2000);

    // Queryハンドル生成
    PdhOpenQuery(NULL, 0, &hQuery);

    PathList = NULL;
    PathListSize = 0;
    // ワイルドカード指定でPreocessor Timeを指定(1回目)
    status = PdhExpandWildCardPath(NULL, TEXT("\\Processor(*)\\% Processor Time"), PathList, &PathListSize, NULL);
    if (status == PDH_MORE_DATA)
    {
        // ワイルドカード展開に必要なサイズのバッファを確保
        PathList = (TCHAR *)calloc(PathListSize+1, sizeof(TCHAR));

        // ワイルドカード指定でPreocessor Timeを指定(2回目)
        status = PdhExpandWildCardPath(NULL, TEXT("\\Processor(*)\\% Processor Time"), PathList, &PathListSize, NULL);
        for(cp = PathList, numProcessor = 0; *cp; cp += _tcslen(cp)+1)
        {
            // _Totalの数字は要らないので抜き、その他のカウンタをAddCounterで登録
            if (!_tcsstr(cp, TEXT("_Total")))
            {
                status = PdhAddCounter(hQuery, cp, 0, &hCounter[numProcessor]);
                numProcessor++;
            }
        }

        while(TRUE)
        {
            // 1秒間待つ
            Sleep(1000);
            // データ取得
            PdhCollectQueryData(hQuery);
            for(lpCnt = 0; lpCnt < numProcessor; lpCnt++)
            {
                // データを配列に格納
                status = PdhGetFormattedCounterValue( hCounter[lpCnt], PDH_FMT_DOUBLE, NULL, &FmtValue );
                procLoad[lpCnt] = FmtValue.doubleValue;

                // 最大値を念のために99.9に丸める
                if (procLoad[lpCnt] > 99.9) procLoad[lpCnt] = 99.9;
            }

            // 画面にも表示
            _tprintf(_TEXT("%02d %02d %02d %02d\n"),
                     (int)procLoad[0],(int)procLoad[1],(int)procLoad[2],(int)procLoad[3]);

            // 出力向けにまとめて値を文字列変換
            sprintf_s(ansiBuf, "%01s%01s%01s%01sz",
                                transferChar[(int)((procLoad[0]+2.49)/5.0)],
                                transferChar[(int)((procLoad[1]+2.49)/5.0)],
                                transferChar[(int)((procLoad[2]+2.49)/5.0)],
                                transferChar[(int)((procLoad[3]+2.49)/5.0)]);

            // 変換した文字列をRS232Cポートに送る
            if( !WriteFile(hCOM, ansiBuf, 5, &charOutLength, NULL))
            {
                _tprintf(_TEXT("Data send error\n"));
                break;
            }
        }
    }

    //後処理
    CloseHandle(hCOM);

    return 0;
}