RE: resolve R1.8/R1.9 analog/state summary via request+response capture

Captured the native StartQuery2 pRequestBuff and the GetNextQueryResultBuffer2
response (instrument-wcf-writemessage + chained instrument-wcf-readmessage) and
decoded both against AnalogSummaryHistory SQL ground truth. Conclusion: the rich
multi-aggregate analog/state summary struct is NOT delivered over the 2020 WCF
binary protocol — the response is the ordinary version-9 row buffer the existing
aggregate parser already handles, carrying one value per cycle selected by
RetrievalMode (QueryType 5-8), not ValueSelector (inert on this path). So
"analog summary" == the existing ReadAggregateAsync; no new src/ code warranted.

Tooling (tools/ + scripts/ only, nothing in src/):
- NativeTraceHarness: drive summary knobs via --value-selector /
  --aggregation-type / --max-states (uint16) / --filter
- Capture-SummaryRequest.ps1: repeatable instrument+stage+matrix capture,
  -WithResponse chains the ReadMessage hook
- decode-summary-capture.py: StartQuery2 request diff vs baseline
- decode-summary-response.py: response decode vs SQL ground truth

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-06-20 17:01:42 -04:00
parent 362fcb0ef4
commit 1a7519c803
5 changed files with 531 additions and 38 deletions
@@ -36,6 +36,13 @@ internal static class Program
string runtimeMethodPointerFilters = GetArg(args, "--runtime-method-pointer-filters")
?? "StartDataQuery;StartQuery;GetNextRow;StartEventQuery";
ulong resolutionTicks = ulong.TryParse(GetArg(args, "--resolution-ticks"), out ulong parsedResolutionTicks) ? parsedResolutionTicks : 0;
// Summary-query knobs on HistoryQueryArgs (R1.8/R1.9 capture). Left null/0 = not set,
// so a normal Full read is unaffected. ValueSelector/AggregationType/MaxStates/Filter
// are the native properties that turn a Cyclic/Full query into an analog/state summary.
string? valueSelectorName = GetArg(args, "--value-selector");
string? aggregationTypeName = GetArg(args, "--aggregation-type");
uint maxStates = uint.TryParse(GetArg(args, "--max-states"), out uint parsedMaxStates) ? parsedMaxStates : 0;
string? historyFilter = GetArg(args, "--filter");
DateTime endUtc = TryParseUtc(GetArg(args, "--end-utc")) ?? DateTime.UtcNow;
DateTime startUtc = TryParseUtc(GetArg(args, "--start-utc")) ?? endUtc.AddMinutes(-lookbackMinutes);
@@ -789,6 +796,26 @@ internal static class Program
{
SetProperty(queryArgs, "Resolution", resolutionTicks);
}
// Summary knobs — only set when explicitly supplied so plain reads are untouched.
if (valueSelectorName is not null)
{
Type valueSelectorType = GetType(assembly, "ArchestrA.HistorianValueSelector");
SetProperty(queryArgs, "ValueSelector", Enum.Parse(valueSelectorType, valueSelectorName, ignoreCase: true));
}
if (aggregationTypeName is not null)
{
Type aggregationType = GetType(assembly, "ArchestrA.HistorianAggregationType");
SetProperty(queryArgs, "AggregationType", Enum.Parse(aggregationType, aggregationTypeName, ignoreCase: true));
}
if (maxStates > 0)
{
// HistoryQueryArgs.MaxStates is a UInt16 on the native wrapper.
SetProperty(queryArgs, "MaxStates", checked((ushort)maxStates));
}
if (historyFilter is not null)
{
SetProperty(queryArgs, "Filter", historyFilter);
}
snapshots["QueryArgsBeforeStart"] = SnapshotObject(queryArgs);
startError = Activator.CreateInstance(errorType)!;
@@ -890,6 +917,10 @@ internal static class Program
LookbackMinutes = lookbackMinutes,
RetrievalMode = retrievalModeName,
ResolutionTicks = resolutionTicks,
ValueSelector = valueSelectorName,
AggregationType = aggregationTypeName,
MaxStates = maxStates,
HistoryFilter = historyFilter,
StartUtc = startUtc.ToString("O"),
EndUtc = endUtc.ToString("O"),
OpenSuccess = openSuccess,