- DiskForecast.EnoughHistory: false for "ingen målinger endnu" and "for lidt historik", so a rule can say "ikke målbar" instead of "fyldes ikke op". Days are counted from now (a last point older than its hour is subtracted); FullAt takes the time the forecast was made; Describe says "fuld nu" at 0 days. - ServerEventGroup.Buckets: counts per clock hour, and Recent sums only the hours with events in the last 24 hours (FirstAt within the window), so a recurring error no longer adds up from when it was first seen. Older groups without buckets still merge. - ServerPerformanceMath.Sample takes the interval: after a gap of more than three intervals CPU and pages/s are null rather than the gap's average stamped now. Commit percent is computed in floating point. - TaskResults.IsUserOrUpdaterTask (a user's SID in the name, OneDrive/Edge/Google updaters, User_Feed_Synchronization), HasUserSid and Display (SID replaced by a short hash for titles and subjects). - Tests: forecast from real history, irregular sampling, identical timestamps, a shrunk volume, days from now; an error every ten hours over three days; a sample after a gap. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
108 lines
4.9 KiB
C#
108 lines
4.9 KiB
C#
using System.Globalization;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace SQLInsight.Core.Servers;
|
|
|
|
/// <summary>Task Scheduler's result codes (LastTaskResult) in Danish, and which of them mean the task failed.</summary>
|
|
public static partial class TaskResults
|
|
{
|
|
public const uint Success = 0;
|
|
public const uint Ready = 0x41300;
|
|
public const uint Running = 0x41301;
|
|
public const uint Disabled = 0x41302;
|
|
public const uint HasNotRun = 0x41303;
|
|
public const uint NoMoreRuns = 0x41304;
|
|
public const uint TerminatedByUser = 0x41306;
|
|
public const uint Queued = 0x41325;
|
|
public const uint AlreadyRunning = 0x8004131F;
|
|
|
|
/// <summary>Whether the last run ended badly. Running, queued, never run and "already running" are not failures.</summary>
|
|
public static bool IsFailure(uint? result) => result is { } r && r is not (Success or Ready or Running or Disabled or HasNotRun or NoMoreRuns or Queued or AlreadyRunning);
|
|
|
|
public static string Hex(uint result) => "0x" + result.ToString("X", CultureInfo.InvariantCulture);
|
|
|
|
public static string Describe(uint result) => result switch
|
|
{
|
|
Success => "Fuldført (0x0)",
|
|
Ready => "Klar (0x41300)",
|
|
Running => "Kører nu (0x41301)",
|
|
Disabled => "Deaktiveret (0x41302)",
|
|
HasNotRun => "Har ikke kørt endnu (0x41303)",
|
|
NoMoreRuns => "Ingen flere planlagte kørsler (0x41304)",
|
|
TerminatedByUser => "Afbrudt af en bruger eller fordi den kørte for længe (0x41306)",
|
|
Queued => "I kø (0x41325)",
|
|
AlreadyRunning => "Kørte allerede, en ny kørsel blev ikke startet (0x8004131F)",
|
|
0x1 => "Programmet sluttede med fejlkode 1 (typisk en fejl i scriptet eller programmet) (0x1)",
|
|
0x2 => "Filen blev ikke fundet (0x2)",
|
|
0xC000013A => "Programmet blev afbrudt (Ctrl+C eller nedlukning) (0xC000013A)",
|
|
0x80070002 => "Filen blev ikke fundet (0x80070002)",
|
|
0x80070005 => "Adgang nægtet: kontoen, opgaven kører som, mangler rettigheder (0x80070005)",
|
|
0x8007052E => "Logon fejlede: forkert brugernavn eller adgangskode til opgavens konto (0x8007052E)",
|
|
0x800710E0 => "Opgaven blev afvist (fx fordi computeren kørte på batteri eller kontoen ikke var logget på) (0x800710E0)",
|
|
0x80041326 => "Opgaven er deaktiveret (0x80041326)",
|
|
_ => "Sluttede med fejlkode " + Hex(result),
|
|
};
|
|
|
|
/// <summary>Whether a task is Microsoft's own (\Microsoft\…), which is not watched unless asked for.</summary>
|
|
public static bool IsMicrosoftTask(string path)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(path);
|
|
return path.StartsWith(@"\Microsoft\", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tasks that programs create per user or to update themselves, in the root folder rather than under \Microsoft\:
|
|
/// they are not the server's own work, and their failures are rarely anyone's business. A name, or a prefix ending in *.
|
|
/// </summary>
|
|
public static readonly IReadOnlyList<string> DefaultIgnoredNames =
|
|
[
|
|
"OneDrive Standalone Update Task*",
|
|
"OneDrive Per-Machine Standalone Update Task*",
|
|
"OneDrive Reporting Task*",
|
|
"OneDrive Startup Task*",
|
|
"MicrosoftEdgeUpdateTask*",
|
|
"GoogleUpdateTask*",
|
|
"GoogleUpdaterTask*",
|
|
"User_Feed_Synchronization-*",
|
|
];
|
|
|
|
/// <summary>
|
|
/// Whether a task belongs to one user (its name carries the user's SID, S-1-5-21-…) or is a program's own updater
|
|
/// (<see cref="DefaultIgnoredNames"/>). Such tasks are not watched unless asked for.
|
|
/// </summary>
|
|
public static bool IsUserOrUpdaterTask(string path)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(path);
|
|
if (path.Contains("S-1-5-21-", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var name = path[(path.LastIndexOf('\\') + 1)..];
|
|
return DefaultIgnoredNames.Any(p => p.EndsWith('*')
|
|
? name.StartsWith(p[..^1], StringComparison.OrdinalIgnoreCase)
|
|
: name.Equals(p, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
/// <summary>Whether the task's path names a user by SID.</summary>
|
|
public static bool HasUserSid(string path)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(path);
|
|
return Sid().IsMatch(path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The task's path for titles and subjects: a user's SID is replaced by "bruger-" and a short hash of it, which tells
|
|
/// two users' tasks apart without naming the user.
|
|
/// </summary>
|
|
public static string Display(string path)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(path);
|
|
return Sid().Replace(path, m => "bruger-" + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(m.Value.ToUpperInvariant())))[..8].ToLowerInvariant());
|
|
}
|
|
|
|
[GeneratedRegex(@"S-1-5-21(-[0-9]+)+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
|
private static partial Regex Sid();
|
|
}
|