let user set ionice in settings
This commit is contained in:
@@ -4,6 +4,54 @@ import sys
|
|||||||
from loguru import logger
|
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:
|
def get_path_uuid(path: str) -> str | None:
|
||||||
"""Attempts to retrieve a stable hardware/filesystem UUID for a given path."""
|
"""Attempts to retrieve a stable hardware/filesystem UUID for a given path."""
|
||||||
if not os.path.exists(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}")
|
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)
|
workload_batch = self.assemble_backup_batch(db_session, media_id)
|
||||||
if not workload_batch:
|
if not workload_batch:
|
||||||
JobManager.add_job_log(job_id, "No files require backup")
|
JobManager.add_job_log(job_id, "No files require backup")
|
||||||
@@ -744,6 +748,7 @@ class ArchiverService:
|
|||||||
logger.exception(f"Archival failed: {e}")
|
logger.exception(f"Archival failed: {e}")
|
||||||
JobManager.fail_job(job_id, str(e))
|
JobManager.fail_job(job_id, str(e))
|
||||||
finally:
|
finally:
|
||||||
|
set_process_priority("normal")
|
||||||
# Clean up any residual staging files
|
# Clean up any residual staging files
|
||||||
for chunk_file in os.listdir(self.staging_directory):
|
for chunk_file in os.listdir(self.staging_directory):
|
||||||
if chunk_file.startswith("backup_") and chunk_file.endswith(".tar"):
|
if chunk_file.startswith("backup_") and chunk_file.endswith(".tar"):
|
||||||
|
|||||||
@@ -169,23 +169,6 @@ class ScannerService:
|
|||||||
return
|
return
|
||||||
time.sleep(0.1)
|
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(
|
def compute_sha256(
|
||||||
self, file_path: str, job_id: Optional[int] = None
|
self, file_path: str, job_id: Optional[int] = None
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
@@ -231,7 +214,9 @@ class ScannerService:
|
|||||||
JobManager.update_job(job_id, 0.0, "Starting system scan...")
|
JobManager.update_job(job_id, 0.0, "Starting system scan...")
|
||||||
JobManager.add_job_log(job_id, "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:
|
with self._metrics_lock:
|
||||||
self.files_processed = 0
|
self.files_processed = 0
|
||||||
self.files_new = 0
|
self.files_new = 0
|
||||||
@@ -434,7 +419,9 @@ class ScannerService:
|
|||||||
with self._metrics_lock:
|
with self._metrics_lock:
|
||||||
self.is_hashing = True
|
self.is_hashing = True
|
||||||
|
|
||||||
self._set_process_priority("background")
|
from app.core.utils import set_process_priority
|
||||||
|
|
||||||
|
set_process_priority("background")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with SessionLocal() as db_session:
|
with SessionLocal() as db_session:
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
Upload,
|
Upload,
|
||||||
Terminal,
|
Terminal,
|
||||||
Globe,
|
Globe,
|
||||||
Key
|
Key,
|
||||||
|
ChevronDown
|
||||||
} from "lucide-svelte";
|
} from "lucide-svelte";
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import PageHeader from "$lib/components/ui/PageHeader.svelte";
|
import PageHeader from "$lib/components/ui/PageHeader.svelte";
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
let scanSchedule = $state("");
|
let scanSchedule = $state("");
|
||||||
let archivalSchedule = $state("");
|
let archivalSchedule = $state("");
|
||||||
let notificationUrls = $state<string[]>([]);
|
let notificationUrls = $state<string[]>([]);
|
||||||
|
let ioniceLevel = $state("idle");
|
||||||
|
|
||||||
// Secrets keystore
|
// Secrets keystore
|
||||||
let secretsList = $state<string[]>([]);
|
let secretsList = $state<string[]>([]);
|
||||||
@@ -66,7 +68,8 @@
|
|||||||
globalExclusions,
|
globalExclusions,
|
||||||
scanSchedule,
|
scanSchedule,
|
||||||
archivalSchedule,
|
archivalSchedule,
|
||||||
notificationUrls
|
notificationUrls,
|
||||||
|
ioniceLevel
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeNavigate((navigation: any) => {
|
beforeNavigate((navigation: any) => {
|
||||||
@@ -155,6 +158,7 @@
|
|||||||
if (data.schedule_scan) scanSchedule = data.schedule_scan;
|
if (data.schedule_scan) scanSchedule = data.schedule_scan;
|
||||||
if (data.schedule_archival) archivalSchedule = data.schedule_archival;
|
if (data.schedule_archival) archivalSchedule = data.schedule_archival;
|
||||||
if (data.notification_urls) notificationUrls = JSON.parse(data.notification_urls);
|
if (data.notification_urls) notificationUrls = JSON.parse(data.notification_urls);
|
||||||
|
if (data.ionice_level) ioniceLevel = data.ionice_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load secrets
|
// Load secrets
|
||||||
@@ -169,7 +173,8 @@
|
|||||||
globalExclusions,
|
globalExclusions,
|
||||||
scanSchedule,
|
scanSchedule,
|
||||||
archivalSchedule,
|
archivalSchedule,
|
||||||
notificationUrls
|
notificationUrls,
|
||||||
|
ioniceLevel
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Failed to load system configuration");
|
toast.error("Failed to load system configuration");
|
||||||
@@ -188,7 +193,8 @@
|
|||||||
updateSettings({ body: { key: "global_exclusions", value: globalExclusions } }),
|
updateSettings({ body: { key: "global_exclusions", value: globalExclusions } }),
|
||||||
updateSettings({ body: { key: "schedule_scan", value: scanSchedule } }),
|
updateSettings({ body: { key: "schedule_scan", value: scanSchedule } }),
|
||||||
updateSettings({ body: { key: "schedule_archival", value: archivalSchedule } }),
|
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
|
// Snapshot saved state
|
||||||
@@ -199,7 +205,8 @@
|
|||||||
globalExclusions,
|
globalExclusions,
|
||||||
scanSchedule,
|
scanSchedule,
|
||||||
archivalSchedule,
|
archivalSchedule,
|
||||||
notificationUrls
|
notificationUrls,
|
||||||
|
ioniceLevel
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success("System configuration committed");
|
toast.success("System configuration committed");
|
||||||
@@ -648,6 +655,24 @@
|
|||||||
|
|
||||||
{:else if activeTab === 'system'}
|
{:else if activeTab === 'system'}
|
||||||
<div class="animate-in slide-in-from-bottom-4 duration-500 space-y-6">
|
<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">
|
<Card class="p-5 shadow-xl">
|
||||||
<SectionHeader title="Index management" icon={Database} class="mb-6 px-0" />
|
<SectionHeader title="Index management" icon={Database} class="mb-6 px-0" />
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user