RTOSのCPU使用率を測る

「この機能を追加しても処理は間に合う?」「いまCPUにどれだけ余裕がある?」。リアルタイムシステムの設計判断には、CPU使用率という数字が欠かせません。この記事ではFreeRTOSを例にとり、RTOS環境でCPU使用率を測る2つの方法(ランタイム統計と、アイドルフックを使った軽量な常時監視)を、仕組みから実装、計測時の注意点まで解説します。ほかのRTOSでも「アイドル時間を測る」という考え方は共通ですが、設定方法やAPI名は異なります。

考え方は1つ:アイドル時間を測る

RTOSは、実行すべきタスクがないときアイドルタスク(他に実行可能なタスクがないときだけ動作する最低優先度のタスク)を実行します。一定期間のうちアイドルタスクが動いていた時間の割合をアイドル率(=アイドルタスクの実行時間割合)と呼びます。CPU使用率は100%からアイドル率を引いた値です。以降、CPUの余裕は「余裕(アイドル率)」という言い方で統一します。余裕とCPU使用率の関係は「余裕(%)=100-CPU使用率(%)」という式で表せます。多くの計測方法は、アイドル時間(または同等の空き時間)を推定する発想に基づきます。

方法1:RTOSのランタイム統計(FreeRTOSの例、タスク別の内訳が見える)

多くのRTOSには、タスクごとの累積実行時間を集計する機能が用意されています。ここではFreeRTOSを例に説明します。仕組みは、タスクが切り替わるたびに高分解能タイマの値を読み、前回切り替え時との差分をそのタスクの実行時間として積算していくものです。有効化の手順は次のとおりです。

  • FreeRTOSConfig.hで configGENERATE_RUN_TIME_STATS を1にする。
  • 計測用タイマを用意し、portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()(タイマ初期化)と portGET_RUN_TIME_COUNTER_VALUE()(現在値取得)の2つのマクロを定義する。タイマの周波数は、少なくともティック(tick)の10倍以上とし、推奨は10〜100倍程度(環境により調整)です。ティック1 kHzなら10 kHz以上のカウンタが目安です。
  • 表形式の出力が欲しい場合は configUSE_STATS_FORMATTING_FUNCTIONS も1にし、vTaskGetRunTimeStats() を呼ぶと、タスク名・累積時間・割合の一覧が文字列で得られます。FreeRTOSのバージョンにより関数名が異なり、旧名はvTaskGetRunTimeStats()、新名はvTaskGetRunTimeStatistics()です(いずれもconfigGENERATE_RUN_TIME_STATSとconfigUSE_STATS_FORMATTING_FUNCTIONSの設定が必要です)。

注意点も公式に明記されています。vTaskGetRunTimeStats() は内部で uxTaskGetSystemState() を使って情報を取得し、そのあいだスケジューラがサスペンドされます(デバッグ用途の関数です)。さらに取得結果をログ出力する場合は、出力処理が長いほどリアルタイム性への影響が加わります。そのため、開発時のデバッグ補助と割り切り、製品の通常動作中には常用しない設計にします。常時監視したい場合は、周期的に差分だけを取って短いログにする、計測専用ビルドでのみ有効化する、といった方法を採ります。また累積カウンタが32ビットの符号なしの場合、2³²(=4,294,967,296)を10 kHzで割ると約429,497秒(≒4.97日)で一周します。カウンタが何ビットかはポート実装に依存し得るため、使用環境で確認してください。長期稼働での連続監視には次の簡易法を組み合わせます。

方法2:アイドルフックで軽量に常時監視する

アイドルタスクから毎回呼ばれるフック関数(vApplicationIdleHook、configUSE_IDLE_HOOK=1で有効)でカウンタを増やし、周期ごとに「完全に暇なときのカウント値」と比べる方法です。製品に入れっぱなしにできる軽さが持ち味です。

static volatile uint32_t idle_count;
void vApplicationIdleHook(void)
{
    idle_count++;                  /* 暇なあいだ回り続ける */
}
/* IDLE_COUNT_FULL:負荷ゼロ状態で1秒間に到達するカウント値。
   事前に実測して定数化しておく                              */
void cpu_monitor_task(void *arg)
{
    (void)arg;
    static uint32_t prev_count = 0u;
    for (;;) {
        vTaskDelay(pdMS_TO_TICKS(1000u));
        uint32_t now  = idle_count;               /* volatile はいったんローカルへ */
        uint32_t diff = now - prev_count;         /* 符号なしの差分はラップしても正しい */
        prev_count = now;
        uint32_t idle_pct = (uint32_t)((uint64_t)diff * 100u / IDLE_COUNT_FULL);  /* 100倍は64ビットで計算しオーバーフローを防ぐ */
        if (idle_pct > 100u) idle_pct = 100u;
        log_info("CPU usage: %u%%", (unsigned)(100u - idle_pct));
    }
}

基準値 IDLE_COUNT_FULL は、全アプリケーションタスクを停止し周期タスクも最小にした負荷ゼロの状態で1秒間計測し、複数回の平均を取って決めます。コンパイラの最適化設定やクロックが変わると変化するため、ビルド条件を変えたら取り直します。アイドルフック内では処理を増やさない(ブロックする呼び出しをしない)こともRTOS共通の作法です。ティックレスアイドル(tickless idle)は、実行可能なタスクがないあいだ、定期的なティック割り込みを停止してCPUを低消費電力状態(スリープ)に移行させる省電力機能です。これを有効にすると、アイドルタスクが低消費電力スリープに入るため、スリープ中はidle_countが増えません。したがって「ループ回数=時間」という前提が崩れ、この方式は成立しません。常時監視したい場合はティックレスアイドルを無効にするか、低消費電力タイマなど別の時間ベースでアイドル時間を積算してください。

8ビット・16ビットMCUでは、32ビット変数の読み書きが分割アクセスになり得ます。その場合は idle_count の読み出しをクリティカルセクション(taskENTER_CRITICAL()/taskEXIT_CRITICAL() など)で保護するか、クリティカルセクション内でローカル変数へコピーしてから差分を計算してください。

計測時の注意点

  • 最悪条件で測る:通信のバースト、全チャネル同時動作、エラー処理経路など、実運用で最も重い状態を再現して測らないと意味のある数字になりません。
  • 割り込みの時間は見えにくい:多くの実装ではISR中も統計用タイマが進むため、ハンドラの消費時間が「実行中だったタスク」の実行時間に含まれて見える場合があります(詳細はポート実装に依存します)。割り込み負荷が大きいシステムでは、ハンドラの実行時間を別途計測します(「割り込みレイテンシとジッタの計測」参照)。
  • 計測自体のコスト:ランタイム統計はコンテキストスイッチごとにタイマ読み出しが入ります。シビアな系では、開発時のみ有効化する切り替えをビルド構成に用意します。
  • カウンタの一周:累積方式は必ずどこかでオーバーフローします。周期リセット型の簡易法と併用するか、差分で扱う設計にします。

数字をどう使うか

CPU使用率は「測って終わり」ではなく、判断基準として運用してこそ価値が出ます。たとえば、次のような形で運用します。
最悪条件のピーク使用率に上限(設計目標の一例として7〜8割。適切な値は応答時間の要件やバースト性によって変わるため、システムごとに定めます)を設けてリリース判定に組み込む。
新機能の見積もり時に現状の余裕(アイドル率)を確認する。
量産後も定期ログとして残し、経年の変化(通信リトライ増加など)の早期発見に使う。
タスク別の内訳はボトルネック特定や優先度設計の見直し(「RTOSのタスク設計」、「RTOSスケジューリング方式の比較」参照)の出発点になります。

まとめ

CPU使用率の計測は「アイドル時間を測る」が本質です。開発中はランタイム統計でタスク別の内訳を把握し、製品にはアイドルフックの軽量監視を仕込みます。この二段構えで、リアルタイムシステムの余裕を勘ではなく数字で語れるようになります。

組み込みソフトの世界 トップへ戻る