-
Notifications
You must be signed in to change notification settings - Fork 83
switch to google analytics #408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -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> | ||
|
|
||
| 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; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.