メモリ使用量を取得する

Rust の sysinfo クレート(0.38 系)で物理メモリの合計・使用量・空きとアプリ自身の使用量を読み、コマンドで JS に渡す。版ごとの API の違いと WebView 分の扱いも示す。

システム情報 対象: Tauri 2.x 更新日: 読了目安: 約8分 sys-004
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 2. バックエンドから実装する (Rust)
  4. 動作確認
  5. よくあるエラーと対処法
  6. OS ごとの違いと注意点
  7. 関連レシピ

「メモリが少ない PC では軽量モードを既定にする」「設定画面に空きメモリを表示する」「長時間動かしてアプリの使用量が増え続けていないか見る」といった用途では、メモリの量を数字で取る必要があります。Tauri 本体と公式プラグインにはメモリ量を返す API が無く、WebView の JS からも OS 全体の正確な値は取れないので、Rust の sysinfo クレートで読んでコマンドで JS に渡すのが定番です。sysinfo は版が上がるたびに API が変わりやすいため、このレシピは 0.38 系(0.38.4)を前提にしています。

前提条件

src-tauri で次を実行します。メモリとプロセスの情報だけなら、既定の機能を外して system だけにするとビルドが軽くなります。

cargo add sysinfo@0.38 --no-default-features --features system

src-tauri/Cargo.toml には次のように入ります。0.37 以降は Rust 1.88 以上が必要です。自作のコマンドを呼ぶだけなので、capabilities に権限を足す必要はありません。

[dependencies]
sysinfo = { version = "0.38", default-features = false, features = ["system"] }

読める値は次のとおりで、単位はすべてバイトです。

メソッド意味
total_memory()物理メモリの合計
available_memory()新しく使える量。「空き」として見せるのはこちら
free_memory()どこにも使われていない量。キャッシュの分を含まないので小さく出やすい
used_memory()使用中の量
total_swap() / used_swap()スワップの合計と使用量
Process::memory()そのプロセスが今使っている物理メモリ

1. フロントエンドから実装する (TypeScript)

Rust 側の get_memory_stats コマンド(2 章)を呼び、GiB に直して表示します。一定間隔で更新するときは setInterval ではなく、前回の取得が終わってから次を予約すると、処理が遅れても呼び出しが重なりません。

import { invoke } from '@tauri-apps/api/core';

type MemoryStats = {
  total: number; // バイト
  available: number;
  used: number;
  swapTotal: number;
  swapUsed: number;
  appRss: number | null; // このアプリ(Rust 側のプロセス)の使用量
};

const gib = (bytes: number) => `${(bytes / 1024 ** 3).toFixed(1)} GiB`;

export async function renderMemory(el: HTMLElement) {
  const m = await invoke<MemoryStats>('get_memory_stats');
  const pct = m.total > 0 ? Math.round((m.used / m.total) * 100) : 0;
  const app = m.appRss === null ? '' : ` ・ このアプリ ${Math.round(m.appRss / 1024 ** 2)} MiB`;
  el.textContent = `使用中 ${gib(m.used)} / ${gib(m.total)} (${pct}%) ・ 空き ${gib(m.available)}${app}`;
}

// 2 秒ごとに更新し、止めるための関数を返す
export function startMemoryMonitor(el: HTMLElement): () => void {
  let stopped = false;
  let timer: number | undefined;
  const tick = async () => {
    try {
      await renderMemory(el);
    } catch (e) {
      el.textContent = `取得に失敗しました: ${String(e)}`;
    }
    if (!stopped) timer = window.setTimeout(tick, 2000);
  };
  void tick();
  return () => {
    stopped = true;
    window.clearTimeout(timer);
  };
}

起動時に 1 回だけ読んで、搭載量に応じて既定の設定を変える使い方もできます。u64 の値は JSON の数値として届き、JS の数値で正確に扱える範囲を超えることはないので BigInt は要りません。

import { invoke } from '@tauri-apps/api/core';

// 物理メモリが 8 GiB 未満なら軽量モードを既定にする
const { total } = await invoke<{ total: number }>('get_memory_stats');
document.documentElement.classList.toggle('lite', total < 8 * 1024 ** 3);

2. バックエンドから実装する (Rust)

System::new() は何も読み込まない空の状態で作られ、この時点の値はすべて 0 です。refresh_memory() で RAM とスワップだけを読みます。古い記事でよく見る System::new_all() は全プロセスや CPU の情報まで集めるので時間がかかり、async でないコマンドはメインスレッドで動くため、呼ぶたびに画面が固まる原因になります。System は State に 1 つだけ持って使い回し、必要な部分だけ更新します。アプリ自身の使用量は、get_current_pid() で自分のプロセス ID を取り、そのプロセスのメモリ情報だけを更新して読みます。

use std::sync::Mutex;

use serde::Serialize;
use sysinfo::{get_current_pid, ProcessRefreshKind, ProcessesToUpdate, System};
use tauri::State;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct MemoryStats {
    total: u64,
    available: u64,
    used: u64,
    swap_total: u64,
    swap_used: u64,
    app_rss: Option<u64>,
}

struct SysInfo(Mutex<System>);

#[tauri::command]
fn get_memory_stats(state: State<'_, SysInfo>) -> Result<MemoryStats, String> {
    let mut sys = state.0.lock().map_err(|e| e.to_string())?;
    sys.refresh_memory(); // RAM とスワップだけを読み直す(プロセスや CPU は読まない)

    // 自分のプロセスのメモリ情報だけを更新する
    let app_rss = match get_current_pid() {
        Ok(pid) => {
            sys.refresh_processes_specifics(
                ProcessesToUpdate::Some(&[pid]),
                true,
                ProcessRefreshKind::nothing().with_memory(),
            );
            sys.process(pid).map(|p| p.memory())
        }
        Err(_) => None,
    };

    Ok(MemoryStats {
        total: sys.total_memory(),
        available: sys.available_memory(),
        used: sys.used_memory(),
        swap_total: sys.total_swap(),
        swap_used: sys.used_swap(),
        app_rss,
    })
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .manage(SysInfo(Mutex::new(System::new()))) // 空の状態で作り、コマンドで必要な部分だけ読む
        .invoke_handler(tauri::generate_handler![get_memory_stats])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

app_rss に入るのは Rust 側のプロセスの分だけです。ページの JS や DOM は WebView の別プロセスで動くため、この値には含まれません。ページ側の使用量は DevTools の Memory タブで調べます(メモリリークしていないか検査する)。CPU の使用率も同じ sysinfo で取れますが、2 回の計測の差から求めるので扱いが違います。CPU 使用率を常時監視する を参照してください。

動作確認

npm run tauri dev で起動し、startMemoryMonitor() に表示先の要素を渡すと、2 秒ごとに次のような表示に更新されます(メモリ 16 GB の Windows の例)。

使用中 11.3 GiB / 15.7 GiB (72%) ・ 空き 4.4 GiB ・ このアプリ 42 MiB

重いアプリを開くと空きが減り、閉じると戻ります。この例で合計が搭載量より少し少ないのは、ハードウェアが予約している分を除いた量が返るためです。

よくあるエラーと対処法

  • 値がすべて 0 になる: System::new() の後に refresh_memory() を呼んでいません。new() は何も読み込みません。
  • 古い記事のコードがコンパイルできない: SystemExt が見つからない趣旨のエラーは、0.30 でトレイトが廃止されたためで、use から外せば動きます。RefreshKind::new() は 0.33 で nothing() に改名されました。refresh_process(pid) は 0.31 で廃止され、refresh_processes_specifics() に ProcessesToUpdate::Some(&[pid]) を渡す形になり、0.32 で remove_dead_processes の引数も増えています。0.26 より前は単位が KB だったので、古いコードの計算は桁がずれます。
  • Rust のバージョンが足りない趣旨のビルドエラー: 0.37 以降は Rust 1.88 以上が必要です。rustup update で上げるか、sysinfo を古い版に固定します。
  • 呼ぶたびに画面が一瞬固まる: System::new_all() や refresh_all() をコマンドのたびに呼んでいます。refresh_memory() だけにするか、コマンドを async fn にしてメインスレッドから外します。

OS ごとの違いと注意点

  • Windows: free_memory() は available_memory() と同じ値を返します。アプリの memory() はタスクマネージャーの「メモリ」列とは数え方が違うので、一致しないことがあります。
  • Linux: free_memory() はファイルのキャッシュに使われている分を含まないため、長く動いている PC ほど小さく見えます。空きの表示には available_memory() を使います。
  • macOS: used_memory() と available_memory() の求め方は 0.38.3 で改善されており、版によって値が変わります。数字を記録して比べるなら sysinfo の版を固定します。Mac App Store で配布する場合は、ストアの規則に触れる機能を外す apple-app-store 機能を有効にします。
  • 共通: OS の種類や CPU アーキテクチャのように起動中に変わらない情報は、OS の種類・CPU アーキテクチャ・ホスト名を取得する の os プラグインで取れます。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する