From 54735941fee4c908ddc0ce6ed8af347295c75ec1 Mon Sep 17 00:00:00 2001 From: Ismayil Ismayilov Date: Fri, 23 Jun 2023 13:40:38 +0400 Subject: [PATCH 1/5] Added telemetry classes to produce inline publish functionality --- src/Agent.Listener/Agent.cs | 15 +++ .../Telemetry/CustomerIntelligenceServer.cs | 35 ++++++ .../Telemetry/TelemetryPublisher.cs | 112 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 src/Agent.Listener/Telemetry/CustomerIntelligenceServer.cs create mode 100644 src/Agent.Listener/Telemetry/TelemetryPublisher.cs diff --git a/src/Agent.Listener/Agent.cs b/src/Agent.Listener/Agent.cs index 7628352679..2213ee5e79 100644 --- a/src/Agent.Listener/Agent.cs +++ b/src/Agent.Listener/Agent.cs @@ -15,6 +15,10 @@ using System.IO; using System.Reflection; using System.Runtime.CompilerServices; +using Microsoft.TeamFoundation.TestClient.PublishTestResults.Telemetry; +using Microsoft.VisualStudio.Services.Agent.Listener.Telemetry; +using System.Collections.Generic; +using Newtonsoft.Json; namespace Microsoft.VisualStudio.Services.Agent.Listener { @@ -227,6 +231,17 @@ public async Task ExecuteCommand(CommandSettings command) } } } + + var tp = HostContext.GetService(); + + // Sample telemetry data to publish + //var cmd = new Command("area", "feature"); + //var props = new Dictionary() { { "test", "data" } }; + //cmd.Data = JsonConvert.SerializeObject(props); + //cmd.Properties.Add("area", "PipelinesTasks"); + //cmd.Properties.Add("feature", "ExecutionHandler"); + //await tp.PublishEvent(HostContext, cmd); + // Run the agent interactively or as service return await RunAsync(settings, command.GetRunOnce()); } diff --git a/src/Agent.Listener/Telemetry/CustomerIntelligenceServer.cs b/src/Agent.Listener/Telemetry/CustomerIntelligenceServer.cs new file mode 100644 index 0000000000..3fba6079d5 --- /dev/null +++ b/src/Agent.Listener/Telemetry/CustomerIntelligenceServer.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.VisualStudio.Services.Agent.Util; +using Microsoft.VisualStudio.Services.CustomerIntelligence.WebApi; +using Microsoft.VisualStudio.Services.WebApi; +using Microsoft.VisualStudio.Services.WebPlatform; + +namespace Microsoft.VisualStudio.Services.Agent.Listener.Telemetry +{ + [ServiceLocator(Default = typeof(CustomerIntelligenceServer))] + public interface ICustomerIntelligenceServer : IAgentService + { + void Initialize(VssConnection connection); + Task PublishEventsAsync(CustomerIntelligenceEvent[] ciEvents); + } + + // This service is used for tracking task events which are applicable for VSTS internal tasks + public class CustomerIntelligenceServer : AgentService, ICustomerIntelligenceServer + { + private CustomerIntelligenceHttpClient _ciClient; + + public void Initialize(VssConnection connection) + { + ArgUtil.NotNull(connection, nameof(connection)); + _ciClient = connection.GetClient(); + } + + public Task PublishEventsAsync(CustomerIntelligenceEvent[] ciEvents) + { + return _ciClient.PublishEventsAsync(events: ciEvents); + } + } +} diff --git a/src/Agent.Listener/Telemetry/TelemetryPublisher.cs b/src/Agent.Listener/Telemetry/TelemetryPublisher.cs new file mode 100644 index 0000000000..5faa4fde0c --- /dev/null +++ b/src/Agent.Listener/Telemetry/TelemetryPublisher.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Services.Agent.Listener.Configuration; +using Microsoft.VisualStudio.Services.Agent.Util; +using Microsoft.VisualStudio.Services.Common; +using Microsoft.VisualStudio.Services.WebPlatform; + +using Newtonsoft.Json; + +namespace Microsoft.VisualStudio.Services.Agent.Listener.Telemetry +{ + [ServiceLocator(Default = typeof(TelemetryPublisher))] + public interface IAgenetListenerTelemetryPublisher : IAgentService + { + public Task PublishEvent(IHostContext context, Command command); + } + + public sealed class TelemetryPublisher : AgentService, IAgenetListenerTelemetryPublisher + { + private ICustomerIntelligenceServer _ciService; + + public string Name => "publish"; + public List Aliases => null; + + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA2000:Dispose objects before losing scope", MessageId = "jobServer")] + public async Task PublishEvent(IHostContext context, Command command) + { + Trace.Info("Loading Credentials"); + var credMgr = HostContext.GetService(); + VssCredentials creds = credMgr.LoadCredentials(); + + ArgUtil.NotNull(creds, nameof(creds)); + + var configManager = HostContext.GetService(); + AgentSettings settings = configManager.LoadSettings(); + + var vssConnection = VssUtil.CreateConnection(new Uri(settings.ServerUrl), creds, Trace); + try + { + _ciService = HostContext.GetService(); + _ciService.Initialize(vssConnection); + } + + catch (Exception ex) + { + Trace.Warning(StringUtil.Loc("TelemetryCommandFailed", ex.Message)); + return; + } + + ArgUtil.NotNull(context, nameof(context)); + ArgUtil.NotNull(command, nameof(command)); + Dictionary eventProperties = command.Properties; + string data = command.Data; + string area; + if (!eventProperties.TryGetValue(WellKnownEventTrackProperties.Area, out area) || string.IsNullOrEmpty(area)) + { + throw new ArgumentException(StringUtil.Loc("ArgumentNeeded", "Area")); + } + + string feature; + if (!eventProperties.TryGetValue(WellKnownEventTrackProperties.Feature, out feature) || string.IsNullOrEmpty(feature)) + { + throw new ArgumentException(StringUtil.Loc("ArgumentNeeded", "Feature")); + } + + if (string.IsNullOrEmpty(data)) + { + throw new ArgumentException(StringUtil.Loc("ArgumentNeeded", "EventTrackerData")); + } + + CustomerIntelligenceEvent ciEvent; + try + { + var ciProperties = JsonConvert.DeserializeObject>(data); + ciEvent = new CustomerIntelligenceEvent() + { + Area = area, + Feature = feature, + Properties = ciProperties + }; + } + catch (Exception ex) + { + throw new ArgumentException(StringUtil.Loc("TelemetryCommandDataError", data, ex.Message)); + } + + await PublishEventsAsync(context, ciEvent); + } + + private async Task PublishEventsAsync(IHostContext context, CustomerIntelligenceEvent ciEvent) + { + try + { + await _ciService.PublishEventsAsync(new CustomerIntelligenceEvent[] { ciEvent }); + } + catch (Exception ex) + { + Trace.Warning(StringUtil.Loc("TelemetryCommandFailed", ex.Message)); + } + } + } + internal static class WellKnownEventTrackProperties + { + internal static readonly string Area = "area"; + internal static readonly string Feature = "feature"; + } +} \ No newline at end of file From b683b83fec8d3a62c2bb133459dc6872d98f2f6d Mon Sep 17 00:00:00 2001 From: Ismayil Ismayilov Date: Fri, 30 Jun 2023 19:40:05 +0400 Subject: [PATCH 2/5] Clean PR and remove unnecessary changes --- src/Agent.Listener/Agent.cs | 36 +++++++++++++----- .../Telemetry/TelemetryPublisher.cs | 37 +++++++++++-------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/Agent.Listener/Agent.cs b/src/Agent.Listener/Agent.cs index 2213ee5e79..f25e8e2af4 100644 --- a/src/Agent.Listener/Agent.cs +++ b/src/Agent.Listener/Agent.cs @@ -232,15 +232,33 @@ public async Task ExecuteCommand(CommandSettings command) } } - var tp = HostContext.GetService(); - - // Sample telemetry data to publish - //var cmd = new Command("area", "feature"); - //var props = new Dictionary() { { "test", "data" } }; - //cmd.Data = JsonConvert.SerializeObject(props); - //cmd.Properties.Add("area", "PipelinesTasks"); - //cmd.Properties.Add("feature", "ExecutionHandler"); - //await tp.PublishEvent(HostContext, cmd); + //Publish inital telemetry data + var telemetryPublisher = HostContext.GetService(); + + try + { + var systemVersion = PlatformUtil.GetSystemVersion(); + + Dictionary telemetryData = new Dictionary + { + { "OS", PlatformUtil.GetSystemId() ?? "" }, + { "OSVersion", systemVersion?.Name?.ToString() ?? "" }, + { "OSBuild", systemVersion?.Version?.ToString() ?? "" }, + { "configuredAsService", $"{configuredAsService}"}, + { "startupType", startupTypeAsString } + }; + var cmd = new Command("telemetry", "publish"); + cmd.Data = JsonConvert.SerializeObject(telemetryData); + cmd.Properties.Add("area", "PipelinesTasks"); + cmd.Properties.Add("feature", "AgentListener"); + await telemetryPublisher.PublishEvent(HostContext, cmd); + } + + catch (Exception ex) + { + Trace.Warning($"Unable to publish telemetry data. {ex}"); + } + // Run the agent interactively or as service return await RunAsync(settings, command.GetRunOnce()); diff --git a/src/Agent.Listener/Telemetry/TelemetryPublisher.cs b/src/Agent.Listener/Telemetry/TelemetryPublisher.cs index 5faa4fde0c..967b2ecdb0 100644 --- a/src/Agent.Listener/Telemetry/TelemetryPublisher.cs +++ b/src/Agent.Listener/Telemetry/TelemetryPublisher.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; + using Microsoft.VisualStudio.Services.Agent.Listener.Configuration; using Microsoft.VisualStudio.Services.Agent.Util; using Microsoft.VisualStudio.Services.Common; @@ -30,30 +31,34 @@ public sealed class TelemetryPublisher : AgentService, IAgenetListenerTelemetryP [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA2000:Dispose objects before losing scope", MessageId = "jobServer")] public async Task PublishEvent(IHostContext context, Command command) { - Trace.Info("Loading Credentials"); - var credMgr = HostContext.GetService(); - VssCredentials creds = credMgr.LoadCredentials(); - ArgUtil.NotNull(creds, nameof(creds)); + if (_ciService == null) + { + var credMgr = context.GetService(); + VssCredentials creds = credMgr.LoadCredentials(); - var configManager = HostContext.GetService(); - AgentSettings settings = configManager.LoadSettings(); + ArgUtil.NotNull(creds, nameof(creds)); - var vssConnection = VssUtil.CreateConnection(new Uri(settings.ServerUrl), creds, Trace); - try - { - _ciService = HostContext.GetService(); - _ciService.Initialize(vssConnection); - } + var configManager = context.GetService(); + AgentSettings settings = configManager.LoadSettings(); - catch (Exception ex) - { - Trace.Warning(StringUtil.Loc("TelemetryCommandFailed", ex.Message)); - return; + var vssConnection = VssUtil.CreateConnection(new Uri(settings.ServerUrl), creds, Trace); + try + { + _ciService = context.GetService(); + _ciService.Initialize(vssConnection); + } + + catch (Exception ex) + { + Trace.Warning(StringUtil.Loc("TelemetryCommandFailed", ex.Message)); + return; + } } ArgUtil.NotNull(context, nameof(context)); ArgUtil.NotNull(command, nameof(command)); + Dictionary eventProperties = command.Properties; string data = command.Data; string area; From a98b50a9815bb455c7b5444db968b5db1e322660 Mon Sep 17 00:00:00 2001 From: Ismayil Ismayilov Date: Mon, 3 Jul 2023 15:40:10 +0400 Subject: [PATCH 3/5] Added DI for tests --- src/Test/L0/Listener/AgentL0.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Test/L0/Listener/AgentL0.cs b/src/Test/L0/Listener/AgentL0.cs index 5ec33cc5c5..14250ab78d 100644 --- a/src/Test/L0/Listener/AgentL0.cs +++ b/src/Test/L0/Listener/AgentL0.cs @@ -13,6 +13,7 @@ using Microsoft.VisualStudio.Services.WebApi; using Pipelines = Microsoft.TeamFoundation.DistributedTask.Pipelines; using Microsoft.VisualStudio.Services.Agent.Util; +using Microsoft.VisualStudio.Services.Agent.Listener.Telemetry; namespace Microsoft.VisualStudio.Services.Agent.Tests.Listener { @@ -29,6 +30,7 @@ public sealed class AgentL0 private Mock _proxy; private Mock _cert; private Mock _updater; + private Mock _listenerTelemetryPublisher; public AgentL0() { @@ -43,6 +45,7 @@ public AgentL0() _proxy = new Mock(); _cert = new Mock(); _updater = new Mock(); + _listenerTelemetryPublisher = new Mock(); } private AgentJobRequestMessage CreateJobRequestMessage(string jobName) @@ -81,6 +84,8 @@ public async void TestRunAsync() hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); + agent.Initialize(hc); var settings = new AgentSettings { @@ -193,7 +198,7 @@ public async void TestExecuteCommandForRunAsService(string[] args, bool configur hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); - + hc.SetSingleton(_listenerTelemetryPublisher.Object); var command = new CommandSettings(hc, args); _configurationManager.Setup(x => x.IsConfigured()).Returns(true); @@ -227,6 +232,7 @@ public async void TestMachineProvisionerCLI() hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); var command = new CommandSettings(hc, new[] { "run" }); @@ -263,6 +269,7 @@ public async void TestMachineProvisionerCLICompat() hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); var command = new CommandSettings(hc, new string[] { }); @@ -301,6 +308,8 @@ public async void TestRunOnce() hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); + agent.Initialize(hc); var settings = new AgentSettings { @@ -397,6 +406,8 @@ public async void TestRunOnceOnlyTakeOneJobMessage() hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); + agent.Initialize(hc); var settings = new AgentSettings { @@ -501,6 +512,7 @@ public async void TestRunOnceHandleUpdateMessage() hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); hc.SetSingleton(_updater.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); agent.Initialize(hc); var settings = new AgentSettings @@ -592,6 +604,7 @@ public async void TestInfoArgumentsCLI(string arg, int expected = Constants.Agen hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); var command = new CommandSettings(hc, new[] { arg }); @@ -719,6 +732,8 @@ public async void TestMetadataUpdate() hc.SetSingleton(_proxy.Object); hc.SetSingleton(_cert.Object); hc.SetSingleton(_configStore.Object); + hc.SetSingleton(_listenerTelemetryPublisher.Object); + agent.Initialize(hc); var settings = new AgentSettings { From 2b3d40b8a3f63fc5581d25bcf5d619d91a7dad52 Mon Sep 17 00:00:00 2001 From: Ismayil Ismayilov Date: Sun, 1 Oct 2023 22:24:25 +0400 Subject: [PATCH 4/5] Add delay when ADO returns null message for any reason --- src/Agent.Listener/MessageListener.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Agent.Listener/MessageListener.cs b/src/Agent.Listener/MessageListener.cs index 565fef2a78..16ae691aa4 100644 --- a/src/Agent.Listener/MessageListener.cs +++ b/src/Agent.Listener/MessageListener.cs @@ -261,6 +261,9 @@ public async Task GetNextMessageAsync(CancellationToken token) Trace.Verbose($"No message retrieved from session '{_session.SessionId}'."); } + _getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15), _getNextMessageRetryInterval); + Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds); + await HostContext.Delay(_getNextMessageRetryInterval, token); continue; } From 8182e95b258ebfe645b3a431dc3d4d7afbe3c579 Mon Sep 17 00:00:00 2001 From: Ismayil Ismayilov Date: Sun, 1 Oct 2023 22:27:45 +0400 Subject: [PATCH 5/5] Cleanup PR --- src/Agent.Listener/Telemetry/TelemetryPublisher.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Agent.Listener/Telemetry/TelemetryPublisher.cs b/src/Agent.Listener/Telemetry/TelemetryPublisher.cs index 7201b934e6..bb1b2fc847 100644 --- a/src/Agent.Listener/Telemetry/TelemetryPublisher.cs +++ b/src/Agent.Listener/Telemetry/TelemetryPublisher.cs @@ -28,7 +28,6 @@ public sealed class TelemetryPublisher : AgentService, IAgenetListenerTelemetryP public List Aliases => null; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA2000:Dispose objects before losing scope", MessageId = "jobServer")] public async Task PublishEvent(IHostContext context, Command command) { try