「メモリが少ない 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 プラグインで取れます。
