From 20c8bb306b9f1226a33aa38903e919f308e9f297 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:55:03 +0000 Subject: [PATCH 1/3] Ember: the re-point and the no-prompt proof in the plan and the bench log Co-Authored-By: Claude Fable 5.1 --- docs/bench-log.md | 6 ++++++ docs/plans/ember-tune.md | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/bench-log.md b/docs/bench-log.md index d3670a441..e459ad466 100644 --- a/docs/bench-log.md +++ b/docs/bench-log.md @@ -2262,3 +2262,9 @@ acknowledged true" = the applied rule demanded watts from a card that reports of was read on every tick (amd_watts_source=engine_telemetry, 363 samples). The helper registered on the scratch copy (fixed 200362a: task_exe, the reregister verb). PC 1 mined through the installed app again by 16:44Z: 170.6 MH/s over the three cards, 0 faults. + +Re-point and proof, 16:48 to 16:53Z: the Power Helper task re-pointed by the helper itself ("1 reregister ok: the +task now runs ...Programs\Igneum Miner\igneum-app.exe --power-helper"), then the installed app's caps through the +task with no prompt ("-pl 460: set to 460.00 W from 575.00 W", "-pl 160: set to 160.00 W from 100.00 W"). Task +Running, Highest, user Admin. Cards: 5090 221 W at 1845 MHz, 4070 75.8 W at 1860 MHz, 9070 XT 202 W, all mining +through the installed app. Ember closed 16:53Z. diff --git a/docs/plans/ember-tune.md b/docs/plans/ember-tune.md index 5de75c53c..5b1398f7a 100644 --- a/docs/plans/ember-tune.md +++ b/docs/plans/ember-tune.md @@ -282,6 +282,20 @@ After the run (nvidia-smi, the engine gone, the installed miners not yet back): 170.6 MH/s, 0 faults (console, 16:44Z). The 9070 XT's ADLX state read "gmax 0 plimit 0 factory 0" (before: factory 1): zero offsets, the factory flag cleared by the engine's set of 0; the 3.0 coordinator's reset job puts it back. +### The re-point and the no-prompt proof (6 October 2026, 16:48 to 16:53Z) + +`ember-repoint-pc1-1` (unelevated): task action before = the scratch copy; kit-7 (bd7fcf4) put in its place; +`Start-ScheduledTask`; `1 reregister` written; helper.log at 16:48:42Z: "1 reregister ok: the task now runs +C:\Users\Admin\AppData\Local\Programs\Igneum Miner\igneum-app.exe --power-helper"; action read back = the +installed exe. Three seconds later the installed app's own caps went through the task with no prompt (helper.log: +"nvidia-smi -i 0 -pl 460: Power limit for GPU 01:00.0 was set to 460.00 W from 575.00 W"; "-i 1 -pl 160: set to +160.00 W from 100.00 W"). `ember-proof-pc1-2` (reads only): task state Running, run level Highest, user Admin; +5090 221 W at 1845 MHz, 4070 75.8 W at 1860 MHz, 9070 XT 202 W; all three mining through the installed app. The +Security log's process-start audit is off on PC 1, so a consent.exe count is unreadable; the helper's log is the +record. Known in 0.3.13 on PC 1 until 0.3.14: the rows show the app's own earlier baseline (no /api/tune-progress +to receive the run's result), and the 80% caps are the app's setting; the draw is the tuned one regardless because +the driver holds the clock locks and the caps do not bind. + ## 8a. Next-cut notes (for the 0.3.12 shipper) For the 0.3.14 cut (6 October 2026, after run 6), on ember-tune: From 75679d27a514929e75e040688ac0489020e76caf Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:56:24 +0000 Subject: [PATCH 2/3] Ember: a baseline (measure-only) result's row reads Measured, not Tuned; the event line no longer doubles the words; test; next-cut table row Co-Authored-By: Claude Fable 5.1 --- app/igneum-app/src/ember.rs | 22 ++++++++++++++++++++++ app/igneum-app/src/engine.rs | 6 +++--- docs/plans/ember-tune.md | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/igneum-app/src/ember.rs b/app/igneum-app/src/ember.rs index 0dfbd87a8..9a9f5a354 100644 --- a/app/igneum-app/src/ember.rs +++ b/app/igneum-app/src/ember.rs @@ -906,6 +906,21 @@ pub fn tuned_line(mhs: f64, watts: f64, eff: f64) -> String { format!("Tuned: {mhs:.1} MH/s at {watts:.0} W ({eff:.3} MH/W)") } +/// The row's line for a baseline (measure-only) result: the card was measured as it runs, nothing was set. +/// (6 October 2026: PC 1's rows read "Tuned: 114.2 MH/s at 305 W" for a baseline, which is not a tune.) +pub fn measured_line(mhs: f64, watts: f64, eff: f64) -> String { + format!("Measured: {mhs:.1} MH/s at {watts:.0} W ({eff:.3} MH/W)") +} + +/// The row's line for a plan's result. +pub fn result_line(kind: PlanKind, mhs: f64, watts: f64, eff: f64) -> String { + if kind == PlanKind::Baseline { + measured_line(mhs, watts, eff) + } else { + tuned_line(mhs, watts, eff) + } +} + /// Why a card cannot be tuned beyond measuring, or None when both knobs are available. pub fn control_reason(vendor: &str, limits: &Limits, device: &str, power_control: bool, amd_helper: bool) -> Option { match vendor { @@ -992,6 +1007,13 @@ mod tests { assert!(Run::applied(&step, Readback { limit_w: 90.0, acked: false }, 100.0)); } + #[test] + fn a_baseline_result_reads_measured_and_a_tune_reads_tuned() { + assert_eq!(result_line(PlanKind::Baseline, 114.2, 305.0, 0.3744), "Measured: 114.2 MH/s at 305 W (0.374 MH/W)"); + assert_eq!(result_line(PlanKind::Full, 127.71, 226.8, 0.5632), "Tuned: 127.7 MH/s at 227 W (0.563 MH/W)"); + assert_eq!(result_line(PlanKind::Confirm, 127.71, 226.8, 0.5632), "Tuned: 127.7 MH/s at 227 W (0.563 MH/W)"); + } + #[test] fn limits_never_exceed_the_vendor_or_undercut_the_floor() { let l = l5090(); diff --git a/app/igneum-app/src/engine.rs b/app/igneum-app/src/engine.rs index 0d391cbac..697fb64c5 100644 --- a/app/igneum-app/src/engine.rs +++ b/app/igneum-app/src/engine.rs @@ -2749,7 +2749,7 @@ impl Engine { c.tune_clock_mhz = row.point.clock_mhz; c.tune_mem_mhz = row.point.mem_mhz; c.tune_source = kind.name().into(); - c.tune_line = crate::ember::tuned_line(row.mhs, row.watts, row.eff); + c.tune_line = crate::ember::result_line(kind, row.mhs, row.watts, row.eff); c.tune_curve = run.rows.iter().map(|r| r.json()).collect(); let control = c.tune_control; if kind == crate::ember::PlanKind::Baseline { @@ -2792,9 +2792,9 @@ impl Engine { let before = run.rows.first().filter(|_| kind == crate::ember::PlanKind::Full).cloned(); let record = crate::ember::record_json(crate::platform::unix_now_f(), &crate::config::fingerprint8(&self.shared.runtime.machine_id), VERSION, crate::manifest::platform_name(), &name, &vendor, &driver, &class, kind, &run.rows, Some(&row), before.as_ref(), floor); self.sweep_say(&format!("TUNE {record}")); - let line = crate::ember::tuned_line(row.mhs, row.watts, row.eff); + let line = crate::ember::result_line(kind, row.mhs, row.watts, row.eff); match kind { - crate::ember::PlanKind::Baseline => self.shared.event("info", &format!("{name}: measured as it runs: {line}{}", if control { String::new() } else { " (measure only; no control on this card)".into() })), + crate::ember::PlanKind::Baseline => self.shared.event("info", &format!("{name}: {line}{}", if control { String::new() } else { " (measure only; no control on this card)".into() })), _ if pinned => self.shared.event("ok", &format!("{name}: best point {} ({line}); your setting stays pinned", point_words(&row.point))), _ => self.shared.event("ok", &format!("{name}: {line}, {} held", point_words(&row.point))), } diff --git a/docs/plans/ember-tune.md b/docs/plans/ember-tune.md index 5b1398f7a..b7d08a927 100644 --- a/docs/plans/ember-tune.md +++ b/docs/plans/ember-tune.md @@ -302,6 +302,7 @@ For the 0.3.14 cut (6 October 2026, after run 6), on ember-tune: | Commit | What | Where | |---|---|---| +| (this cut) | a baseline result's row reads "Measured: ..." not "Tuned: ..." (`ember::result_line`); PC 1's 0.3.13 rows read "Tuned: 114.2 MH/s at 305 W" for a baseline | ember.rs, engine.rs + test | | bd7fcf4 | AMD: a power step with no limit readback is applied on the acknowledgement (run 6 aborted the 9070 XT's ladder at step 1 on "card reports 0 W") | ember.rs `Run::applied` + test | | 200362a | `task_exe` prefers the installed exe for every path that is not itself an install candidate; the helper's `reregister` verb; the clock ladder and floor to 45%; a chosen point on the floor says so; the fleet page's team rows | powertask.rs, ember.rs, engine.rs, site | | 5165de7, 9fba504, 5b0968f | Ember 2's Settings (goal, price, climb), £/day and the curve on the card; the playbook's AMD watts source and kit-7 preference; run 6 notes | ui, relay/playbooks, docs | From 6b94082e980aecddb339bf8e8f10e7890acd8967 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:59:33 +0000 Subject: [PATCH 3/3] Ember: every task command goes to the helper's fixed folder (platform::fixed_data_root, powertask::helper_dir, engine helper_cmd_dir): a scratch-root engine wrote under its own root, where the task never reads; test. The shipped-app window playbook: the installed app tunes itself through the task (AMD ladder, then the NVIDIA climbs), asked and watched over its API, never stopped Co-Authored-By: Claude Fable 5.1 --- app/igneum-app/src/engine.rs | 28 ++++++-- app/igneum-app/src/main.rs | 2 +- app/igneum-app/src/platform.rs | 8 +++ app/igneum-app/src/powertask.rs | 9 +++ docs/plans/ember-tune.md | 1 + relay/playbooks/ember-installed-tune-pc1.ps1 | 68 ++++++++++++++++++++ 6 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 relay/playbooks/ember-installed-tune-pc1.ps1 diff --git a/app/igneum-app/src/engine.rs b/app/igneum-app/src/engine.rs index 697fb64c5..11e133f5b 100644 --- a/app/igneum-app/src/engine.rs +++ b/app/igneum-app/src/engine.rs @@ -1817,7 +1817,7 @@ impl Engine { // once, ever (src/powertask.rs): a registered task sets the caps with no prompt; the readback judges it if cfg!(windows) && self.power_task_registered() { let pairs: Vec<(String, u64)> = self.st().mining.cards.iter().filter(|c| c.vendor == "nvidia" && c.enabled && c.present() && c.power_default_w > 0.0 && !c.power_applied).map(|c| (c.device.clone(), requested_watts(c).round() as u64)).collect(); - let dir = self.sweep_dir(); + let dir = crate::powertask::helper_dir(); let shared = self.shared.clone(); self.shared.log("power cap: through the Igneum Power Helper task (no prompt)"); std::thread::spawn(move || { @@ -1936,8 +1936,9 @@ impl Engine { } if cfg!(windows) && self.power_task_registered() { // the kill switch: the task unregisters itself (elevated) and exits; nothing is left behind - let dir = self.sweep_dir(); - let _ = std::fs::write(dir.join("cmd.txt"), "remove\n"); + let hdir = crate::powertask::helper_dir(); + let _ = std::fs::create_dir_all(&hdir); + let _ = std::fs::write(hdir.join("cmd.txt"), "remove\n"); match crate::powertask::start() { Ok(()) => self.shared.log("power control off: the Igneum Power Helper task removes itself"), Err(e) => self.shared.log(&format!("power control off: the task could not be started to remove itself ({e}); remove it in Task Scheduler")), @@ -2142,6 +2143,16 @@ impl Engine { self.shared.runtime.app_dir.join("sweep") } + /// Where this engine's helper reads its commands: the task's own folder when the task is the helper, else this + /// engine's sweep folder (the prompted helper script was handed that path on its command line). + fn helper_cmd_dir(&self) -> PathBuf { + if self.sweep_helper_is_task { + crate::powertask::helper_dir() + } else { + self.sweep_dir() + } + } + /// The manifest's tuning object (/tuning.json, written by the updater from the signed manifest), or /// None: the Ember settings (kill switch, thresholds) and the fleet priors live under it. fn tuning_object(&self) -> Option { @@ -2470,8 +2481,11 @@ impl Engine { format!("sh \"{}\" \"{}\" \"{}\" {} {}", script.display(), dir.display(), smi, c.device, restore) }; if cfg!(windows) && self.power_task_registered() { - // once, ever: the registered task is the helper; it reads the same command file, no prompt - let _ = std::fs::write(dir.join("cmd.txt"), format!("{} dev {}\n", crate::platform::unix_now() % 1_000_000, c.device)); + // once, ever: the registered task is the helper; it reads ITS command file (powertask::helper_dir, never + // under a scratch IGNEUM_APP_DATA), no prompt + let hdir = crate::powertask::helper_dir(); + let _ = std::fs::create_dir_all(&hdir); + let _ = std::fs::write(hdir.join("cmd.txt"), format!("{} dev {}\n", crate::platform::unix_now() % 1_000_000, c.device)); crate::powertask::start()?; self.shared.log("tune helper: the Igneum Power Helper task (no prompt)"); self.sweep_helper = true; @@ -2491,7 +2505,7 @@ impl Engine { fn sweep_helper_quit(&mut self) { if self.sweep_helper { - let _ = std::fs::write(self.sweep_dir().join("cmd.txt"), "quit\n"); + let _ = std::fs::write(self.helper_cmd_dir().join("cmd.txt"), "quit\n"); if self.sweep_helper_is_task { // the task exits on quit and reports nothing back; the next tune starts it again self.sweep_helper = false; @@ -2553,7 +2567,7 @@ impl Engine { } let dev = device.to_string(); let cmd = format!("{seq}0 dev {dev}\n{seq}1 pl {w}\n{seq}2 {}\n{seq}3 {}\n", if clock > 0 { format!("lgc {clock}") } else { "rgc".to_string() }, if mem > 0 { format!("lmc {mem}") } else { "rmc".to_string() }); - let _ = std::fs::write(self.sweep_dir().join("cmd.txt"), cmd); + let _ = std::fs::write(self.helper_cmd_dir().join("cmd.txt"), cmd); // the helper polls twice a second and nvidia-smi answers within a second or two std::thread::spawn(move || { std::thread::sleep(Duration::from_secs(4)); diff --git a/app/igneum-app/src/main.rs b/app/igneum-app/src/main.rs index 809453382..670837ce0 100644 --- a/app/igneum-app/src/main.rs +++ b/app/igneum-app/src/main.rs @@ -58,7 +58,7 @@ fn main() { if args.iter().any(|a| a == "--power-helper") { // the scheduled task's action (src/powertask.rs): elevated, runs only digit-argument nvidia-smi commands // from /app/sweep/cmd.txt, exits on quit, remove or 20 idle minutes - let dir = powertask::sweep_dir(&platform::data_root().join("app")); + let dir = powertask::helper_dir(); std::process::exit(powertask::run_helper(&dir)); } if args.iter().any(|a| a == "--launch") { diff --git a/app/igneum-app/src/platform.rs b/app/igneum-app/src/platform.rs index 01f288c6d..55bfd2fae 100644 --- a/app/igneum-app/src/platform.rs +++ b/app/igneum-app/src/platform.rs @@ -103,6 +103,14 @@ pub fn data_root() -> PathBuf { if let Some(p) = std::env::var_os("IGNEUM_APP_DATA") { return PathBuf::from(p); } + fixed_data_root() +} + +/// The platform's own root, with IGNEUM_APP_DATA ignored: where the Power Helper task (started by Windows with no +/// environment of ours) reads its command file, so a measurement engine under a scratch root (IGNEUM_APP_DATA set) +/// writes its task commands here and not under its own root. 6 October 2026: found while planning the unelevated +/// climb through the task; the elevated runs never crossed it (they set the limits directly). +pub fn fixed_data_root() -> PathBuf { #[cfg(target_os = "macos")] { home().join("Library").join("Application Support").join("Igneum") diff --git a/app/igneum-app/src/powertask.rs b/app/igneum-app/src/powertask.rs index f674b31f7..232ef5589 100644 --- a/app/igneum-app/src/powertask.rs +++ b/app/igneum-app/src/powertask.rs @@ -272,6 +272,12 @@ pub fn sweep_dir(app_dir: &Path) -> PathBuf { app_dir.join("sweep") } +/// The one folder the task's helper reads: `/app/sweep` (never under IGNEUM_APP_DATA). Every +/// engine that talks to the task writes its command file here; the `--power-helper` entry reads here. +pub fn helper_dir() -> PathBuf { + sweep_dir(&crate::platform::fixed_data_root().join("app")) +} + #[cfg(test)] mod tests { use super::*; @@ -293,6 +299,9 @@ mod tests { assert_eq!(parse_line("12 reregister C:\\evil.exe"), None, "no path argument: the helper picks the install folder itself"); assert_eq!(smi_args("0", &HelperCmd::Reregister), None); assert!(readback_command().contains("Actions[0].Execute")); + // the helper's folder never follows IGNEUM_APP_DATA (the task has no environment of ours) + assert!(helper_dir().ends_with(Path::new("app").join("sweep"))); + assert!(helper_dir().starts_with(crate::platform::fixed_data_root())); // nothing else: no shell, no path, no string argument, no oversized number for bad in ["7 pl 460; calc", "7 pl -460", "7 pl 4.60", "7 lgc 0,2472", "7 rm C:\\x", "x pl 460", "7 pl", "7 lgc 12345678", "7 dev ../1", "", "7 pl 460 extra"] { assert_eq!(parse_line(bad), None, "{bad:?}"); diff --git a/docs/plans/ember-tune.md b/docs/plans/ember-tune.md index b7d08a927..50487f366 100644 --- a/docs/plans/ember-tune.md +++ b/docs/plans/ember-tune.md @@ -302,6 +302,7 @@ For the 0.3.14 cut (6 October 2026, after run 6), on ember-tune: | Commit | What | Where | |---|---|---| +| (this cut) | every task command goes to the helper's fixed folder (`powertask::helper_dir` = `/app/sweep`, IGNEUM_APP_DATA ignored): a scratch-root measurement engine talking to the task wrote under its own root, where the task never reads (found 6 October 2026 planning the unelevated climb; the elevated runs set limits directly and never crossed it) | platform.rs `fixed_data_root`, powertask.rs, engine.rs `helper_cmd_dir`, main.rs + test | | (this cut) | a baseline result's row reads "Measured: ..." not "Tuned: ..." (`ember::result_line`); PC 1's 0.3.13 rows read "Tuned: 114.2 MH/s at 305 W" for a baseline | ember.rs, engine.rs + test | | bd7fcf4 | AMD: a power step with no limit readback is applied on the acknowledgement (run 6 aborted the 9070 XT's ladder at step 1 on "card reports 0 W") | ember.rs `Run::applied` + test | | 200362a | `task_exe` prefers the installed exe for every path that is not itself an install candidate; the helper's `reregister` verb; the clock ladder and floor to 45%; a chosen point on the floor says so; the fleet page's team rows | powertask.rs, ember.rs, engine.rs, site | diff --git a/relay/playbooks/ember-installed-tune-pc1.ps1 b/relay/playbooks/ember-installed-tune-pc1.ps1 new file mode 100644 index 000000000..4426ef81c --- /dev/null +++ b/relay/playbooks/ember-installed-tune-pc1.ps1 @@ -0,0 +1,68 @@ +# Ember: the SHIPPED app tunes itself (6 October 2026). Unelevated, no kit, no second engine, no --stop-miners: +# the installed app (0.3.14 or later: Ember Tune, the Power Helper task, /api/tune/goal, the climb) runs one tune +# per card through the registered task (no prompt) while it mines. This script only asks and watches: +# POST api/tune/goal (the climb on, balanced) and POST api/sweep/start per card, then reads api/state and the app's +# own log for the TUNE rows. It never quits, pauses or resumes the installed app (the 5 October rule) and never +# touches the cards itself. At the end the climb switch goes back to what it was. +# Order: the AMD card's ladder first (the ADLX apply-on-ack fix, bd7fcf4), then each NVIDIA card's climb. +$ErrorActionPreference = 'Continue' +function Say([string] $s) { Write-Output ('[' + (Get-Date -Format 'HH:mm:ss') + '] ' + $s) } +$appDir = Join-Path $env:LOCALAPPDATA 'igneum\app' +$u = Join-Path $appDir 'app.url' +if (-not (Test-Path -LiteralPath $u)) { Write-Output 'RESULT TUNE error=no_app_url (installed app not running)'; exit 1 } +$base = (Get-Content -LiteralPath $u -Raw).Trim() +function State() { try { return ((Invoke-WebRequest -Uri ($base + 'api/state') -UseBasicParsing -TimeoutSec 5).Content | ConvertFrom-Json) } catch { return $null } } +function Post([string] $path, [string] $json) { try { Invoke-WebRequest -Uri ($base + $path) -Method POST -Body $json -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null; return $true } catch { Say ($path + ': ' + $_.Exception.Message); return $false } } +$s = State +if (-not $s) { Write-Output 'RESULT TUNE error=state_unreadable'; exit 1 } +$ver = [string]$s.version +Write-Output ('RESULT TUNE installed app ' + $ver + ' power_control=' + $s.settings.power_control + ' tuning_off=' + $s.settings.tuning_off + ' climb_before=' + $s.settings.tune_climb + ' goal_before=' + $s.settings.tune_goal) +$t = Get-ScheduledTask -TaskName 'Igneum Power Helper' -ErrorAction SilentlyContinue +Write-Output ('RESULT TUNE task ' + $(if ($t) { $t.Actions[0].Execute + ' state=' + $t.State } else { 'NOT REGISTERED' })) +if (-not $t) { Write-Output 'RESULT TUNE error=task_not_registered (the NVIDIA climbs would need a prompt; stopping)'; exit 1 } +if (-not $s.settings.power_control) { Write-Output 'RESULT TUNE error=power_control_off'; exit 1 } +$climbBefore = [bool]$s.settings.tune_climb +$cards = @($s.mining.cards | Where-Object { $_.enabled -and ($_.vendor -eq 'nvidia' -or $_.vendor -eq 'amd') -and $_.name -notmatch 'Radeon\(TM\) Graphics' }) +Write-Output ('RESULT TUNE cards ' + (($cards | ForEach-Object { $_.key + ' (' + $_.name + ', ' + $_.vendor + ', sweep_state ' + $_.sweep_state + ', line "' + $_.tune_line + '")' }) -join ' | ')) +foreach ($c in $cards) { Write-Output ('RESULT TUNE before key=' + $c.key + ' power_w=' + $c.power_w + ' power_pct=' + $c.power_pct + ' clock_cap=' + $c.clock_cap_mhz + ' mhs=' + $c.mhs + ' line=' + $c.tune_line) } +# the climb on (NVIDIA cards; an AMD card has no clock knob and runs its ladder regardless) +if (-not (Post 'api/tune/goal' '{"goal":"balanced","climb":true}')) { Write-Output 'RESULT TUNE error=tune_goal_refused'; exit 1 } +Start-Sleep -Seconds 2 +$order = @($cards | Where-Object { $_.vendor -eq 'amd' }) + @($cards | Where-Object { $_.vendor -eq 'nvidia' }) +$deadline = (Get-Date).AddMinutes(27) +$logs = Join-Path $appDir 'logs' +function LogMark() { $l = Get-ChildItem -Path $logs -Filter 'app-*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1; if ($l) { return @{ f = $l.FullName; n = (Get-Content -LiteralPath $l.FullName).Count } } else { return @{ f = ''; n = 0 } } } +function LogSince($m) { if (-not $m.f) { return @() }; $all = @(Get-Content -LiteralPath $m.f -ErrorAction SilentlyContinue); if ($all.Count -le $m.n) { return @() }; return @($all[$m.n..($all.Count - 1)]) } +$rows = 0 +foreach ($c in $order) { + if ((Get-Date) -gt $deadline) { Say 'budget spent before ' + $c.key; break } + $m = LogMark + $body = '{"key":"' + ($c.key -replace '"', '\"') + '"}' + if (-not (Post 'api/sweep/start' $body)) { Write-Output ('RESULT TUNE error=sweep_start_refused key=' + $c.key); continue } + Say ('tune asked for ' + $c.key) + $seen = 0; $done = $false; $started = Get-Date + while (-not $done -and (Get-Date) -lt $deadline) { + Start-Sleep -Seconds 10 + $new = @(LogSince $m) + while ($seen -lt $new.Count) { $l = [string]$new[$seen]; $seen++; if ($l -match 'TUNE (card=|chosen|start|aborted|progress)') { if ($l -notmatch 'TUNE progress') { Write-Output ('RESULT ' + ($l -replace '^.*?(TUNE )', 'TUNE ')) }; if ($l -match 'TUNE card=') { $rows++ } } } + $st = State + if ($st) { + $cc = $st.mining.cards | Where-Object { $_.key -eq $c.key } | Select-Object -First 1 + if ($cc) { + $running = ($cc.sweep_state -eq 'running') + if (-not $running -and ((Get-Date) - $started).TotalSeconds -gt 180) { $done = $true } + if ($running -and $cc.tune_steps -gt 0 -and ($seen % 6 -eq 0)) { Say ($c.key + ': step ' + $cc.tune_step + ' of ' + $cc.tune_steps + ', ' + $cc.sweep_note) } + } + } + } + $st = State + if ($st) { $cc = $st.mining.cards | Where-Object { $_.key -eq $c.key } | Select-Object -First 1; if ($cc) { Write-Output ('RESULT TUNE after key=' + $c.key + ' power_w=' + $cc.power_w + ' power_pct=' + $cc.power_pct + ' clock_cap=' + $cc.clock_cap_mhz + ' line=' + $cc.tune_line + ' source=' + $cc.tune_source + ' note=' + $cc.sweep_note); if ($cc.tune_curve) { Write-Output ('RESULT TUNE curve key=' + $c.key + ' ' + (($cc.tune_curve | ForEach-Object { ($_.clock_mhz) + 'MHz/' + ($_.mem_mhz) + 'mem/' + ($_.power_pct) + '%:' + $_.mhs + 'MH/s ' + $_.watts + 'W ' + $_.eff + ' ' + $_.mark }) -join ' | ')) } } } +} +# the climb switch back to what it was +Post 'api/tune/goal' ('{"climb":' + $(if ($climbBefore) { 'true' } else { 'false' }) + '}') | Out-Null +Write-Output ('RESULT TUNE climb_restored=' + $climbBefore + ' rows=' + $rows) +$smi = Join-Path $env:ProgramFiles 'NVIDIA Corporation\NVSMI\nvidia-smi.exe' +if (-not (Test-Path $smi)) { $smi = Join-Path $env:SystemRoot 'System32\nvidia-smi.exe' } +if (Test-Path $smi) { Write-Output ('RESULT TUNE nvidia_now ' + ((& $smi --query-gpu=index,name,power.draw,power.limit,clocks.gr,clocks.mem --format=csv,noheader 2>&1 | Out-String) -replace "`r?`n", ' | ')) } +if ($rows -eq 0) { Write-Output 'RESULT TUNE error=no_rows'; exit 1 } +exit 0