1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Linux向けバックアップツール「GXBackup」を自作した

1
Posted at

はじめに

Linux デスクトップ環境で重要なデータのバックアップをとる際、rsync コマンドは非常に頼もしい存在です。
しかし、バックグラウンドで rsync を回していると、大量のファイル I/O や CPU リソースの消費によって重い作業(ビルドや動画エンコード、ゲーム等)の最中にPCの操作感が重くなるという問題に直面することがあります。

そこで今回、Qt6 (C++17) を用いて、rsync の堅牢性を活かしつつ**「PCの負荷が高くなったら自動でプロセスを一時停止し、空いたら再開する (Smart Auto-Pause)」**機能を備えた Linux 向けバックアップ GUI アプリ 「GXBackup」 を開発しました。

本記事では、GXBackup の機能概要と、Qt6 / Linux C++ での実装テクニック(POSIXシグナルによるプロセス制御、/proc のリアルタイム監視、rsync進捗パースなど)を解説します。


🌟 概要と特徴

GXBackup は Linux 環境向けに構築された C++17 / Qt6 製のオープンソース GUI バックアップツールです。

  • バックエンドに rsync を採用:パーミッションや拡張属性 (ACL/xattr) の維持、ハードリンクの保持など rsync の実績と信頼性をそのまま活用。
  • スマート負荷制限 (Smart Auto-Pause):CPU 使用率や Load Average を監視し、設定閾値を超えたら SIGSTOP で即座にバックアップを自動待機、負荷が下がったら SIGCONT でシームレス再開。
  • nice / ionice 統合:デフォルトで CPU / I/O スケジューリング優先度を最低値 (Idle / Nice 19) に設定。
  • システムトレイ常駐 & 追いつき実行 (Catch-Up):タスクトレイに常駐し、PC起動時にスケジュール時刻を過ぎていた場合も次回起動時に「追いつきバックアップ」を実行。

🏗 アーキテクチャ設計

GXBackup は Qt のシグナル・スロット機構を活用し、GUI 層と Core (バックエンド処理) 層を明確に分離した設計を行っています。


💡 実装のポイント

1. QProcess と POSIX シグナルによる rsync の一時停止・再開制御

Qt の QProcess にはプロセスの起動・停止・強制終了メソッドはありますが、プロセスの「一時停止」機能は標準で用意されていません。
GXBackup では、Linux の POSIX シグナル SIGSTOPSIGCONT::kill() システムコールで送信することで、rsync をプロセスレベルで安全に一時停止・再開させています。

// RsyncProcess.cpp より抜粋
#include <csignal>
#include <unistd.h>

void RsyncProcess::pauseBackup()
{
    if (m_state != Running) return;

    qint64 pid = m_process.processId();
    if (pid > 0) {
        // PIDに対して SIGSTOP を送信してプロセスを一時停止
        ::kill(static_cast<pid_t>(pid), SIGSTOP);
        setState(Paused);
    }
}

void RsyncProcess::resumeBackup()
{
    if (m_state != Paused) return;

    qint64 pid = m_process.processId();
    if (pid > 0) {
        // PIDに対して SIGCONT を送信してプロセスを再開
        ::kill(static_cast<pid_t>(pid), SIGCONT);
        setState(Running);
    }
}

また、バックアップ実行時には nice および ionice コマンドを経由させることで、CPU スケジューリング優先度を最低 (nice -n 19)、I/O 優先度を Idle クラス (ionice -c 3) に設定し、他アプリのレスポンスを損なわない配慮をしています。

QString program = "nice";
QStringList args;
args << "-n" << "19" << "ionice" << "-c" << "3" << "rsync";
args << "-aHAX" << "--delete" << "--info=progress2";

2. /proc/stat & /proc/loadavg の直接パースによる軽量な負荷監視

サードパーティ依存を増やさず Linux ネイティブにシステム負荷を監視するため、/proc/stat/proc/loadavgQTimer で定期パースしています。

CPU 使用率の計算 (/proc/stat)

/proc/statcpu 行には、システム起動時からの Tick 数(user, nice, system, idle, iowait 等)が記録されています。直前との差分から CPU 使用率を正確に割り出します。

// SystemMonitor.cpp より抜粋
double SystemMonitor::readCpuUsage()
{
    std::ifstream file("/proc/stat");
    if (!file.is_open()) return 0.0;

    std::string cpuStr;
    file >> cpuStr; // "cpu"

    unsigned long long user, nice, system, idle, iowait, irq, softirq, steal;
    if (!(file >> user >> nice >> system >> idle >> iowait >> irq >> softirq >> steal)) {
        return 0.0;
    }

    unsigned long long idleTicks = idle + iowait;
    unsigned long long totalTicks = user + nice + system + idle + iowait + irq + softirq + steal;

    // 前回の測定値との差分
    unsigned long long totalDiff = totalTicks - m_prevTotalTicks;
    unsigned long long idleDiff = idleTicks - m_prevIdleTicks;

    m_prevTotalTicks = totalTicks;
    m_prevIdleTicks = idleTicks;

    if (totalDiff == 0) return 0.0;

    double cpuPercent = 100.0 * (1.0 - static_cast<double>(idleDiff) / static_cast<double>(totalDiff));
    return std::clamp(cpuPercent, 0.0, 100.0);
}

Load Average 1分値の取得 (/proc/loadavg)

double SystemMonitor::readLoadAverage()
{
    QFile file("/proc/loadavg");
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        QString line = in.readLine();
        QStringList parts = line.split(QChar(' '), Qt::SkipEmptyParts);
        if (!parts.isEmpty()) {
            bool ok = false;
            double load1 = parts[0].toDouble(&ok);
            if (ok) return load1;
        }
    }
    return 0.0;
}

BackupController は、この SystemMonitor から loadStateChanged(bool highLoad) シグナルを受け取ることで、自動一時停止 (pauseBackup) と自動再開 (resumeBackup) のオーケストレーションを行います。


3. rsync --info=progress2 のリアルタイムログバッファと Regex 解析

rsync--info=progress2 オプションは、全体の進捗率・転送速度・残り時間を1行の出力としてキャリッジリターン (\r) 更新で出力します。

QProcess から読み込む標準出力をキャリッジリターン分割対応のリングバッファで処理し、正規表現を用いて進行状況を取り出しています。

// RsyncProcess.cpp より抜粋
bool RsyncProcess::parseProgressLine(const QString &line)
{
    // 例: "  1,234,567  45%   12.34MB/s    0:00:15 (xfr#12, to-chk=45/100)"
    static const QRegularExpression re(R"(\s*([\d,]+)\s+(\d+)%\s+([\d\.\w/]+)\s+([\d:]+))");
    QRegularExpressionMatch match = re.match(line);

    if (match.hasMatch()) {
        QString transferred = match.captured(1);
        int percent = match.captured(2).toInt();
        QString speed = match.captured(3);
        QString remainingTime = match.captured(4);

        // GUI更新用シグナルを発行
        emit progressUpdated(percent, speed, transferred, remainingTime);
        return true; // 進捗行として消費
    }
    return false; // 通常ログ行
}

💻 動作環境

必須環境

  • OS: Linux (Ubuntu, Debian, Fedora, Arch Linux 等)
  • コンパイラ: C++17 対応 (GCC 8+ / Clang 7+)
  • フレームワーク: Qt 5 または Qt 6
  • 依存ツール: rsync

📝 おわりに

rsync という強力な CLI ツールをベースにしつつ、Qt6 の GUI と Linux のプロセス制御(POSIX シグナル、/proc モニタリング)を組み合わせることで、「重くならないバックアップツール」 を実現できました。

オープンソースとしてリポジトリを公開していますので、ぜひ星やフィードバックをいただけると励みになります!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?