* Add initial version of telemetry data extracting

* Initial tracing data transport

* Move buildcheck enabled telemetry to buildtelemetry

* Add unittests

* Fix typos

* Add CheckFriendlyName to telemetry

* Add SAC telemetry

* Adjust after merging
This commit is contained in:
Jan Krivanek 2024-09-13 17:51:15 +02:00 коммит произвёл GitHub
Родитель e4797f3f00
Коммит aeabd8b112
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
33 изменённых файлов: 725 добавлений и 89 удалений

Просмотреть файл

@ -8,6 +8,7 @@ using System.Linq;
using FluentAssertions;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Execution;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Xunit;
@ -77,6 +78,7 @@ namespace Microsoft.Build.UnitTests.BackEnd
EnvironmentVariableReadEventArgs environmentVariableRead = new("env", "message", "file", 0, 0);
GeneratedFileUsedEventArgs generatedFileUsed = new GeneratedFileUsedEventArgs("path", "some content");
BuildSubmissionStartedEventArgs buildSubmissionStarted = new(new Dictionary<string, string> { { "Value1", "Value2" } }, ["Path1"], ["TargetName"], BuildRequestDataFlags.ReplaceExistingProjectInstance, 123);
BuildCheckTracingEventArgs buildCheckTracing = new();
VerifyLoggingPacket(buildFinished, LoggingEventType.BuildFinishedEvent);
VerifyLoggingPacket(buildStarted, LoggingEventType.BuildStartedEvent);
@ -111,6 +113,7 @@ namespace Microsoft.Build.UnitTests.BackEnd
VerifyLoggingPacket(environmentVariableRead, LoggingEventType.EnvironmentVariableReadEvent);
VerifyLoggingPacket(generatedFileUsed, LoggingEventType.GeneratedFileUsedEvent);
VerifyLoggingPacket(buildSubmissionStarted, LoggingEventType.BuildSubmissionStartedEvent);
VerifyLoggingPacket(buildCheckTracing, LoggingEventType.BuildCheckTracingEvent);
}
private static BuildEventContext CreateBuildEventContext()

Просмотреть файл

@ -4,11 +4,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using FluentAssertions;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Profiler;
using Microsoft.Build.Logging;
@ -530,6 +532,27 @@ namespace Microsoft.Build.UnitTests
e => string.Join(", ", e.RawArguments ?? Array.Empty<object>()));
}
[Fact]
public void RoundtripBuildCheckTracingEventArgs()
{
string key1 = "AA";
TimeSpan span1 = TimeSpan.FromSeconds(5);
string key2 = "b";
TimeSpan span2 = TimeSpan.FromSeconds(15);
string key3 = "cCc";
TimeSpan span3 = TimeSpan.FromSeconds(50);
Dictionary<string, TimeSpan> stats = new() { { key1, span1 }, { key2, span2 }, { key3, span3 } };
BuildCheckTracingEventArgs args = new BuildCheckTracingEventArgs(stats);
Roundtrip(args,
e => e.TracingData.InfrastructureTracingData.Keys.Count.ToString(),
e => e.TracingData.InfrastructureTracingData.Keys.ToCsvString(false),
e => e.TracingData.InfrastructureTracingData.Values
.Select(v => v.TotalSeconds.ToString(CultureInfo.InvariantCulture)).ToCsvString(false));
}
[Theory]
[InlineData(true)]
[InlineData(false)]

Просмотреть файл

@ -1070,6 +1070,11 @@ namespace Microsoft.Build.Execution
}
_buildTelemetry.Host = host;
_buildTelemetry.BuildCheckEnabled = _buildParameters!.IsBuildCheckEnabled;
var sacState = NativeMethodsShared.GetSACState();
// The Enforcement would lead to build crash - but let's have the check for completeness sake.
_buildTelemetry.SACEnabled = sacState == NativeMethodsShared.SAC_State.Evaluation || sacState == NativeMethodsShared.SAC_State.Enforcement;
loggingService.LogTelemetry(buildEventContext: null, _buildTelemetry.EventName, _buildTelemetry.GetProperties());
// Clean telemetry to make it ready for next build submission.
_buildTelemetry = null;

Просмотреть файл

@ -43,6 +43,8 @@ public abstract class Check : IDisposable
/// </param>
public abstract void RegisterActions(IBuildCheckRegistrationContext registrationContext);
internal virtual bool IsBuiltIn => false;
public virtual void Dispose()
{ }
}

Просмотреть файл

@ -28,4 +28,6 @@ internal abstract class InternalCheck : Check
this.RegisterInternalActions(internalRegistrationContext);
}
internal override bool IsBuiltIn => true;
}

Просмотреть файл

@ -7,6 +7,7 @@ using System.Linq;
using System.Reflection;
using Microsoft.Build.Experimental.BuildCheck.Infrastructure;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Telemetry;
using Microsoft.Build.Shared;
namespace Microsoft.Build.Experimental.BuildCheck.Acquisition;
@ -53,21 +54,24 @@ internal class BuildCheckAcquisitionModule : IBuildCheckAcquisitionModule
.ForEach(t => checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckBaseTypeNotAssignable", t.Name, t.Assembly));
}
}
catch (ReflectionTypeLoadException ex)
catch (ReflectionTypeLoadException ex) when (ex.LoaderExceptions.Length != 0)
{
if (ex.LoaderExceptions.Length != 0)
foreach (Exception? unrolledEx in ex.LoaderExceptions.Where(e => e != null).Prepend(ex))
{
foreach (Exception? loaderException in ex.LoaderExceptions)
{
checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckFailedRuleLoading", loaderException?.Message);
}
ReportLoadingError(unrolledEx!);
}
}
catch (Exception ex)
{
checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckFailedRuleLoading", ex?.Message);
ReportLoadingError(ex);
}
return checksFactories;
void ReportLoadingError(Exception ex)
{
checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckFailedRuleLoading", ex.Message);
checkContext.DispatchFailedAcquisitionTelemetry(System.IO.Path.GetFileName(checkAcquisitionData.AssemblyPath), ex);
}
}
}

Просмотреть файл

@ -42,6 +42,8 @@ internal sealed class DoubleWritesCheck : Check
registrationContext.RegisterTaskInvocationAction(TaskInvocationAction);
}
internal override bool IsBuiltIn => true;
/// <summary>
/// Contains the first project file + task that wrote the given file during the build.
/// </summary>
@ -126,5 +128,5 @@ internal sealed class DoubleWritesCheck : Check
_filesWritten.Add(fileBeingWritten, (context.Data.ProjectFilePath, context.Data.TaskName));
}
}
}
}
}

Просмотреть файл

@ -49,6 +49,8 @@ internal sealed class NoEnvironmentVariablePropertyCheck : Check
public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) => registrationContext.RegisterEnvironmentVariableReadAction(ProcessEnvironmentVariableReadAction);
internal override bool IsBuiltIn => true;
private void ProcessEnvironmentVariableReadAction(BuildCheckDataContext<EnvironmentVariableCheckData> context)
{
EnvironmentVariableIdentityKey identityKey = new(context.Data.EnvironmentVariableName, context.Data.EnvironmentVariableLocation);

Просмотреть файл

@ -118,6 +118,8 @@ internal class PropertiesUsageCheck : InternalCheck
}
}
internal override bool IsBuiltIn => true;
private Dictionary<string, IMSBuildElementLocation?> _writenProperties = new(MSBuildNameIgnoreCaseComparer.Default);
private HashSet<string> _readProperties = new(MSBuildNameIgnoreCaseComparer.Default);
// For the 'Property Initialized after used' check - we are interested in cases where:

Просмотреть файл

@ -35,6 +35,8 @@ internal sealed class SharedOutputPathCheck : Check
registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction);
}
internal override bool IsBuiltIn => true;
private readonly Dictionary<string, string> _projectsPerOutputPath = new(StringComparer.CurrentCultureIgnoreCase);
private readonly HashSet<string> _projects = new(StringComparer.CurrentCultureIgnoreCase);

Просмотреть файл

@ -107,7 +107,7 @@ internal class BuildCheckBuildEventHandler
{
if (!eventArgs.IsAggregatedGlobalReport)
{
_stats.Merge(eventArgs.TracingData, (span1, span2) => span1 + span2);
_tracingData.MergeIn(eventArgs.TracingData);
}
}
@ -138,36 +138,25 @@ internal class BuildCheckBuildEventHandler
private bool IsMetaProjFile(string? projectFile) => projectFile?.EndsWith(".metaproj", StringComparison.OrdinalIgnoreCase) == true;
private readonly Dictionary<string, TimeSpan> _stats = new Dictionary<string, TimeSpan>();
private readonly BuildCheckTracingData _tracingData = new BuildCheckTracingData();
private void HandleBuildFinishedEvent(BuildFinishedEventArgs eventArgs)
{
_buildCheckManager.ProcessBuildFinished(_checkContextFactory.CreateCheckContext(eventArgs.BuildEventContext!));
_stats.Merge(_buildCheckManager.CreateCheckTracingStats(), (span1, span2) => span1 + span2);
_tracingData.MergeIn(_buildCheckManager.CreateCheckTracingStats());
LogCheckStats(_checkContextFactory.CreateCheckContext(GetBuildEventContext(eventArgs)));
}
private void LogCheckStats(ICheckContext checkContext)
{
Dictionary<string, TimeSpan> infraStats = new Dictionary<string, TimeSpan>();
Dictionary<string, TimeSpan> checkStats = new Dictionary<string, TimeSpan>();
Dictionary<string, TimeSpan> infraStats = _tracingData.InfrastructureTracingData;
// Stats are per rule, while runtime is per check - and check can have multiple rules.
// In case of multi-rule check, the runtime stats are duplicated for each rule.
Dictionary<string, TimeSpan> checkStats = _tracingData.ExtractCheckStats();
foreach (var stat in _stats)
{
if (stat.Key.StartsWith(BuildCheckConstants.infraStatPrefix))
{
string newKey = stat.Key.Substring(BuildCheckConstants.infraStatPrefix.Length);
infraStats[newKey] = stat.Value;
}
else
{
checkStats[stat.Key] = stat.Value;
}
}
BuildCheckTracingEventArgs statEvent = new BuildCheckTracingEventArgs(_stats, true)
BuildCheckTracingEventArgs statEvent = new BuildCheckTracingEventArgs(_tracingData, true)
{ BuildEventContext = checkContext.BuildEventContext };
checkContext.DispatchBuildEvent(statEvent);
@ -177,6 +166,7 @@ internal class BuildCheckBuildEventHandler
checkContext.DispatchAsCommentFromText(MessageImportance.Low, infraData);
string checkData = BuildCsvString("Checks run times", checkStats);
checkContext.DispatchAsCommentFromText(MessageImportance.Low, checkData);
checkContext.DispatchTelemetry(_tracingData);
}
private string BuildCsvString(string title, Dictionary<string, TimeSpan> rowData)

Просмотреть файл

@ -229,10 +229,10 @@ internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider
CheckConfigurationEffective[] configurations;
if (checkFactoryContext.MaterializedCheck == null)
{
CheckConfiguration[] userConfigs =
CheckConfiguration[] userEditorConfigs =
_configurationProvider.GetUserConfigurations(projectFullPath, checkFactoryContext.RuleIds);
if (userConfigs.All(c => !(c.IsEnabled ?? checkFactoryContext.IsEnabledByDefault)))
if (userEditorConfigs.All(c => !(c.IsEnabled ?? checkFactoryContext.IsEnabledByDefault)))
{
// the check was not yet instantiated nor mounted - so nothing to do here now.
return;
@ -242,7 +242,7 @@ internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider
_configurationProvider.GetCustomConfigurations(projectFullPath, checkFactoryContext.RuleIds);
Check uninitializedCheck = checkFactoryContext.Factory();
configurations = _configurationProvider.GetMergedConfigurations(userConfigs, uninitializedCheck);
configurations = _configurationProvider.GetMergedConfigurations(userEditorConfigs, uninitializedCheck);
ConfigurationContext configurationContext = ConfigurationContext.FromDataEnumeration(customConfigData, configurations);
@ -271,7 +271,7 @@ internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider
// price to be paid in that case is slight performance cost.
// Create the wrapper and register to central context
wrapper.StartNewProject(projectFullPath, configurations);
wrapper.StartNewProject(projectFullPath, configurations, userEditorConfigs);
var wrappedContext = new CheckRegistrationContext(wrapper, _buildCheckCentralContext);
check.RegisterActions(wrappedContext);
}
@ -279,13 +279,15 @@ internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider
{
wrapper = checkFactoryContext.MaterializedCheck;
configurations = _configurationProvider.GetMergedConfigurations(projectFullPath, wrapper.Check);
CheckConfiguration[] userEditorConfigs =
_configurationProvider.GetUserConfigurations(projectFullPath, checkFactoryContext.RuleIds);
configurations = _configurationProvider.GetMergedConfigurations(userEditorConfigs, wrapper.Check);
_configurationProvider.CheckCustomConfigurationDataValidity(projectFullPath,
checkFactoryContext.RuleIds[0]);
// Update the wrapper
wrapper.StartNewProject(projectFullPath, configurations);
wrapper.StartNewProject(projectFullPath, configurations, userEditorConfigs);
}
}
@ -346,7 +348,7 @@ internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider
if (checkToRemove.MaterializedCheck is not null)
{
_buildCheckCentralContext.DeregisterCheck(checkToRemove.MaterializedCheck);
_tracingReporter.AddCheckStats(checkToRemove.MaterializedCheck.Check.FriendlyName, checkToRemove.MaterializedCheck.Elapsed);
_ruleTelemetryData.AddRange(checkToRemove.MaterializedCheck.GetRuleTelemetryData());
checkToRemove.MaterializedCheck.Check.Dispose();
}
}
@ -411,19 +413,18 @@ internal sealed class BuildCheckManagerProvider : IBuildCheckManagerProvider
=> _buildEventsProcessor
.ProcessTaskParameterEventArgs(checkContext, taskParameterEventArgs);
public Dictionary<string, TimeSpan> CreateCheckTracingStats()
private readonly List<BuildCheckRuleTelemetryData> _ruleTelemetryData = [];
public BuildCheckTracingData CreateCheckTracingStats()
{
foreach (CheckFactoryContext checkFactoryContext in _checkRegistry)
{
if (checkFactoryContext.MaterializedCheck != null)
{
_tracingReporter.AddCheckStats(checkFactoryContext.FriendlyName, checkFactoryContext.MaterializedCheck.Elapsed);
checkFactoryContext.MaterializedCheck.ClearStats();
_ruleTelemetryData.AddRange(checkFactoryContext.MaterializedCheck.GetRuleTelemetryData());
}
}
_tracingReporter.AddCheckInfraStats();
return _tracingReporter.TracingStats;
return new BuildCheckTracingData(_ruleTelemetryData, _tracingReporter.GetInfrastructureTracingStats());
}
public void FinalizeProcessing(LoggingContext loggingContext)

Просмотреть файл

@ -69,4 +69,12 @@ internal class CheckDispatchingContext : ICheckContext
_eventDispatcher.Dispatch(buildEvent);
}
public void DispatchFailedAcquisitionTelemetry(string assemblyName, Exception exception)
// This is it - no action for replay mode.
{ }
public void DispatchTelemetry(BuildCheckTracingData data)
// This is it - no action for replay mode.
{ }
}

Просмотреть файл

@ -8,6 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Telemetry;
using Microsoft.Build.Shared;
namespace Microsoft.Build.Experimental.BuildCheck;
@ -43,4 +44,18 @@ internal readonly struct CheckLoggingContext(ILoggingService loggingService, Bui
public void DispatchAsWarningFromText(string? subcategoryResourceName, string? errorCode, string? helpKeyword, BuildEventFileInfo file, string message)
=> loggingService
.LogWarningFromText(eventContext, subcategoryResourceName, errorCode, helpKeyword, file, message);
public void DispatchFailedAcquisitionTelemetry(string assemblyName, Exception exception)
{
var telemetryTransportData = KnownTelemetry.BuildCheckTelemetry.ProcessCustomCheckLoadingFailure(assemblyName, exception);
loggingService.LogTelemetry(eventContext, telemetryTransportData.Item1, telemetryTransportData.Item2);
}
public void DispatchTelemetry(BuildCheckTracingData data)
{
foreach ((string, IDictionary<string, string>) telemetryTransportData in KnownTelemetry.BuildCheckTelemetry.ProcessBuildCheckTracingData(data))
{
loggingService.LogTelemetry(eventContext, telemetryTransportData.Item1, telemetryTransportData.Item2);
}
}
}

Просмотреть файл

@ -45,4 +45,14 @@ internal interface ICheckContext
/// Dispatch the instance of <see cref="BuildEventContext"/> as a warning message.
/// </summary>
void DispatchAsWarningFromText(string? subcategoryResourceName, string? errorCode, string? helpKeyword, BuildEventFileInfo file, string message);
/// <summary>
/// Dispatch the telemetry data for a failed acquisition.
/// </summary>
void DispatchFailedAcquisitionTelemetry(string assemblyName, Exception exception);
/// <summary>
/// If supported - dispatches the telemetry data.
/// </summary>
void DispatchTelemetry(BuildCheckTracingData data);
}

Просмотреть файл

@ -17,6 +17,7 @@ namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
internal sealed class CheckWrapper
{
private readonly Stopwatch _stopwatch = new Stopwatch();
private readonly BuildCheckRuleTelemetryData[] _ruleTelemetryData;
/// <summary>
/// Maximum amount of messages that could be sent per check rule.
@ -41,6 +42,23 @@ internal sealed class CheckWrapper
public CheckWrapper(Check check)
{
Check = check;
_ruleTelemetryData = new BuildCheckRuleTelemetryData[check.SupportedRules.Count];
InitializeTelemetryData(_ruleTelemetryData, check);
}
private static void InitializeTelemetryData(BuildCheckRuleTelemetryData[] ruleTelemetryData, Check check)
{
int idx = 0;
foreach (CheckRule checkRule in check.SupportedRules)
{
ruleTelemetryData[idx++] = new BuildCheckRuleTelemetryData(
ruleId: checkRule.Id,
checkFriendlyName: check.FriendlyName,
isBuiltIn: check.IsBuiltIn,
defaultSeverity: (checkRule.DefaultConfiguration.Severity ??
CheckConfigurationEffective.Default.Severity).ToDiagnosticSeverity());
}
}
internal Check Check { get; }
@ -51,29 +69,87 @@ internal sealed class CheckWrapper
// In such case - configuration will be same for all projects. So we do not need to store it per project in a collection.
internal CheckConfigurationEffective? CommonConfig { get; private set; }
// start new project
/// <summary>
/// Ensures the check being configured for a new project (as each project can have different settings)
/// </summary>
/// <param name="fullProjectPath"></param>
/// <param name="effectiveConfigs">Resulting merged configurations per rule (merged from check default and explicit user editorconfig).</param>
/// <param name="editorConfigs">Configurations from editorconfig per rule.</param>
internal void StartNewProject(
string fullProjectPath,
IReadOnlyList<CheckConfigurationEffective> userConfigs)
IReadOnlyList<CheckConfigurationEffective> effectiveConfigs,
IReadOnlyList<CheckConfiguration> editorConfigs)
{
// Let's first update the telemetry data for the rules.
int idx = 0;
foreach (BuildCheckRuleTelemetryData ruleTelemetryData in _ruleTelemetryData)
{
CheckConfigurationEffective effectiveConfig = effectiveConfigs[Math.Max(idx, effectiveConfigs.Count - 1)];
if (editorConfigs[idx].Severity != null)
{
ruleTelemetryData.ExplicitSeverities.Add(editorConfigs[idx].Severity!.Value.ToDiagnosticSeverity());
}
if (effectiveConfig.IsEnabled)
{
ruleTelemetryData.ProjectNamesWhereEnabled.Add(fullProjectPath);
}
idx++;
}
if (!_areStatsInitialized)
{
_areStatsInitialized = true;
CommonConfig = userConfigs[0];
CommonConfig = effectiveConfigs[0];
if (userConfigs.Count == 1)
if (effectiveConfigs.Count == 1)
{
return;
}
}
// The Common configuration is not common anymore - let's nullify it and we will need to fetch configuration per project.
if (CommonConfig == null || !userConfigs.All(t => t.IsSameConfigurationAs(CommonConfig)))
if (CommonConfig == null || !effectiveConfigs.All(t => t.IsSameConfigurationAs(CommonConfig)))
{
CommonConfig = null;
}
}
private void AddDiagnostic(CheckConfigurationEffective configurationEffective)
{
BuildCheckRuleTelemetryData? telemetryData =
_ruleTelemetryData.FirstOrDefault(td => td.RuleId.Equals(configurationEffective.RuleId));
if (telemetryData == null)
{
return;
}
switch (configurationEffective.Severity)
{
case CheckResultSeverity.Suggestion:
telemetryData.IncrementMessagesCount();
break;
case CheckResultSeverity.Warning:
telemetryData.IncrementWarningsCount();
break;
case CheckResultSeverity.Error:
telemetryData.IncrementErrorsCount();
break;
case CheckResultSeverity.Default:
case CheckResultSeverity.None:
default:
break;
}
if (IsThrottled)
{
telemetryData.SetThrottled();
}
}
internal void ReportResult(BuildCheckResult result, ICheckContext checkContext, CheckConfigurationEffective config)
{
if (!IsThrottled)
@ -93,6 +169,9 @@ internal sealed class CheckWrapper
IsThrottled = true;
}
}
// Add the diagnostic to the check wrapper for telemetry purposes.
AddDiagnostic(config);
}
}
@ -102,9 +181,15 @@ internal sealed class CheckWrapper
_areStatsInitialized = false;
}
internal TimeSpan Elapsed => _stopwatch.Elapsed;
internal IReadOnlyList<BuildCheckRuleTelemetryData> GetRuleTelemetryData()
{
foreach (BuildCheckRuleTelemetryData ruleTelemetryData in _ruleTelemetryData)
{
ruleTelemetryData.TotalRuntime = _stopwatch.Elapsed;
}
internal void ClearStats() => _stopwatch.Reset();
return _ruleTelemetryData;
}
internal CleanupScope StartSpan()
{

Просмотреть файл

@ -61,7 +61,7 @@ internal interface IBuildCheckManager
void ProcessCheckAcquisition(CheckAcquisitionData acquisitionData, ICheckContext checksContext);
Dictionary<string, TimeSpan> CreateCheckTracingStats();
BuildCheckTracingData CreateCheckTracingStats();
void FinalizeProcessing(LoggingContext loggingContext);

Просмотреть файл

@ -79,7 +79,7 @@ internal class NullBuildCheckManager : IBuildCheckManager, IBuildEngineDataRoute
{
}
public Dictionary<string, TimeSpan> CreateCheckTracingStats() => new Dictionary<string, TimeSpan>();
public BuildCheckTracingData CreateCheckTracingStats() => new BuildCheckTracingData();
public void ProcessPropertyRead(PropertyReadInfo propertyReadInfo, CheckLoggingContext buildEventContext)
{ }

Просмотреть файл

@ -13,26 +13,10 @@ namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
internal class TracingReporter
{
internal Dictionary<string, TimeSpan> TracingStats { get; } = new();
// Infrastructure time keepers
// TODO: add more timers throughout BuildCheck run
private TimeSpan checkAcquisitionTime;
private TimeSpan checkSetDataSourceTime;
private TimeSpan newProjectChecksTime;
public void AddCheckStats(string name, TimeSpan subtotal)
{
if (TracingStats.TryGetValue(name, out TimeSpan existing))
{
TracingStats[name] = existing + subtotal;
}
else
{
TracingStats[name] = subtotal;
}
}
public void AddAcquisitionStats(TimeSpan subtotal)
{
checkAcquisitionTime += subtotal;
@ -48,14 +32,11 @@ internal class TracingReporter
newProjectChecksTime += subtotal;
}
public void AddCheckInfraStats()
{
var infraStats = new Dictionary<string, TimeSpan>() {
{ $"{BuildCheckConstants.infraStatPrefix}checkAcquisitionTime", checkAcquisitionTime },
{ $"{BuildCheckConstants.infraStatPrefix}checkSetDataSourceTime", checkSetDataSourceTime },
{ $"{BuildCheckConstants.infraStatPrefix}newProjectChecksTime", newProjectChecksTime }
};
TracingStats.Merge(infraStats, (span1, span2) => span1 + span2);
}
public Dictionary<string, TimeSpan> GetInfrastructureTracingStats()
=> new Dictionary<string, TimeSpan>()
{
{ $"{BuildCheckConstants.infraStatPrefix}checkAcquisitionTime", checkAcquisitionTime },
{ $"{BuildCheckConstants.infraStatPrefix}checkSetDataSourceTime", checkSetDataSourceTime },
{ $"{BuildCheckConstants.infraStatPrefix}newProjectChecksTime", newProjectChecksTime }
};
}

Просмотреть файл

@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
namespace Microsoft.Build.Experimental.BuildCheck;
internal static class CheckResultSeverityExtensions
{
public static DiagnosticSeverity? ToDiagnosticSeverity(this CheckResultSeverity? severity)
{
if (severity == null)
{
return null;
}
return ToDiagnosticSeverity(severity.Value);
}
public static DiagnosticSeverity ToDiagnosticSeverity(this CheckResultSeverity severity)
{
return severity switch
{
CheckResultSeverity.Default => DiagnosticSeverity.Default,
CheckResultSeverity.None => DiagnosticSeverity.None,
CheckResultSeverity.Suggestion => DiagnosticSeverity.Suggestion,
CheckResultSeverity.Warning => DiagnosticSeverity.Warning,
CheckResultSeverity.Error => DiagnosticSeverity.Error,
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null)
};
}
}

Просмотреть файл

@ -338,7 +338,11 @@ namespace Microsoft.Build.Logging
private BinaryLogRecordKind Write(BuildCheckTracingEventArgs e)
{
WriteBuildEventArgsFields(e, writeMessage: false);
WriteProperties(e.TracingData);
Dictionary<string, TimeSpan> stats = e.TracingData.ExtractCheckStats();
stats.Merge(e.TracingData.InfrastructureTracingData, (span1, span2) => span1 + span2);
WriteProperties(stats);
return BinaryLogRecordKind.BuildCheckTracing;
}

Просмотреть файл

@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Experimental.BuildCheck;
using Shouldly;
using Xunit;
namespace Microsoft.Build.Framework.UnitTests
{
public class BuildCheckTracingEventArgs_Tests
{
[Fact]
public void SerializationDeserializationTest()
{
string key1 = "AA";
TimeSpan span1 = TimeSpan.FromSeconds(5);
string key2 = "b";
TimeSpan span2 = TimeSpan.FromSeconds(15);
string key3 = "cCc";
TimeSpan span3 = TimeSpan.FromSeconds(50);
Dictionary<string, TimeSpan> stats = new() { { key1, span1 }, { key2, span2 }, { key3, span3 } };
BuildCheckRuleTelemetryData ruleData1 = new("id1", "name1", true, DiagnosticSeverity.Suggestion,
new HashSet<DiagnosticSeverity>() { DiagnosticSeverity.Default, DiagnosticSeverity.Suggestion },
new HashSet<string>() { "aa", "b" }, 5, 2, 8, true, TimeSpan.FromSeconds(123));
BuildCheckRuleTelemetryData ruleData2 = new("id2", "name2", false, DiagnosticSeverity.Error,
new HashSet<DiagnosticSeverity>(),
new HashSet<string>(), 0, 0, 500, false, TimeSpan.FromSeconds(1234));
BuildCheckTracingData data = new(new [] {ruleData1, ruleData2}, stats);
BuildCheckTracingEventArgs arg = new(data);
using MemoryStream stream = new MemoryStream();
using BinaryWriter bw = new BinaryWriter(stream);
arg.WriteToStream(bw);
stream.Position = 0;
using BinaryReader br = new BinaryReader(stream);
BuildCheckTracingEventArgs argDeserialized = new();
int packetVersion = (Environment.Version.Major * 10) + Environment.Version.Minor;
argDeserialized.CreateFromStream(br, packetVersion);
argDeserialized.TracingData.InfrastructureTracingData.ShouldBeEquivalentTo(arg.TracingData.InfrastructureTracingData);
argDeserialized.TracingData.TelemetryData.Keys.ShouldBeEquivalentTo(arg.TracingData.TelemetryData.Keys);
argDeserialized.TracingData.TelemetryData["id1"].ShouldBeEquivalentTo(arg.TracingData.TelemetryData["id1"]);
argDeserialized.TracingData.TelemetryData["id2"].ShouldBeEquivalentTo(arg.TracingData.TelemetryData["id2"]);
}
}
}

Просмотреть файл

@ -19,15 +19,20 @@ internal abstract class BuildCheckEventArgs : BuildEventArgs
/// <summary>
/// Transport mean for the BuildCheck tracing data from additional nodes.
/// </summary>
/// <param name="tracingData"></param>
internal sealed class BuildCheckTracingEventArgs(Dictionary<string, TimeSpan> tracingData) : BuildCheckEventArgs
internal sealed class BuildCheckTracingEventArgs(
BuildCheckTracingData tracingData) : BuildCheckEventArgs
{
internal BuildCheckTracingEventArgs()
: this([])
{
}
: this(new BuildCheckTracingData())
{ }
internal BuildCheckTracingEventArgs(Dictionary<string, TimeSpan> data, bool isAggregatedGlobalReport) : this(data) => IsAggregatedGlobalReport = isAggregatedGlobalReport;
internal BuildCheckTracingEventArgs(Dictionary<string, TimeSpan> executionData)
: this(new BuildCheckTracingData(executionData))
{ }
internal BuildCheckTracingEventArgs(
BuildCheckTracingData tracingData,
bool isAggregatedGlobalReport) : this(tracingData) => IsAggregatedGlobalReport = isAggregatedGlobalReport;
/// <summary>
/// When true, the tracing information is from the whole build for logging purposes
@ -35,18 +40,42 @@ internal sealed class BuildCheckTracingEventArgs(Dictionary<string, TimeSpan> tr
/// </summary>
public bool IsAggregatedGlobalReport { get; private set; } = false;
public Dictionary<string, TimeSpan> TracingData { get; private set; } = tracingData;
public BuildCheckTracingData TracingData { get; private set; } = tracingData;
internal override void WriteToStream(BinaryWriter writer)
{
base.WriteToStream(writer);
writer.Write7BitEncodedInt(TracingData.Count);
foreach (KeyValuePair<string, TimeSpan> kvp in TracingData)
writer.Write7BitEncodedInt(TracingData.InfrastructureTracingData.Count);
foreach (KeyValuePair<string, TimeSpan> kvp in TracingData.InfrastructureTracingData)
{
writer.Write(kvp.Key);
writer.Write(kvp.Value.Ticks);
}
writer.Write7BitEncodedInt(TracingData.TelemetryData.Count);
foreach (BuildCheckRuleTelemetryData data in TracingData.TelemetryData.Values)
{
writer.Write(data.RuleId);
writer.Write(data.CheckFriendlyName);
writer.Write(data.IsBuiltIn);
writer.Write7BitEncodedInt((int)data.DefaultSeverity);
writer.Write7BitEncodedInt(data.ExplicitSeverities.Count);
foreach (DiagnosticSeverity severity in data.ExplicitSeverities)
{
writer.Write7BitEncodedInt((int)severity);
}
writer.Write7BitEncodedInt(data.ProjectNamesWhereEnabled.Count);
foreach (string projectName in data.ProjectNamesWhereEnabled)
{
writer.Write(projectName);
}
writer.Write7BitEncodedInt(data.ViolationMessagesCount);
writer.Write7BitEncodedInt(data.ViolationWarningsCount);
writer.Write7BitEncodedInt(data.ViolationErrorsCount);
writer.Write(data.IsThrottled);
writer.Write(data.TotalRuntime.Ticks);
}
}
internal override void CreateFromStream(BinaryReader reader, int version)
@ -54,14 +83,59 @@ internal sealed class BuildCheckTracingEventArgs(Dictionary<string, TimeSpan> tr
base.CreateFromStream(reader, version);
int count = reader.Read7BitEncodedInt();
TracingData = new Dictionary<string, TimeSpan>(count);
var infrastructureTracingData = new Dictionary<string, TimeSpan>(count);
for (int i = 0; i < count; i++)
{
string key = reader.ReadString();
TimeSpan value = TimeSpan.FromTicks(reader.ReadInt64());
TracingData.Add(key, value);
infrastructureTracingData.Add(key, value);
}
count = reader.Read7BitEncodedInt();
List<BuildCheckRuleTelemetryData> tracingData = new List<BuildCheckRuleTelemetryData>(count);
for (int i = 0; i < count; i++)
{
string ruleId = reader.ReadString();
string checkFriendlyName = reader.ReadString();
bool isBuiltIn = reader.ReadBoolean();
DiagnosticSeverity defaultSeverity = (DiagnosticSeverity)reader.Read7BitEncodedInt();
int explicitSeveritiesCount = reader.Read7BitEncodedInt();
HashSet<DiagnosticSeverity> explicitSeverities =
#if NETSTANDARD2_0
new HashSet<DiagnosticSeverity>();
#else
new HashSet<DiagnosticSeverity>(explicitSeveritiesCount);
#endif
for (int j = 0; j < explicitSeveritiesCount; j++)
{
explicitSeverities.Add((DiagnosticSeverity)reader.Read7BitEncodedInt());
}
int projectNamesWhereEnabledCount = reader.Read7BitEncodedInt();
HashSet<string> projectNamesWhereEnabled =
#if NETSTANDARD2_0
new HashSet<string>();
#else
new HashSet<string>(projectNamesWhereEnabledCount);
#endif
for (int j = 0; j < projectNamesWhereEnabledCount; j++)
{
projectNamesWhereEnabled.Add(reader.ReadString());
}
int violationMessagesCount = reader.Read7BitEncodedInt();
int violationWarningsCount = reader.Read7BitEncodedInt();
int violationErrorsCount = reader.Read7BitEncodedInt();
bool isThrottled = reader.ReadBoolean();
TimeSpan totalRuntime = TimeSpan.FromTicks(reader.ReadInt64());
BuildCheckRuleTelemetryData data = new BuildCheckRuleTelemetryData(
ruleId, checkFriendlyName, isBuiltIn, defaultSeverity, explicitSeverities, projectNamesWhereEnabled,
violationMessagesCount, violationWarningsCount, violationErrorsCount, isThrottled, totalRuntime);
tracingData.Add(data);
}
TracingData = new BuildCheckTracingData(tracingData, infrastructureTracingData);
}
}

Просмотреть файл

@ -0,0 +1,90 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Build.Experimental.BuildCheck;
/// <summary>
/// Telemetry data for a single build check rule.
/// </summary>
/// <param name="ruleId"></param>
/// <param name="checkFriendlyName"></param>
/// <param name="isBuiltIn"></param>
/// <param name="defaultSeverity"></param>
internal sealed class BuildCheckRuleTelemetryData(
string ruleId,
string checkFriendlyName,
bool isBuiltIn,
DiagnosticSeverity defaultSeverity)
{
public BuildCheckRuleTelemetryData(
string ruleId,
string checkFriendlyName,
bool isBuiltIn,
DiagnosticSeverity defaultSeverity,
HashSet<DiagnosticSeverity> explicitSeverities,
HashSet<string> projectNamesWhereEnabled,
int violationMessagesCount,
int violationWarningsCount,
int violationErrorsCount,
bool isThrottled,
TimeSpan totalRuntime) : this(ruleId, checkFriendlyName, isBuiltIn,
defaultSeverity)
{
ExplicitSeverities = explicitSeverities;
ProjectNamesWhereEnabled = projectNamesWhereEnabled;
ViolationMessagesCount = violationMessagesCount;
ViolationWarningsCount = violationWarningsCount;
ViolationErrorsCount = violationErrorsCount;
IsThrottled = isThrottled;
TotalRuntime = totalRuntime;
}
public static BuildCheckRuleTelemetryData Merge(
BuildCheckRuleTelemetryData data1,
BuildCheckRuleTelemetryData data2)
{
if (data1.RuleId != data2.RuleId)
{
throw new InvalidOperationException("Cannot merge telemetry data for different rules.");
}
return new BuildCheckRuleTelemetryData(
data1.RuleId,
data1.CheckFriendlyName,
data1.IsBuiltIn,
data1.DefaultSeverity,
new HashSet<DiagnosticSeverity>(data1.ExplicitSeverities.Union(data2.ExplicitSeverities)),
new HashSet<string>(data1.ProjectNamesWhereEnabled.Union(data2.ProjectNamesWhereEnabled)),
data1.ViolationMessagesCount + data2.ViolationMessagesCount,
data1.ViolationWarningsCount + data2.ViolationWarningsCount,
data1.ViolationErrorsCount + data2.ViolationErrorsCount,
data1.IsThrottled || data2.IsThrottled,
data1.TotalRuntime + data2.TotalRuntime);
}
public string RuleId { get; init; } = ruleId;
public string CheckFriendlyName { get; init; } = checkFriendlyName;
public bool IsBuiltIn { get; init; } = isBuiltIn;
public DiagnosticSeverity DefaultSeverity { get; init; } = defaultSeverity;
/// <summary>
/// A set of explicitly set severities (through editorconfig(s)) for the rule. There can be multiple - as different projects can have different settings.
/// </summary>
public HashSet<DiagnosticSeverity> ExplicitSeverities { get; init; } = [];
public HashSet<string> ProjectNamesWhereEnabled { get; init; } = [];
public int ViolationMessagesCount { get; private set; }
public int ViolationWarningsCount { get; private set; }
public int ViolationErrorsCount { get; private set; }
public int ViolationsCount => ViolationMessagesCount + ViolationWarningsCount + ViolationErrorsCount;
public bool IsThrottled { get; private set; }
public TimeSpan TotalRuntime { get; set; }
public void IncrementMessagesCount() => ViolationMessagesCount++;
public void IncrementWarningsCount() => ViolationWarningsCount++;
public void IncrementErrorsCount() => ViolationErrorsCount++;
public void SetThrottled() => IsThrottled = true;
}

Просмотреть файл

@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Build.Experimental.BuildCheck;
/// <summary>
/// Wrapper for the tracing data to be transferred from the worker nodes to the central node.
/// </summary>
/// <param name="telemetryData"></param>
/// <param name="infrastructureTracingData"></param>
internal sealed class BuildCheckTracingData(
Dictionary<string, BuildCheckRuleTelemetryData> telemetryData,
Dictionary<string, TimeSpan> infrastructureTracingData)
{
public BuildCheckTracingData(IReadOnlyList<BuildCheckRuleTelemetryData> telemetryData, Dictionary<string, TimeSpan> infrastructureTracingData)
: this(telemetryData.ToDictionary(data => data.RuleId), infrastructureTracingData)
{ }
public BuildCheckTracingData()
: this(new Dictionary<string, BuildCheckRuleTelemetryData>(), [])
{ }
internal BuildCheckTracingData(Dictionary<string, TimeSpan> executionData)
: this(new Dictionary<string, BuildCheckRuleTelemetryData>(), executionData)
{ }
public Dictionary<string, BuildCheckRuleTelemetryData> TelemetryData { get; private set; } = telemetryData;
public Dictionary<string, TimeSpan> InfrastructureTracingData { get; private set; } = infrastructureTracingData;
/// <summary>
/// Gets the runtime stats per individual checks friendly names
/// </summary>
public Dictionary<string, TimeSpan> ExtractCheckStats() =>
// Stats are per rule, while runtime is per check - and check can have multiple rules.
// In case of multi-rule check, the runtime stats are duplicated for each rule.
TelemetryData
.GroupBy(d => d.Value.CheckFriendlyName)
.ToDictionary(g => g.Key, g => g.First().Value.TotalRuntime);
public void MergeIn(BuildCheckTracingData other)
{
InfrastructureTracingData.Merge(other.InfrastructureTracingData, (span1, span2) => span1 + span2);
TelemetryData.Merge(other.TelemetryData, BuildCheckRuleTelemetryData.Merge);
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.Build.Experimental.BuildCheck;
internal enum DiagnosticSeverity
{
/// <summary>
/// When set, the default value of the BuildCheck rule will be used.
/// </summary>
Default,
/// <summary>
/// When set to None the rule will not run.
/// </summary>
None,
/// <summary>
/// Information level message.
/// </summary>
Suggestion,
/// <summary>
/// Results a warning in build if the BuildCheck rule applied.
/// </summary>
Warning,
/// <summary>
/// Results an error in build if the BuildCheck rule applied.
/// </summary>
Error
}

Просмотреть файл

@ -66,4 +66,33 @@ internal static class EnumerableExtensions
}
}
}
/// <summary>
/// Adds a content of given list to current dictionary.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dict">Dictionary to receive another values.</param>
/// <param name="another">List to be merged into current.</param>
/// <param name="extractKey">Way of getting a key of an incoming value.</param>
/// <param name="mergeValues">Way of resolving keys conflicts.</param>
public static void Merge<TKey, TValue>(
this IDictionary<TKey, TValue> dict,
IReadOnlyList<TValue> another,
Func<TValue, TKey> extractKey,
Func<TValue, TValue, TValue> mergeValues)
{
foreach (var mergeValue in another)
{
TKey key = extractKey(mergeValue);
if (!dict.TryGetValue(key, out TValue? value))
{
dict[key] = mergeValue;
}
else
{
dict[key] = mergeValues(value, mergeValue);
}
}
}
}

Просмотреть файл

@ -127,7 +127,7 @@ public abstract class BuildExceptionBase : Exception
string? deserializedStackTrace = reader.ReadOptionalString();
string? source = reader.ReadOptionalString();
string? helpLink = reader.ReadOptionalString();
int hResult = reader.ReadOptionalInt32();
int hResult = reader.ReadOptionalInt32() ?? 0;
IDictionary<string, string?>? customKeyedSerializedData = null;
if (reader.ReadByte() == 1)

Просмотреть файл

@ -664,11 +664,20 @@ internal static class NativeMethods
}
}
private static SAC_State? s_sacState;
/// <summary>
/// Get from registry state of the Smart App Control (SAC) on the system.
/// </summary>
/// <returns>State of SAC</returns>
internal static SAC_State GetSACState()
{
s_sacState ??= GetSACStateInternal();
return s_sacState.Value;
}
internal static SAC_State GetSACStateInternal()
{
if (IsWindows)
{

Просмотреть файл

@ -0,0 +1,96 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Build.Experimental.BuildCheck;
namespace Microsoft.Build.Framework.Telemetry;
internal class BuildCheckTelemetry
{
private const string FailedAcquisitionEventName = "buildcheck/acquisitionfailure";
private const string RunEventName = "buildcheck/run";
private const string RuleStatsEventName = "buildcheck/rule";
private Guid _submissionId = Guid.NewGuid();
/// <summary>
/// Translates failed acquisition event to telemetry transport data.
/// </summary>
internal (string, IDictionary<string, string>) ProcessCustomCheckLoadingFailure(string assemblyName,
Exception exception)
{
var properties = new Dictionary<string, string>();
properties["SubmissionId"] = _submissionId.ToString();
properties["AssemblyName"] = assemblyName;
string? exceptionType = exception.GetType().FullName;
if (exceptionType != null)
{
properties["ExceptionType"] = exceptionType;
}
if (exception.Message != null)
{
properties["ExceptionMessage"] = exception.Message;
}
return (FailedAcquisitionEventName, properties);
}
/// <summary>
/// Translates BuildCheck tracing data to telemetry transport data.
/// </summary>
internal IEnumerable<(string, IDictionary<string, string>)> ProcessBuildCheckTracingData(BuildCheckTracingData data)
{
int rulesCount = data.TelemetryData.Count;
int customRulesCount = data.TelemetryData.Count(t => !t.Value.IsBuiltIn);
int violationsCount = data.TelemetryData.Sum(t => t.Value.ViolationsCount);
long runtimeTicks = data.ExtractCheckStats().Sum(v => v.Value.Ticks);
runtimeTicks += data.InfrastructureTracingData.Sum(v => v.Value.Ticks);
TimeSpan totalRuntime = new TimeSpan(runtimeTicks);
var properties = new Dictionary<string, string>();
properties["SubmissionId"] = _submissionId.ToString();
properties["RulesCount"] = rulesCount.ToString(CultureInfo.InvariantCulture);
properties["CustomRulesCount"] = customRulesCount.ToString(CultureInfo.InvariantCulture);
properties["ViolationsCount"] = violationsCount.ToString(CultureInfo.InvariantCulture);
properties["TotalRuntimeInMilliseconds"] = totalRuntime.TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
yield return (RunEventName, properties);
foreach (BuildCheckRuleTelemetryData buildCheckRuleTelemetryData in data.TelemetryData.Values)
{
properties = new Dictionary<string, string>();
properties["SubmissionId"] = _submissionId.ToString();
properties["RuleId"] = buildCheckRuleTelemetryData.RuleId;
properties["CheckFriendlyName"] = buildCheckRuleTelemetryData.CheckFriendlyName;
properties["IsBuiltIn"] = buildCheckRuleTelemetryData.IsBuiltIn.ToString(CultureInfo.InvariantCulture);
properties["DefaultSeverityId"] = ((int)buildCheckRuleTelemetryData.DefaultSeverity).ToString(CultureInfo.InvariantCulture);
properties["DefaultSeverity"] = buildCheckRuleTelemetryData.DefaultSeverity.ToString();
properties["EnabledProjectsCount"] = buildCheckRuleTelemetryData.ProjectNamesWhereEnabled.Count.ToString(CultureInfo.InvariantCulture);
if (buildCheckRuleTelemetryData.ExplicitSeverities.Any())
{
properties["ExplicitSeverities"] = buildCheckRuleTelemetryData.ExplicitSeverities
.Select(s => s.ToString()).ToCsvString(false);
properties["ExplicitSeveritiesIds"] = buildCheckRuleTelemetryData.ExplicitSeverities
.Select(s => ((int)s).ToString(CultureInfo.InvariantCulture)).ToCsvString(false);
}
properties["ViolationMessagesCount"] = buildCheckRuleTelemetryData.ViolationMessagesCount.ToString(CultureInfo.InvariantCulture);
properties["ViolationWarningsCount"] = buildCheckRuleTelemetryData.ViolationWarningsCount.ToString(CultureInfo.InvariantCulture);
properties["ViolationErrorsCount"] = buildCheckRuleTelemetryData.ViolationErrorsCount.ToString(CultureInfo.InvariantCulture);
properties["IsThrottled"] = buildCheckRuleTelemetryData.IsThrottled.ToString(CultureInfo.InvariantCulture);
properties["TotalRuntimeInMilliseconds"] = buildCheckRuleTelemetryData.TotalRuntime.TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
yield return (RuleStatsEventName, properties);
}
// set for the new submission in case of build server
_submissionId = Guid.NewGuid();
}
}

Просмотреть файл

@ -74,6 +74,16 @@ namespace Microsoft.Build.Framework.Telemetry
/// </summary>
public string? Host { get; set; }
/// <summary>
/// True if buildcheck was used.
/// </summary>
public bool? BuildCheckEnabled { get; set; }
/// <summary>
/// True if Smart Application Control was enabled.
/// </summary>
public bool? SACEnabled { get; set; }
/// <summary>
/// State of MSBuild server process before this build.
/// One of 'cold', 'hot', null (if not run as server)
@ -145,6 +155,16 @@ namespace Microsoft.Build.Framework.Telemetry
properties["BuildEngineVersion"] = Version.ToString();
}
if (BuildCheckEnabled != null)
{
properties["BuildCheckEnabled"] = BuildCheckEnabled.Value.ToString(CultureInfo.InvariantCulture);
}
if (SACEnabled != null)
{
properties["SACEnabled"] = SACEnabled.Value.ToString(CultureInfo.InvariantCulture);
}
return properties;
}
}

Просмотреть файл

@ -20,4 +20,9 @@ internal static class KnownTelemetry
/// Describes how logging was configured.
/// </summary>
public static LoggingConfigurationTelemetry LoggingConfigurationTelemetry { get; } = new LoggingConfigurationTelemetry();
/// <summary>
/// Describes if and how BuildCheck was used.
/// </summary>
public static BuildCheckTelemetry BuildCheckTelemetry { get; } = new BuildCheckTelemetry();
}

Просмотреть файл

@ -22,9 +22,9 @@ namespace Microsoft.Build.Shared
#if !TASKHOST
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
public static int ReadOptionalInt32(this BinaryReader reader)
public static int? ReadOptionalInt32(this BinaryReader reader)
{
return reader.ReadByte() == 0 ? 0 : reader.ReadInt32();
return reader.ReadByte() == 0 ? null : reader.ReadInt32();
}
#if !TASKHOST