@@ -4,59 +4,90 @@ namespace SimpleNvidiaUndervolt;
44/// The tuning document every persisting run stores for the logon task, which re-applies it with
55/// <c>tune --apply-persisted</c>. Persisting the resolved offsets as data — rather than a command
66/// line to re-plan — makes the logon re-apply exact: per-anchor deltas are temperature-independent,
7- /// so nothing is re-derived from a boot-time curve read. Stored as a JSON file in the install
8- /// directory's data subfolder — the same document <c>--out-tuning-file</c> exports, so it can be
9- /// read or copied directly — which is under Program Files and thus admin-only writable, so the
10- /// elevated logon re-apply never consumes user-writable data.
7+ /// so nothing is re-derived from a boot-time curve read. Stored per GPU as a JSON file in the
8+ /// install directory's data subfolder (see <see cref="Persistence.DataFilePath"/>) — the same
9+ /// document <c>--out-tuning-file</c> exports, so it can be read or copied directly — which is
10+ /// under Program Files and thus admin-only writable, so the elevated logon re-apply never
11+ /// consumes user-writable data.
1112/// </summary>
1213internal static class PersistedTuning
1314{
14- public static string FilePath ( ) => Path . Combine ( Persistence . DataDir ( ) , "persisted-tuning.json" ) ;
15+ private const string FileStem = "persisted-tuning" ;
1516
16- /// <summary>Stores the document, replacing any previous one. Requires administrator (the
17- /// install directory); the environment refusing the write is reported as the anticipated
17+ public static string FilePath ( int gpuIndex ) => Persistence . DataFilePath ( FileStem , gpuIndex ) ;
18+
19+ /// <summary>The GPUs a tuning is stored for, ascending.</summary>
20+ public static IReadOnlyList < int > StoredGpuIndexes ( ) => Persistence . StoredGpuIndexes ( FileStem ) ;
21+
22+ /// <summary>What the logon re-apply runs against: every GPU with a stored tuning. Nothing
23+ /// stored must fail loudly — silence would read as an applied tuning.</summary>
24+ public static GpuSelector StoredSelector ( )
25+ {
26+ IReadOnlyList < int > stored = StoredGpuIndexes ( ) ;
27+ return stored . Count > 0 ? GpuSelector . Stored ( stored ) : throw NothingStored ( 0 ) ;
28+ }
29+
30+ /// <summary>Stores the document, replacing any previous one for the GPU. Requires administrator
31+ /// (the install directory); the environment refusing the write is reported as the anticipated
1832 /// failure it is.</summary>
19- public static void Save ( TuningDoc doc )
20- => TuningDocuments . WriteFile ( FilePath ( ) , TuningDocuments . Render ( doc ) , "the persisted tuning" ) ;
33+ public static void Save ( TuningDoc doc , int gpuIndex )
34+ => TuningDocuments . WriteFile ( FilePath ( gpuIndex ) , TuningDocuments . Render ( doc ) , "the persisted tuning" ) ;
2135
2236 /// <summary>The stored document. An <c>--apply-persisted</c> run with nothing (usable) stored
2337 /// must fail loudly — it is the logon re-apply, and silence would read as an applied tuning.</summary>
24- public static TuningDoc Load ( )
38+ public static TuningDoc Load ( int gpuIndex )
2539 {
26- if ( ! File . Exists ( FilePath ( ) ) )
40+ if ( ! File . Exists ( FilePath ( gpuIndex ) ) )
2741 {
28- throw new CliError ( $ "No persisted tuning is stored at { FilePath ( ) } - nothing to apply. "
29- + "Re-run your tune command to persist one." ) ;
42+ throw NothingStored ( gpuIndex ) ;
3043 }
3144
32- return TuningDocuments . ReadTuningFile ( FilePath ( ) ) ;
45+ return TuningDocuments . ReadTuningFile ( FilePath ( gpuIndex ) ) ;
3346 }
3447
35- /// <summary>One phrase for status's logon line: the stored document's capture date when it
48+ private static CliError NothingStored ( int gpuIndex )
49+ => new ( $ "No persisted tuning is stored at { FilePath ( gpuIndex ) } - nothing to apply. "
50+ + "Re-run your tune command to persist one." ) ;
51+
52+ /// <summary>One phrase for status's logon line: each stored document's capture date when it
3653 /// reads, else the inconsistency — an apply-persisted task with nothing to apply fails at the
37- /// next logon, and status must say so rather than "yes".</summary>
54+ /// next logon, and status must say so rather than "yes". GPUs are named only past the
55+ /// single-GPU case.</summary>
3856 public static string DescribeForStatus ( )
57+ {
58+ IReadOnlyList < int > stored = StoredGpuIndexes ( ) ;
59+ if ( stored . Count == 0 )
60+ {
61+ return "which is missing - re-run your tune command" ;
62+ }
63+
64+ return stored is [ 0 ]
65+ ? Describe ( 0 )
66+ : string . Join ( "; " , stored . Select ( index => $ "GPU { index } : { Describe ( index ) } ") ) ;
67+ }
68+
69+ private static string Describe ( int gpuIndex )
3970 {
4071 try
4172 {
42- return TuningDocuments . DescribeCapture ( Load ( ) . SavedAt ) ;
73+ return TuningDocuments . DescribeCapture ( Load ( gpuIndex ) . SavedAt ) ;
4374 }
4475 catch ( CliError )
4576 {
46- return "which is missing or unreadable - re-run your tune command" ;
77+ return "unreadable - re-run your tune command" ;
4778 }
4879 }
4980
50- /// <summary>Deletes the stored document, returning whether one existed. Throws when the delete
51- /// itself fails, so <c>clear</c> doesn't report persisted state as gone when it isn't.</summary>
52- public static bool Remove ( )
81+ /// <summary>Deletes the GPU's stored document, returning whether one existed. Throws when the
82+ /// delete itself fails, so <c>clear</c> doesn't report persisted state as gone when it isn't.</summary>
83+ public static bool Remove ( int gpuIndex )
5384 {
54- if ( ! File . Exists ( FilePath ( ) ) )
85+ if ( ! File . Exists ( FilePath ( gpuIndex ) ) )
5586 {
5687 return false ;
5788 }
5889
59- File . Delete ( FilePath ( ) ) ;
90+ File . Delete ( FilePath ( gpuIndex ) ) ;
6091 return true ;
6192 }
6293}
0 commit comments