-
Notifications
You must be signed in to change notification settings - Fork 4
feat: P8 runtime performance and resilience hardening #303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
masarray
wants to merge
42
commits into
fix/fat-workstation-convergence
Choose a base branch
from
feat/p8-runtime-performance-resilience
base: fix/fat-workstation-convergence
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
02a6242
feat(p8): add defensive telemetry envelope
masarray a939b51
feat(p8): normalize read values without false timestamps
masarray d76f8bb
feat(p8): add lossless-safe UI latest-value batcher
masarray 27687fe
feat(p8): add pooled transient byte-buffer lease
masarray 621fe1d
feat(p8): add low-overhead runtime allocation metrics
masarray dd61712
test(p8): cover defensive telemetry normalization
masarray 013dd0a
test(p8): prove latest-value UI batching without edge loss contract
masarray f4edd70
test(p8): cover pooled buffers and allocation snapshots
masarray 883d528
test(p8): lock runtime isolation batching virtualization and disposal…
masarray 78c5d00
docs(p8): document runtime performance and resilience invariants
masarray 196b8a8
fix(p8): keep empty placeholder outside process-value semantics
masarray 4c8fd83
test(p8): keep relay-time regression independent of constructor syntax
masarray 663b5f5
refactor(p8): remove unused UI batcher experiment
masarray 2cfa635
refactor(p8): rely on production UI batching regression
masarray 31664ba
refactor(p8): remove unproven buffer pooling surface
masarray 5937399
test(p8): keep allocation profiling deterministic
masarray f825497
test(p8): avoid flaky GC scheduling assertions
masarray c4200c8
docs(p8): close audit with production-only hardening scope
masarray 7b4555b
test(p8): guard production authorities and reject speculative pooling
masarray 7f3e1ba
fix(p8): never promote unknown quality to Good
masarray b6fb3c7
test(p8): cover conservative quality and UTC timestamp semantics
masarray 1752046
test(p8): align relay-time guard with final telemetry contract
masarray 179cf38
fix(p8): clamp reversed allocation snapshot intervals
masarray fd4f340
test(p8): cover defensive allocation snapshot ordering
masarray de39f19
fix(p8): distinguish Good validity from questionable usability
masarray 272909f
test(p8): require explicit Good before telemetry is valid
masarray 90eb106
docs(p8): align quality validity semantics with defensive envelope
masarray 041609b
test(p8): lock virtualization on production live grids too
masarray 569ff1a
fix(p8): preserve read receipt timestamp during normalization
masarray 35ade62
test(p8): preserve stored receipt time by default
masarray ffea9f4
fix(p8): unify missing-value detection across telemetry envelope
masarray c4445f6
test(p8): cover whitespace missing-value sentinel
masarray a4f7f95
fix(p8): reject incomplete source timestamps
masarray f3c025f
fix(p8): use monotonic allocation timing and honest heap metrics
masarray b36d723
test(p8): reject incomplete relay timestamp evidence
masarray 0ed5ddf
test(p8): lock monotonic allocation timing and heap semantics
masarray 8ad2bab
docs(p8): close second review findings
masarray de7e228
test(p8): make telemetry architecture guard semantic not comment-based
masarray cadf024
chore(p8): stage production telemetry patch script
masarray d6a9849
chore(p8): run production telemetry integration patch
masarray ccec5f0
fix(p8): wire defensive telemetry into production runtime
github-actions[bot] a3479e9
docs(p8): lock final IED field verification gate
masarray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| namespace ArIED61850Tester.Services; | ||
|
|
||
| /// <summary> | ||
| /// Single production boundary between decoded IEC 61850 network data and UI/runtime state. | ||
| /// It preserves a valid source timestamp, never fabricates PC time as relay evidence, and | ||
| /// never promotes missing/unknown quality to Good. | ||
| /// </summary> | ||
| public static class Iec61850ProductionTelemetryNormalizer | ||
| { | ||
| public static Iec61850TelemetryEnvelope FromReadObject( | ||
| object? value, | ||
| string dataType, | ||
| string unit, | ||
| DateTimeOffset? receivedAtUtc = null, | ||
| string? sourceReference = null, | ||
| string? readReference = null) | ||
| { | ||
| if (value is Iec61850ReadValue rich) | ||
| return Iec61850TelemetryEnvelope.FromReadValue(rich, receivedAtUtc); | ||
|
|
||
| var display = Iec61850ValueFormatter.Format(value, dataType, unit); | ||
| return FromComponents( | ||
| value, | ||
| display, | ||
| quality: null, | ||
| deviceTimestamp: null, | ||
| receivedAtUtc ?? DateTimeOffset.UtcNow, | ||
| sourceReference, | ||
| readReference); | ||
| } | ||
|
|
||
| public static Iec61850TelemetryEnvelope FromComponents( | ||
| object? value, | ||
| string? displayValue, | ||
| string? quality, | ||
| string? deviceTimestamp, | ||
| DateTimeOffset receivedAtUtc, | ||
| string? sourceReference = null, | ||
| string? readReference = null) | ||
| => Iec61850TelemetryEnvelope.FromReadValue(new Iec61850ReadValue | ||
| { | ||
| Value = value, | ||
| DisplayValue = displayValue?.Trim() ?? string.Empty, | ||
| Quality = quality?.Trim() ?? string.Empty, | ||
| DeviceTimestamp = deviceTimestamp?.Trim() ?? string.Empty, | ||
| SourceReference = sourceReference?.Trim() ?? string.Empty, | ||
| ReadReference = readReference?.Trim() ?? string.Empty, | ||
| ReceivedAtUtc = receivedAtUtc | ||
| }, receivedAtUtc); | ||
|
|
||
| public static string SourceTimestampTextOrUnknown( | ||
| Iec61850TelemetryEnvelope envelope, | ||
| string? originalTimestamp) | ||
| => envelope.SourceTimestampUtc.HasValue | ||
| ? originalTimestamp?.Trim() ?? "-" | ||
| : "-"; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| using System.Globalization; | ||
| using System.Text.RegularExpressions; | ||
|
|
||
| namespace ArIED61850Tester.Services; | ||
|
|
||
| /// <summary> | ||
| /// Normalized IEC 61850 telemetry boundary used between network decoding and application logic. | ||
| /// Missing/ambiguous data is never promoted to a valid process value: the source timestamp | ||
| /// stays unknown and quality is preserved conservatively while ReceivedAtUtc records local receipt. | ||
| /// </summary> | ||
| public enum Iec61850TelemetryQualityState | ||
| { | ||
| Good, | ||
| Questionable, | ||
| Invalid | ||
| } | ||
|
|
||
| public readonly record struct Iec61850TelemetryEnvelope( | ||
| object? Value, | ||
| string DisplayValue, | ||
| Iec61850TelemetryQualityState QualityState, | ||
| string QualityText, | ||
| DateTimeOffset? SourceTimestampUtc, | ||
| DateTimeOffset ReceivedAtUtc, | ||
| string SourceReference, | ||
| string Diagnostic) | ||
| { | ||
| private static readonly Regex CompleteSourceTimestampPattern = new( | ||
| @"^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d{1,7})?(?:Z|\s?[+-]\d{2}:\d{2})?$", | ||
| RegexOptions.Compiled | RegexOptions.CultureInvariant); | ||
|
|
||
| public bool HasProcessValue => IsProcessValuePresent(Value, DisplayValue); | ||
|
|
||
| /// <summary> | ||
| /// True only when both the process value and IEC quality are explicitly Good. | ||
| /// Questionable data may still be presented to an engineer, but it is not promoted | ||
| /// to valid evidence by this boundary. | ||
| /// </summary> | ||
| public bool IsValid => HasProcessValue && QualityState == Iec61850TelemetryQualityState.Good; | ||
|
|
||
| public bool IsUsable => HasProcessValue && QualityState != Iec61850TelemetryQualityState.Invalid; | ||
|
|
||
| public static Iec61850TelemetryEnvelope FromReadValue( | ||
| Iec61850ReadValue? read, | ||
| DateTimeOffset? receivedAtUtc = null) | ||
| { | ||
| // Preserve the timestamp captured when the read projection was created. Normalization | ||
| // can happen later after queuing/batching and must not move local receipt evidence | ||
| // forward to the conversion time. UtcNow is only a last resort for a null read. | ||
| var received = receivedAtUtc ?? read?.ReceivedAtUtc ?? DateTimeOffset.UtcNow; | ||
| if (read is null) | ||
| { | ||
| return Invalid( | ||
| received, | ||
| sourceReference: string.Empty, | ||
| diagnostic: "IEC 61850 read returned no value object."); | ||
| } | ||
|
|
||
| var display = read.DisplayValue?.Trim() ?? string.Empty; | ||
| var hasValue = IsProcessValuePresent(read.Value, display); | ||
| var qualityText = NormalizeQualityText(read.Quality); | ||
| var qualityState = ClassifyQuality(qualityText, hasValue); | ||
| var sourceTimestamp = TryParseSourceTimestampUtc(read.DeviceTimestamp); | ||
| var sourceReference = FirstNonEmpty(read.SourceReference, read.ReadReference); | ||
|
|
||
| var diagnostic = qualityState == Iec61850TelemetryQualityState.Invalid | ||
| ? hasValue | ||
| ? $"Telemetry quality is invalid ({qualityText})." | ||
| : "Telemetry contains no process value." | ||
| : qualityState == Iec61850TelemetryQualityState.Questionable | ||
| ? $"Telemetry quality is not proven Good ({qualityText})." | ||
| : sourceTimestamp is null && read.HasDeviceTimestamp | ||
| ? "Device timestamp was present but could not be parsed safely; source timestamp remains unknown." | ||
| : string.Empty; | ||
|
|
||
| return new Iec61850TelemetryEnvelope( | ||
| read.Value, | ||
| display, | ||
| qualityState, | ||
| qualityText, | ||
| sourceTimestamp, | ||
| received, | ||
| sourceReference, | ||
| diagnostic); | ||
| } | ||
|
|
||
| public static Iec61850TelemetryEnvelope Invalid( | ||
| DateTimeOffset receivedAtUtc, | ||
| string sourceReference, | ||
| string diagnostic, | ||
| object? safePlaceholder = null) | ||
| => new( | ||
| safePlaceholder, | ||
| safePlaceholder?.ToString() ?? "-", | ||
| Iec61850TelemetryQualityState.Invalid, | ||
| "Invalid", | ||
| null, | ||
| receivedAtUtc, | ||
| sourceReference?.Trim() ?? string.Empty, | ||
| diagnostic?.Trim() ?? string.Empty); | ||
|
|
||
| internal static DateTimeOffset? TryParseSourceTimestampUtc(string? value) | ||
| { | ||
| var text = value?.Trim() ?? string.Empty; | ||
| if (text.Length == 0 || text == "-") | ||
| return null; | ||
|
|
||
| // DateTimeOffset.TryParse accepts partial values such as "10:00:31" and fills the | ||
| // missing date from the local PC. That would fabricate source evidence. Accept only | ||
| // complete ARIEC/ISO date-time shapes before parsing. A zone-less decoded IEC UtcTime | ||
| // is semantically UTC; malformed or incomplete input remains unknown. | ||
| if (!CompleteSourceTimestampPattern.IsMatch(text) || | ||
| !DateTimeOffset.TryParse( | ||
| text, | ||
| CultureInfo.InvariantCulture, | ||
| DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, | ||
| out var parsed)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return parsed.ToUniversalTime(); | ||
| } | ||
|
|
||
| private static bool IsProcessValuePresent(object? value, string? displayValue) | ||
| { | ||
| if (value is not null) | ||
| return true; | ||
|
|
||
| var display = displayValue?.Trim() ?? string.Empty; | ||
| return display.Length > 0 && display != "-"; | ||
| } | ||
|
|
||
| private static Iec61850TelemetryQualityState ClassifyQuality(string quality, bool hasValue) | ||
| { | ||
| if (!hasValue) | ||
| return Iec61850TelemetryQualityState.Invalid; | ||
|
|
||
| // Never infer Good from an unknown/vendor token. IEC validity is only considered | ||
| // Good when the decoder explicitly said Good. This prevents missing or future | ||
| // quality representations from being silently promoted to trustworthy evidence. | ||
| if (quality.Equals("Good", StringComparison.OrdinalIgnoreCase)) | ||
| return Iec61850TelemetryQualityState.Good; | ||
|
|
||
| if (quality.Contains("invalid", StringComparison.OrdinalIgnoreCase) || | ||
| quality.Contains("failure", StringComparison.OrdinalIgnoreCase) || | ||
| quality.Contains("bad", StringComparison.OrdinalIgnoreCase) || | ||
| quality.Contains("reserved", StringComparison.OrdinalIgnoreCase) || | ||
| quality.Contains("outofrange", StringComparison.OrdinalIgnoreCase) || | ||
| quality.Contains("out-of-range", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return Iec61850TelemetryQualityState.Invalid; | ||
| } | ||
|
|
||
| return Iec61850TelemetryQualityState.Questionable; | ||
| } | ||
|
|
||
| private static string NormalizeQualityText(string? value) | ||
| { | ||
| var text = value?.Trim() ?? string.Empty; | ||
| return text.Length == 0 ? "Unknown" : text; | ||
| } | ||
|
|
||
| private static string FirstNonEmpty(params string?[] values) | ||
| => values.Select(value => value?.Trim() ?? string.Empty) | ||
| .FirstOrDefault(value => value.Length > 0) ?? string.Empty; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.