let user set ionice in settings
This commit is contained in:
@@ -4,6 +4,54 @@ import sys
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def _get_ionice_setting() -> str:
|
||||
"""Reads the user's preferred I/O scheduling class from settings."""
|
||||
try:
|
||||
from app.db.database import SessionLocal
|
||||
from app.db import models
|
||||
|
||||
with SessionLocal() as db_session:
|
||||
record = (
|
||||
db_session.query(models.SystemSetting)
|
||||
.filter(models.SystemSetting.key == "ionice_level")
|
||||
.first()
|
||||
)
|
||||
if record and record.value in ("idle", "best-effort", "realtime"):
|
||||
return record.value
|
||||
except Exception:
|
||||
pass
|
||||
return "idle" # Default: be the most polite
|
||||
|
||||
|
||||
def set_process_priority(level: str):
|
||||
"""Adjusts CPU and I/O priority of the current process.
|
||||
|
||||
Args:
|
||||
level: "background" for lowest priority (ionice idle + nice 19),
|
||||
"normal" to reset (ionice best-effort + nice 0).
|
||||
"""
|
||||
try:
|
||||
import psutil
|
||||
|
||||
p = psutil.Process(os.getpid())
|
||||
if level == "background":
|
||||
ionice_level = _get_ionice_setting()
|
||||
if hasattr(p, "ionice"):
|
||||
if ionice_level == "idle":
|
||||
p.ionice(psutil.IOPRIO_CLASS_IDLE) # type: ignore[attr-defined]
|
||||
elif ionice_level == "realtime":
|
||||
p.ionice(psutil.IOPRIO_CLASS_RT) # type: ignore[attr-defined]
|
||||
else:
|
||||
p.ionice(psutil.IOPRIO_CLASS_BE) # type: ignore[attr-defined]
|
||||
p.nice(19)
|
||||
else:
|
||||
if hasattr(p, "ionice"):
|
||||
p.ionice(psutil.IOPRIO_CLASS_BE) # type: ignore[attr-defined]
|
||||
p.nice(0)
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not set process priority to '{level}': {e}")
|
||||
|
||||
|
||||
def get_path_uuid(path: str) -> str | None:
|
||||
"""Attempts to retrieve a stable hardware/filesystem UUID for a given path."""
|
||||
if not os.path.exists(path):
|
||||
|
||||
@@ -307,6 +307,10 @@ class ArchiverService:
|
||||
)
|
||||
JobManager.add_job_log(job_id, f"Starting backup to {media_record.identifier}")
|
||||
|
||||
from app.core.utils import set_process_priority
|
||||
|
||||
set_process_priority("background")
|
||||
|
||||
workload_batch = self.assemble_backup_batch(db_session, media_id)
|
||||
if not workload_batch:
|
||||
JobManager.add_job_log(job_id, "No files require backup")
|
||||
@@ -744,6 +748,7 @@ class ArchiverService:
|
||||
logger.exception(f"Archival failed: {e}")
|
||||
JobManager.fail_job(job_id, str(e))
|
||||
finally:
|
||||
set_process_priority("normal")
|
||||
# Clean up any residual staging files
|
||||
for chunk_file in os.listdir(self.staging_directory):
|
||||
if chunk_file.startswith("backup_") and chunk_file.endswith(".tar"):
|
||||
|
||||
@@ -169,23 +169,6 @@ class ScannerService:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
def _set_process_priority(self, level: str):
|
||||
"""Adjusts the CPU and I/O priority of the current process."""
|
||||
try:
|
||||
p = psutil.Process(os.getpid())
|
||||
if level == "background":
|
||||
if hasattr(p, "ionice"):
|
||||
p.ionice(
|
||||
psutil.IOPRIO_CLASS_IDLE # ty: ignore[unresolved-attribute]
|
||||
)
|
||||
p.nice(19)
|
||||
else:
|
||||
if hasattr(p, "ionice"):
|
||||
p.ionice(psutil.IOPRIO_CLASS_BE) # ty: ignore[unresolved-attribute]
|
||||
p.nice(0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def compute_sha256(
|
||||
self, file_path: str, job_id: Optional[int] = None
|
||||
) -> Optional[str]:
|
||||
@@ -231,7 +214,9 @@ class ScannerService:
|
||||
JobManager.update_job(job_id, 0.0, "Starting system scan...")
|
||||
JobManager.add_job_log(job_id, "Starting system scan")
|
||||
|
||||
self._set_process_priority("normal")
|
||||
from app.core.utils import set_process_priority
|
||||
|
||||
set_process_priority("normal")
|
||||
with self._metrics_lock:
|
||||
self.files_processed = 0
|
||||
self.files_new = 0
|
||||
@@ -434,7 +419,9 @@ class ScannerService:
|
||||
with self._metrics_lock:
|
||||
self.is_hashing = True
|
||||
|
||||
self._set_process_priority("background")
|
||||
from app.core.utils import set_process_priority
|
||||
|
||||
set_process_priority("background")
|
||||
|
||||
try:
|
||||
with SessionLocal() as db_session:
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
Upload,
|
||||
Terminal,
|
||||
Globe,
|
||||
Key
|
||||
Key,
|
||||
ChevronDown
|
||||
} from "lucide-svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import PageHeader from "$lib/components/ui/PageHeader.svelte";
|
||||
@@ -51,6 +52,7 @@
|
||||
let scanSchedule = $state("");
|
||||
let archivalSchedule = $state("");
|
||||
let notificationUrls = $state<string[]>([]);
|
||||
let ioniceLevel = $state("idle");
|
||||
|
||||
// Secrets keystore
|
||||
let secretsList = $state<string[]>([]);
|
||||
@@ -66,7 +68,8 @@
|
||||
globalExclusions,
|
||||
scanSchedule,
|
||||
archivalSchedule,
|
||||
notificationUrls
|
||||
notificationUrls,
|
||||
ioniceLevel
|
||||
}));
|
||||
|
||||
beforeNavigate((navigation: any) => {
|
||||
@@ -155,6 +158,7 @@
|
||||
if (data.schedule_scan) scanSchedule = data.schedule_scan;
|
||||
if (data.schedule_archival) archivalSchedule = data.schedule_archival;
|
||||
if (data.notification_urls) notificationUrls = JSON.parse(data.notification_urls);
|
||||
if (data.ionice_level) ioniceLevel = data.ionice_level;
|
||||
}
|
||||
|
||||
// Load secrets
|
||||
@@ -169,7 +173,8 @@
|
||||
globalExclusions,
|
||||
scanSchedule,
|
||||
archivalSchedule,
|
||||
notificationUrls
|
||||
notificationUrls,
|
||||
ioniceLevel
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error("Failed to load system configuration");
|
||||
@@ -188,7 +193,8 @@
|
||||
updateSettings({ body: { key: "global_exclusions", value: globalExclusions } }),
|
||||
updateSettings({ body: { key: "schedule_scan", value: scanSchedule } }),
|
||||
updateSettings({ body: { key: "schedule_archival", value: archivalSchedule } }),
|
||||
updateSettings({ body: { key: "notification_urls", value: JSON.stringify(notificationUrls) } })
|
||||
updateSettings({ body: { key: "notification_urls", value: JSON.stringify(notificationUrls) } }),
|
||||
updateSettings({ body: { key: "ionice_level", value: ioniceLevel } })
|
||||
]);
|
||||
|
||||
// Snapshot saved state
|
||||
@@ -199,7 +205,8 @@
|
||||
globalExclusions,
|
||||
scanSchedule,
|
||||
archivalSchedule,
|
||||
notificationUrls
|
||||
notificationUrls,
|
||||
ioniceLevel
|
||||
});
|
||||
|
||||
toast.success("System configuration committed");
|
||||
@@ -648,6 +655,24 @@
|
||||
|
||||
{:else if activeTab === 'system'}
|
||||
<div class="animate-in slide-in-from-bottom-4 duration-500 space-y-6">
|
||||
<Card class="p-5 shadow-xl">
|
||||
<SectionHeader title="I/O scheduling" icon={Cpu} class="mb-6 px-0" />
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<label class="text-xs font-medium text-text-secondary ml-1" for="ionice-level">Background job I/O priority</label>
|
||||
<div class="relative">
|
||||
<select id="ionice-level" bind:value={ioniceLevel} class="w-full h-10 bg-bg-primary border border-border-color rounded-xl px-4 pr-10 text-sm font-medium text-text-primary outline-none focus:ring-2 focus:ring-blue-500/20 transition-all appearance-none cursor-pointer">
|
||||
<option value="idle">Idle (only use I/O when system is free)</option>
|
||||
<option value="best-effort">Best-effort (normal scheduling)</option>
|
||||
<option value="realtime">Real-time (highest priority, requires root)</option>
|
||||
</select>
|
||||
<ChevronDown size={16} class="absolute right-3 top-1/2 -translate-y-1/2 text-text-secondary pointer-events-none" />
|
||||
</div>
|
||||
<p class="text-[10px] text-text-secondary leading-tight opacity-60">Applies to scan and backup jobs. Idle is recommended for production systems.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class="p-5 shadow-xl">
|
||||
<SectionHeader title="Index management" icon={Database} class="mb-6 px-0" />
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
Reference in New Issue
Block a user