Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 88 additions & 4 deletions src/EventLogExpert.Eventing/Common/Events/EventColumnStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ internal void BucketTimeTicksByEventDataHResult(
int bucketCount,
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
long[] targetCodes,
int slotCount,
int[] slotCounts,
Expand All @@ -297,6 +298,10 @@ internal void BucketTimeTicksByEventDataHResult(
? stackalloc int[eligibleProviders.Count]
: new int[eligibleProviders.Count];
ReadOnlySpan<int> eligible = ResolveEligibleSourceIndices(eligibleProviders, eligibleBuffer);
Span<int> userDataPathBuffer = userDataErrorCodePaths.Count <= MaxStackProviderNames
? stackalloc int[userDataErrorCodePaths.Count]
: new int[userDataErrorCodePaths.Count];
ReadOnlySpan<int> userDataPathIndices = ResolveUserDataPathIndices(userDataErrorCodePaths, userDataPathBuffer);
int[] fieldIndexBySchema = NewSchemaFieldMemo();
int offset = 0;

Expand All @@ -315,7 +320,8 @@ internal void BucketTimeTicksByEventDataHResult(
// holds only real failure codes beyond the top-N rather than the non-failure population.
if (eligible.BinarySearch(sourceColumn[row]) < 0) { continue; }

if (!TryGetSealedEventDataHResult(chunk, row, fieldName, fieldIndexBySchema, out long code)) { continue; }
if (!TryGetSealedEventDataHResult(chunk, row, fieldName, fieldIndexBySchema, out long code)
&& !TryGetSealedUserDataHResult(chunk, row, userDataPathIndices, out code)) { continue; }

int bucket = ToBucket(timeColumn[row], minTicks, bucketSpanTicks, bucketCount);
slotCounts[(bucket * slotCount) + SlotForCode(code, targetCodes, otherSlot)]++;
Expand All @@ -336,7 +342,8 @@ internal void BucketTimeTicksByEventDataHResult(

if (!eligibleNames.Contains(pending.Source)) { continue; }

if (!TryGetPendingEventDataHResult(pending, fieldName, out long code)) { continue; }
if (!TryGetPendingEventDataHResult(pending, fieldName, out long code)
&& !TryGetPendingUserDataHResult(pending, userDataErrorCodePaths, out code)) { continue; }

int bucket = ToBucket(pending.TimeCreated.Ticks, minTicks, bucketSpanTicks, bucketCount);
slotCounts[(bucket * slotCount) + SlotForCode(code, targetCodes, otherSlot)]++;
Expand Down Expand Up @@ -666,6 +673,7 @@ internal void CountEventDataHResults(
ReadOnlySpan<int> rankByPhysical,
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
IDictionary<long, int> counts,
CancellationToken cancellationToken)
{
Expand All @@ -674,6 +682,10 @@ internal void CountEventDataHResults(
: new int[eligibleProviders.Count];

ReadOnlySpan<int> eligible = ResolveEligibleSourceIndices(eligibleProviders, eligibleBuffer);
Span<int> userDataPathBuffer = userDataErrorCodePaths.Count <= MaxStackProviderNames
? stackalloc int[userDataErrorCodePaths.Count]
: new int[userDataErrorCodePaths.Count];
ReadOnlySpan<int> userDataPathIndices = ResolveUserDataPathIndices(userDataErrorCodePaths, userDataPathBuffer);
int[] fieldIndexBySchema = NewSchemaFieldMemo();
int offset = 0;

Expand All @@ -689,7 +701,8 @@ internal void CountEventDataHResults(

if (eligible.BinarySearch(sourceColumn[row]) < 0) { continue; }

if (TryGetSealedEventDataHResult(chunk, row, fieldName, fieldIndexBySchema, out long code))
if (TryGetSealedEventDataHResult(chunk, row, fieldName, fieldIndexBySchema, out long code)
|| TryGetSealedUserDataHResult(chunk, row, userDataPathIndices, out code))
{
counts[code] = counts.TryGetValue(code, out int existing) ? existing + 1 : 1;
}
Expand All @@ -710,7 +723,8 @@ internal void CountEventDataHResults(

if (!eligibleNames.Contains(pending.Source)) { continue; }

if (TryGetPendingEventDataHResult(pending, fieldName, out long code))
if (TryGetPendingEventDataHResult(pending, fieldName, out long code)
|| TryGetPendingUserDataHResult(pending, userDataErrorCodePaths, out code))
{
counts[code] = counts.TryGetValue(code, out int existing) ? existing + 1 : 1;
}
Expand Down Expand Up @@ -1218,6 +1232,16 @@ private static (long Min, long Max) ComputeTimeRange(IReadOnlyList<ResolvedEvent
return (min, max);
}

private static bool ContainsOrdinal(IReadOnlyList<string> paths, string value)
{
for (int index = 0; index < paths.Count; index++)
{
if (string.Equals(paths[index], value, StringComparison.Ordinal)) { return true; }
}

return false;
}

private static int FindChunk(int[] prefix, int index)
{
int low = 0;
Expand Down Expand Up @@ -1331,6 +1355,28 @@ private static bool TryGetPendingEventDataHResult(ResolvedEvent pending, string
return false;
}

private static bool TryGetPendingUserDataHResult(ResolvedEvent pending, IReadOnlyList<string> userDataErrorCodePaths, out long code)
{
if (!pending.UserData.IsDefaultOrEmpty)
{
foreach (UserDataField field in pending.UserData)
{
if (!ContainsOrdinal(userDataErrorCodePaths, field.Path)) { continue; }

if (!field.Values.IsDefaultOrEmpty && EventFieldValue.TryParseHResult32(field.Values[0], out uint hresult) && hresult != 0)
{
code = hresult;

return true;
}
}
}

code = 0;

return false;
}

private (EventColumnChunk Chunk, int Row) Locate(int index)
{
int[] prefix = SealedPrefix();
Expand Down Expand Up @@ -1493,6 +1539,18 @@ private int ResolveSchemaFieldIndex(int schemaId, string fieldName)
return SchemaFieldAbsent;
}

private ReadOnlySpan<int> ResolveUserDataPathIndices(IReadOnlyList<string> paths, Span<int> buffer)
{
int count = 0;

for (int index = 0; index < paths.Count; index++)
{
if (count < buffer.Length && _pool.TryGetIndex(paths[index], out int poolIndex)) { buffer[count++] = poolIndex; }
}

return buffer[..count];
}

private int[] SealedPrefix()
{
int[]? prefix = Volatile.Read(ref _sealedPrefix);
Expand Down Expand Up @@ -1612,6 +1670,32 @@ private bool TryGetSealedEventDataHResult(EventColumnChunk chunk, int row, strin
return TryGetHResult32FromRawField(chunk.RowEventDataField(row, fieldIndex), out code) && code != 0;
}

private bool TryGetSealedUserDataHResult(EventColumnChunk chunk, int row, ReadOnlySpan<int> targetPathIndices, out long code)
{
if (!targetPathIndices.IsEmpty)
{
int count = chunk.RowUserDataCount(row);

for (int field = 0; field < count; field++)
{
if (targetPathIndices.IndexOf(chunk.RowUserDataPathIndex(row, field)) < 0) { continue; }

ReadOnlySpan<int> values = chunk.RowUserDataValues(row, field);

if (values.Length > 0 && EventFieldValue.TryParseHResult32(PoolGet(values[0]), out uint hresult) && hresult != 0)
{
code = hresult;

return true;
}
}
}

code = 0;

return false;
}

private bool TryGetWholeNumberFromRawField(in RawEventDataField field, out long code)
{
switch (field.Kind)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ public void BucketTimeTicksByEventDataHResult(
int bucketCount,
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
long[] targetCodes,
int[] slotCounts,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(fieldName);
ArgumentNullException.ThrowIfNull(eligibleProviders);
ArgumentNullException.ThrowIfNull(userDataErrorCodePaths);
ArgumentNullException.ThrowIfNull(targetCodes);
ArgumentNullException.ThrowIfNull(slotCounts);
ArgumentOutOfRangeException.ThrowIfNotEqual(rankByPhysical.Length, Count);
Expand All @@ -81,7 +83,7 @@ public void BucketTimeTicksByEventDataHResult(
int slotCount = targetCodes.Length + 1;
ArgumentOutOfRangeException.ThrowIfLessThan(slotCounts.Length, bucketCount * slotCount);

_store.BucketTimeTicksByEventDataHResult(rankByPhysical, minTicks, bucketSpanTicks, bucketCount, fieldName, eligibleProviders, targetCodes, slotCount, slotCounts, cancellationToken);
_store.BucketTimeTicksByEventDataHResult(rankByPhysical, minTicks, bucketSpanTicks, bucketCount, fieldName, eligibleProviders, userDataErrorCodePaths, targetCodes, slotCount, slotCounts, cancellationToken);
}

public void BucketTimeTicksByEventId(
Expand Down Expand Up @@ -211,15 +213,17 @@ public void CountEventDataHResults(
ReadOnlySpan<int> rankByPhysical,
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
IDictionary<long, int> counts,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(fieldName);
ArgumentNullException.ThrowIfNull(eligibleProviders);
ArgumentNullException.ThrowIfNull(userDataErrorCodePaths);
ArgumentNullException.ThrowIfNull(counts);
ArgumentOutOfRangeException.ThrowIfNotEqual(rankByPhysical.Length, Count);

_store.CountEventDataHResults(rankByPhysical, fieldName, eligibleProviders, counts, cancellationToken);
_store.CountEventDataHResults(rankByPhysical, fieldName, eligibleProviders, userDataErrorCodePaths, counts, cancellationToken);
}

public void CountEventDataValues(ReadOnlySpan<int> rankByPhysical, string fieldName, IDictionary<long, int> counts, CancellationToken cancellationToken)
Expand Down
16 changes: 10 additions & 6 deletions src/EventLogExpert.Eventing/Common/Events/IEventColumnReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ void BucketTimeTicksByEventData(

/// <summary>
/// HRESULT-code variant of <see cref="BucketTimeTicksByEventData" /> for the ErrorCode dimension: each survivor
/// from a provider in <paramref name="eligibleProviders" /> whose <paramref name="fieldName" /> field holds a nonzero
/// 32-bit HRESULT lands in its <paramref name="targetCodes" /> slot else the trailing "other" slot; every
/// ineligible-provider, absent-field, or zero/undecodable row is omitted (contributes to no slot).
/// from a provider in <paramref name="eligibleProviders" /> whose <paramref name="fieldName" /> field - or, when that
/// EventData field is absent, whose UserData carries one of the curated <paramref name="userDataErrorCodePaths" />
/// storage-key leaves (e.g. a servicing <c>Cbs*/ErrorCode</c>) - holds a nonzero 32-bit HRESULT lands in its
/// <paramref name="targetCodes" /> slot else the trailing "other" slot; every ineligible-provider, absent-field, or
/// zero/undecodable row is omitted (contributes to no slot).
Comment thread
jschick04 marked this conversation as resolved.
/// </summary>
void BucketTimeTicksByEventDataHResult(
ReadOnlySpan<int> rankByPhysical,
Expand All @@ -50,6 +52,7 @@ void BucketTimeTicksByEventDataHResult(
int bucketCount,
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
long[] targetCodes,
int[] slotCounts,
CancellationToken cancellationToken);
Expand Down Expand Up @@ -114,10 +117,11 @@ void BucketTimeTicksBySeverity(
/// HRESULT-code variant of <see cref="CountEventDataValues" /> for the ErrorCode dimension: tallies survivors
/// from a provider in <paramref name="eligibleProviders" /> by the nonzero 32-bit HRESULT in
/// <paramref name="fieldName" /> (a <c>win:HexInt32</c> code that sign-extends negative is reinterpreted unsigned, and
/// hex / decimal string spellings fold to one code); ineligible-provider, absent-field, and zero/undecodable rows are
/// omitted.
/// hex / decimal string spellings fold to one code) or, when that EventData field is absent, in one of the curated
/// <paramref name="userDataErrorCodePaths" /> UserData storage-key leaves (e.g. a servicing <c>Cbs*/ErrorCode</c> hex
/// string); ineligible-provider, absent-field, and zero/undecodable rows are omitted.
/// </summary>
void CountEventDataHResults(ReadOnlySpan<int> rankByPhysical, string fieldName, IReadOnlyCollection<string> eligibleProviders, IDictionary<long, int> counts, CancellationToken cancellationToken);
void CountEventDataHResults(ReadOnlySpan<int> rankByPhysical, string fieldName, IReadOnlyCollection<string> eligibleProviders, IReadOnlyList<string> userDataErrorCodePaths, IDictionary<long, int> counts, CancellationToken cancellationToken);

/// <summary>
/// Group-by variant for a named EventData field of allowlisted numeric codes: tallies survivors by the field's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ public static class EventDataValueDecoder
0x800F081F => "CBS_E_SOURCE_MISSING",
0x800F0922 => "CBS_E_INSTALLERS_FAILED",
0x800F0823 => "CBS_E_NEW_SERVICING_STACK_REQUIRED",
0x800F0816 => "CBS_E_DPX_JOB_STATE_SAVED",
0x80073712 => "ERROR_SXS_COMPONENT_STORE_CORRUPT",
0x80D05001 => "DO_E_HTTP_BLOCKSIZE_MISMATCH",
0x80246007 => "WU_E_DM_NOTDOWNLOADED",
0x8024200B => "WU_E_UH_INSTALLERFAILURE",
_ => null
};

Expand Down
9 changes: 7 additions & 2 deletions src/EventLogExpert.Runtime/Histogram/HistogramBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public static class HistogramBuilder
// and servicing providers so a generic errorCode field on an unrelated provider does not pollute the failure view.
private const string ErrorCodeFieldName = "errorCode";

// Microsoft-Windows-Servicing stores its failure HRESULT in a UserData Cbs*ChangeState/ErrorCode leaf (a hex string)
// rather than an EventData errorCode field, so the ErrorCode dimension also probes these curated storage-key paths for
// an eligible row whose EventData lacks the code. CbsPackageInitiateChanges carries no ErrorCode and is not listed.
private static readonly string[] s_updateErrorCodeUserDataPaths = ["CbsPackageChangeState/ErrorCode", "CbsUpdateChangeState/ErrorCode"];

private static readonly string[] s_updateProviders = ["Microsoft-Windows-WindowsUpdateClient", "Microsoft-Windows-Servicing"];

public static HistogramData? Build(
Expand Down Expand Up @@ -75,7 +80,7 @@ private static HistogramData BuildByErrorCode(
CancellationToken cancellationToken)
{
var counts = new Dictionary<long, int>();
view.CountEventDataHResults(ErrorCodeFieldName, s_updateProviders, counts, cancellationToken);
view.CountEventDataHResults(ErrorCodeFieldName, s_updateProviders, s_updateErrorCodeUserDataPaths, counts, cancellationToken);

var minUtc = new DateTime(minTicks, DateTimeKind.Utc);
var maxUtc = new DateTime(maxTicks, DateTimeKind.Utc);
Expand All @@ -100,7 +105,7 @@ private static HistogramData BuildByErrorCode(

int slotCount = targetCodes.Length + 1;
int[] slotCounts = new int[bucketCount * slotCount];
view.BucketTimeTicksByEventDataHResult(minTicks, bucketSpanTicks, bucketCount, ErrorCodeFieldName, s_updateProviders, targetCodes, slotCounts, cancellationToken);
view.BucketTimeTicksByEventDataHResult(minTicks, bucketSpanTicks, bucketCount, ErrorCodeFieldName, s_updateProviders, s_updateErrorCodeUserDataPaths, targetCodes, slotCounts, cancellationToken);

int total = 0;

Expand Down
15 changes: 13 additions & 2 deletions src/EventLogExpert.Runtime/LogTable/CombinedColumnView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public void BucketTimeTicksByEventDataHResult(
int bucketCount,
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
long[] targetCodes,
int[] slotCounts,
CancellationToken cancellationToken)
Expand All @@ -96,6 +97,7 @@ public void BucketTimeTicksByEventDataHResult(
bucketCount,
fieldName,
eligibleProviders,
userDataErrorCodePaths,
targetCodes,
slotCounts,
cancellationToken);
Expand Down Expand Up @@ -155,11 +157,20 @@ public void BucketTimeTicksBySeverity(
}
}

public void CountEventDataHResults(string fieldName, IReadOnlyCollection<string> eligibleProviders, IDictionary<long, int> counts, CancellationToken cancellationToken)
public void CountEventDataHResults(
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
IDictionary<long, int> counts,
CancellationToken cancellationToken)
{
foreach (var view in _views)
{
view.CountEventDataHResults(fieldName, eligibleProviders, counts, cancellationToken);
view.CountEventDataHResults(fieldName,
eligibleProviders,
userDataErrorCodePaths,
counts,
cancellationToken);
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/EventLogExpert.Runtime/LogTable/EventColumnView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public void BucketTimeTicksByEventDataHResult(
int bucketCount,
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
long[] targetCodes,
int[] slotCounts,
CancellationToken cancellationToken) =>
Expand All @@ -67,6 +68,7 @@ public void BucketTimeTicksByEventDataHResult(
bucketCount,
fieldName,
eligibleProviders,
userDataErrorCodePaths,
targetCodes,
slotCounts,
cancellationToken);
Expand Down Expand Up @@ -119,9 +121,10 @@ public void BucketTimeTicksBySeverity(
public void CountEventDataHResults(
string fieldName,
IReadOnlyCollection<string> eligibleProviders,
IReadOnlyList<string> userDataErrorCodePaths,
IDictionary<long, int> counts,
CancellationToken cancellationToken) =>
_reader.CountEventDataHResults(_rankByPhysical, fieldName, eligibleProviders, counts, cancellationToken);
_reader.CountEventDataHResults(_rankByPhysical, fieldName, eligibleProviders, userDataErrorCodePaths, counts, cancellationToken);

public void CountEventDataValues(
string fieldName,
Expand Down
Loading
Loading