Skip to content
5 changes: 5 additions & 0 deletions src/Build.UnitTests/BackEnd/MockLoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ public ICollection<string> RegisteredSinkNames
}
}

/// <summary>
/// Returns the number of events currently queued for processing.
/// </summary>
public int EventQueueCount => 0;

/// <summary>
/// Properties to serialize from the child node to the parent node
/// </summary>
Expand Down
76 changes: 53 additions & 23 deletions src/Build/BackEnd/BuildManager/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1261,41 +1261,71 @@ private void RecordCrashTelemetry(Exception exception, bool isUnhandled)
/// </summary>
private void EmitEndBuildHangDiagnostics(string waitPhase, Stopwatch hangWatch)
{
int pendingSubmissionCount;
int submissionsWithResultNoLogging = 0;
bool threadExceptionRecorded;
int unmatchedProjectStartedCount;
string? host;
var telemetry = new CrashTelemetry
{
ExitType = CrashExitType.EndBuildHang,
EndBuildWaitPhase = waitPhase,
EndBuildWaitDurationMs = hangWatch.ElapsedMilliseconds,
BuildEngineVersion = ProjectCollection.Version?.ToString(),
BuildEngineFrameworkName = NativeMethodsShared.FrameworkName,
IsStandaloneExecution = _buildTelemetry?.IsStandaloneExecution ?? false,
MaxNodeCount = _buildParameters?.MaxNodeCount,
ActiveNodeCount = _activeNodes?.Count,
};
Comment thread
YuliiaKovalova marked this conversation as resolved.
Outdated

lock (_syncLock)
{
var submissionDetailParts = new List<string>(_buildSubmissions.Count);
foreach (BuildSubmissionBase submission in _buildSubmissions.Values)
{
if (submission.BuildResultBase is not null && !submission.LoggingCompleted)
{
submissionsWithResultNoLogging++;
telemetry.SubmissionsWithResultNoLogging = (telemetry.SubmissionsWithResultNoLogging ?? 0) + 1;
}

submissionDetailParts.Add(string.Join(":",
submission.SubmissionId,
submission.IsStarted,
submission.BuildResultBase is not null,
submission.BuildResultBase?.Exception is not null,
submission.LoggingCompleted));
}

pendingSubmissionCount = _buildSubmissions.Count;
threadExceptionRecorded = _threadException is not null;
unmatchedProjectStartedCount = _projectStartedEvents.Count;
host = _buildTelemetry?.BuildEngineHost ?? BuildEnvironmentState.GetHostName();
telemetry.PendingSubmissionCount = _buildSubmissions.Count;
telemetry.ThreadExceptionRecorded = _threadException is not null;
telemetry.UnmatchedProjectStartedCount = _projectStartedEvents.Count;
telemetry.BuildEngineHost = _buildTelemetry?.BuildEngineHost ?? BuildEnvironmentState.GetHostName();
telemetry.IsShuttingDown = _shuttingDown;
telemetry.IsCancellationRequested = _executionCancellationTokenSource?.IsCancellationRequested ?? false;
telemetry.WorkQueueDepth = _workQueue?.InputCount;

if (submissionDetailParts.Count > 0)
{
telemetry.SubmissionDetails = string.Join(";", submissionDetailParts);
}
}

CrashTelemetryRecorder.CollectAndEmitEndBuildHangDiagnostics(
waitPhase,
hangWatch.ElapsedMilliseconds,
pendingSubmissionCount,
submissionsWithResultNoLogging,
threadExceptionRecorded,
unmatchedProjectStartedCount,
ProjectCollection.Version?.ToString(),
NativeMethodsShared.FrameworkName,
host,
isStandaloneExecution: _buildTelemetry?.IsStandaloneExecution ?? false,
maxNodeCount: _buildParameters?.MaxNodeCount,
activeNodeCount: _activeNodes?.Count);
try
{
ILoggingService? loggingService = ((IBuildComponentHost)this).LoggingService;
if (loggingService is not null)
{
telemetry.LoggingServiceState = loggingService.ServiceState.ToString();
telemetry.LoggingEventQueueDepth = loggingService.EventQueueCount;

ICollection<string>? loggerTypes = loggingService.RegisteredLoggerTypeNames;
if (loggerTypes is { Count: > 0 })
{
telemetry.RegisteredLoggerTypeNames = string.Join(";", loggerTypes);
}
}
}
catch
{
// Best effort: accessing the logging service may fail during shutdown.
}

CrashTelemetryRecorder.EmitEndBuildHangDiagnostics(telemetry);
}

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Build/BackEnd/Components/Logging/ILoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ ICollection<string> RegisteredSinkNames
get;
}

/// <summary>
/// Returns the number of events currently queued for processing.
/// Used for hang diagnostics to determine if the logging pipeline is backed up.
/// Returns 0 for synchronous logging or when the queue is not available.
/// </summary>
int EventQueueCount
{
get;
}

/// <summary>
/// List of properties to serialize from the child node
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Build/BackEnd/Components/Logging/LoggingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,13 @@ public int MaxCPUCount
/// </summary>
public LoggerMode LoggingMode => _logMode;

/// <summary>
/// Returns the number of events currently queued for processing.
/// Used for hang diagnostics to determine if the logging pipeline is backed up.
/// Returns 0 for synchronous logging or when the queue is not available.
/// </summary>
public int EventQueueCount => _eventQueue?.Count ?? 0;

/// <summary>
/// Get of warnings to treat as errors. An empty non-null set will treat all warnings as errors.
/// </summary>
Expand Down
Loading