Skip to content
Draft
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
14 changes: 8 additions & 6 deletions Scripts/common.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,21 @@ function Invoke-DotnetBuild([String]$dotnet, [String]$solution, [String]$config,
Write-Comment -prefix "..." -text "Building $solution"

$platform = "/p:Platform=`"Any CPU`""
$restore_command = "restore $solution"
$build_command = "build -c $config $solution --no-restore"
$restore_command = "restore $solution $platform"
$build_command = "build -c $config $solution --no-restore $platform"
if ($local -and $nuget) {
$nuget_config_file = "$PSScriptRoot/../NuGet.config"
$restore_command = "$restore_command --configfile $nuget_config_file /p:UseLocalNugetPackages=true $platform"
$build_command = "$build_command /p:UseLocalNugetPackages=true $platform"
$restore_command = "$restore_command --configfile $nuget_config_file /p:UseLocalNugetPackages=true"
$build_command = "$build_command /p:UseLocalNugetPackages=true"
} elseif ($local) {
$nuget_config_file = "$PSScriptRoot/../Samples/NuGet.config"
$restore_command = "$restore_command --configfile $nuget_config_file /p:UseLocalCoyote=true $platform"
$build_command = "$build_command /p:UseLocalCoyote=true $platform"
$restore_command = "$restore_command --configfile $nuget_config_file /p:UseLocalCoyote=true"
$build_command = "$build_command /p:UseLocalCoyote=true"
}

Write-Host $restore_command
Invoke-ToolCommand -tool $dotnet -cmd $restore_command -error_msg "Failed to restore $solution"
Write-Host $build_command
Invoke-ToolCommand -tool $dotnet -cmd $build_command -error_msg "Failed to build $solution"
}

Expand Down
10 changes: 1 addition & 9 deletions Source/Test/Rewriting/RewritingEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Coyote.Logging;
using Microsoft.Coyote.Runtime;
using Mono.Cecil;
Expand Down Expand Up @@ -311,11 +307,7 @@ private string CreateOutputDirectoryAndCopyFiles()
foreach (var type in new Type[]
{
typeof(CoyoteRuntime),
typeof(RewritingEngine),
typeof(TelemetryConfiguration),
typeof(EventTelemetry),
typeof(ITelemetry),
typeof(TelemetryClient)
typeof(RewritingEngine)
})
{
string assemblyPath = type.Assembly.Location;
Expand Down
28 changes: 10 additions & 18 deletions Source/Test/SystematicTesting/TestingEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ public void Run()

if (this.Configuration.IsTelemetryEnabled)
{
this.TrackTelemetry();
// 10 seconds should be enough, any more than that and too bad.
this.TrackTelemetry().Wait(10000, this.CancellationTokenSource.Token);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the typical wait time btw?

Also, I wonder if we should check (not here, probably inside the client) if there is even network connection and if not to just disable telemetry and return a Completed.Task? (This could optimize cases where say someone is for some reason having left telemetry on, and run tests in some no-network box.) Perhaps this is already implemented, I have not looked into the actual underlying logic yet :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick unscientific local test is getting round trip times in the 50-70 millisecond range. Google is very fast with all their services. When I was playing with firebase I was getting round trip times in the under 10 ms range sometimes down to 2 ms which is simply nuts, I was calculating it was basically the speed of light... let's say I was lucky and were hitting their Dalles Oregon datacenter, that's 267 miles, round trip 534, which in 3ms is /0.003 is 178,000 miles per second. Speed of light is 186,000 mi/s!

Note the Google Analytics GA4 SLA talks about somewhere upward of 96%, so it is possible a call also fail when GA4 is down also and it's hard to know how much retry logic happens automatically in an HTTP stack which is why I put a 10 second cap on it.

Good idea checking for network availability. Checking network availability is a bit tricky sometimes, because you can have a network interface that is talking to a local router, but no internet connection, and the only way to really check for an internet connection is to try ping something outside, well if we're doing that then we might as well make this the ping!

There is System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable() but guess what, it returns true when I put my machine into airplane mode :-)

}
}

Expand Down Expand Up @@ -591,25 +592,16 @@ private void GatherTestingStatistics(CoyoteRuntime runtime)
/// <summary>
/// Tracks anonymized telemetry data.
/// </summary>
private void TrackTelemetry()
private async Task TrackTelemetry()
{
bool isReplaying = this.Scheduler.IsReplaying;
TelemetryClient.TrackEvent(isReplaying ? "replay" : "test");
if (Debugger.IsAttached)
{
TelemetryClient.TrackEvent(isReplaying ? "replay-debug" : "test-debug");
}
else
{
TelemetryClient.TrackMetric(isReplaying ? "replay-time" : "test-time", this.Profiler.Results());
}

if (this.TestReport != null && this.TestReport.NumOfFoundBugs > 0)
{
TelemetryClient.TrackMetric(isReplaying ? "replay-bugs" : "test-bugs", this.TestReport.NumOfFoundBugs);
}

TelemetryClient.Flush();
await TelemetryClient.TrackEvent(
action: Debugger.IsAttached ?
(isReplaying ? "debug-replay" : "debug-test") :
(isReplaying ? "replay" : "test"),
result: (this.TestReport.NumOfFoundBugs > 0) ? "failed" : "passed",
bugsFound: this.TestReport?.NumOfFoundBugs,
testTime: this.Profiler?.Results());
}

/// <summary>
Expand Down
133 changes: 133 additions & 0 deletions Source/Test/Telemetry/HttpProtocol.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.Coyote.Telemetry
{
internal static class HttpProtocol
{
private const string BaseUrl = "https://www.google-analytics.com/mp/collect";
private const string DebugBaseUrl = "https://www.google-analytics.com/debug/mp/collect";

public static Task PostMeasurements(Analytics a)
{
return Post(BaseUrl, a);
}

public static async Task<ValidationResponse> ValidateMeasurements(Analytics a)
{
var response = await Post(DebugBaseUrl, a);
if (response.Content != null)
{
using (var stream = await response.Content.ReadAsStreamAsync())
{
var responseSerializer = new DataContractJsonSerializer(typeof(ValidationResponse));
return (ValidationResponse)responseSerializer.ReadObject(stream);
}
}

throw new Exception("No validation response");
}

private static async Task<HttpResponseMessage> Post(string baseUri, Analytics a)
{
const string guide = "\r\nSee https://developers.google.com/analytics/devguides/collection/protocol/ga4";

if (a.Events.Count > 25)
{
throw new Exception("A maximum of 25 events can be specified per request." + guide);
}

if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
{
throw new Exception("A network intercace is not available");
}

string query = a.ToQueryString();
string url = baseUri + "?" + query;

if (string.IsNullOrEmpty(a.UserId))
{
a.UserId = a.ClientId;
}

HttpClient client = new HttpClient();
AddUserProperties(client, a);

DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings();
settings.EmitTypeInformation = EmitTypeInformation.Never;
settings.UseSimpleDictionaryFormat = true;
settings.KnownTypes = GetKnownTypes(a);
var serializer = new DataContractJsonSerializer(typeof(Analytics), settings);
var ms = new MemoryStream();
serializer.WriteObject(ms, a);
var bytes = ms.GetBuffer();
if (bytes.Length > 130000)
{
throw new Exception("The total size of analytics payloads cannot be greater than 130kb bytes" + guide);
}

var json = Encoding.UTF8.GetString(bytes);
json = new StreamReader("d:\\temp\\json2.json").ReadToEnd();
var jsonContent = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), jsonContent);
response.EnsureSuccessStatusCode();
return response;
}

private static void AddUserProperties(HttpClient client, Analytics a)
{
string platform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Windows" :
(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "Linux" : "OSX");
var arch = RuntimeInformation.OSArchitecture.ToString();
client.DefaultRequestHeaders.Add("User-Agent", string.Format("Mozilla/5.0 ({0}; {1})", platform, arch));
client.DefaultRequestHeaders.Add("Accept-Language", CultureInfo.CurrentCulture.Name);
a.UserProperties = new UserProperties()
{
FrameworkVersion = new UserPropertyValue(RuntimeInformation.FrameworkDescription),
Platform = new UserPropertyValue(platform),
PlatformVersion = new UserPropertyValue(RuntimeInformation.OSDescription),
Language = new UserPropertyValue(CultureInfo.CurrentCulture.Name)
};
}

private static Type[] GetKnownTypes(Analytics a)
{
HashSet<Type> types = new HashSet<Type>();
foreach (var e in a.Events)
{
types.Add(e.GetType());
}

return new List<Type>(types).ToArray();
}
}

[DataContract]
internal class ValidationResponse
{
[DataMember(Name = "validationMessages")]
public ValidationMessage[] ValidationMessages;
}

[DataContract]
internal class ValidationMessage
{
[DataMember(Name = "description")]
public string Description;
[DataMember(Name = "fieldPath")]
public string InvalidFieldPath;
[DataMember(Name = "validationCode")]
public string ValidationCode;
}
}
Loading