xamarin-android/build-tools/plots-to-appinsights/Program.cs

344 строки
12 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace Xamarin.Android.Tools.Plots
{
internal static class Constants
{
public const string AzureTimestampFormatUtc = "yyyy-MM-ddTHH:mm:ss.fffffffZ";
public const string TelemetryEventName = "xamarin-android.plot";
public const string TelemetryEventName_Warning = "xamarin-android.plot.warning";
public const string TelemetryEventName_Error = "xamarin-android.plot.error";
}
class Program
{
static int Main (string [] args)
{
var retCode = 0;
var errMessage = string.Empty;
var settings = new Settings ();
try {
var currentUtcTime = DateTime.UtcNow;
var result = settings.ProcessArgs (args);
if (settings.BuildDateUtc.Equals (default)) {
settings.BuildDateUtc = currentUtcTime;
}
settings.Display (Console.Out);
if (!string.IsNullOrEmpty (result.Message)) {
Console.WriteLine (result.Message);
Console.WriteLine ();
} else if (!File.Exists (settings.CsvPathAndFilename)) {
var message = $"CSV file '{settings.CsvPathAndFilename}' does not exist";
Console.WriteLine ($"ERROR: {message}");
Console.WriteLine ();
result.Status = Status.CsvFileDoesNotExist;
var appInsightsClientError = new AppInsights (settings.AppInsightsTelemetryKey);
var eventNameError = GetEventName (settings.Environment, Constants.TelemetryEventName_Error);
SendTelemetryError (appInsightsClientError, eventNameError, message, settings, Console.Out);
appInsightsClientError.Flush ();
}
if (result.Status != Status.OK) {
Console.WriteLine ("ProcessPlotCSVFile [OPTIONS] PlotFilename.csv");
Console.WriteLine ();
Console.WriteLine ("Options:");
settings.Options.WriteOptionDescriptions (Console.Out);
Console.WriteLine ();
return 1;
}
var plots = ProcessCSVFile (settings.CsvPathAndFilename, out var warningMessages, Console.Out);
var appInsightsClient = new AppInsights (settings.AppInsightsTelemetryKey);
var eventName = GetEventName (settings.Environment, Constants.TelemetryEventName);
Console.WriteLine ($"Sending telemetry. Event name: {eventName} / number of plots: {plots.Count ()}");
SendTelemtry (appInsightsClient, eventName, plots, settings, Console.Out);
appInsightsClient.Flush ();
var eventNameWarning = GetEventName (settings.Environment, Constants.TelemetryEventName_Warning);
SendTelemetryWarnings (appInsightsClient, eventNameWarning, warningMessages, settings, Console.Out);
appInsightsClient.Flush ();
} catch (Exception e) {
errMessage = e.Message;
Console.WriteLine ($"EXCEPTION: {errMessage}");
Console.WriteLine (e.StackTrace);
retCode = 2;
}
try {
if (!string.IsNullOrEmpty (errMessage)) {
var appInsightsClient = new AppInsights (settings.AppInsightsTelemetryKey);
var eventNameError = GetEventName (settings.Environment, Constants.TelemetryEventName_Error);
SendTelemetryError (appInsightsClient, eventNameError, errMessage, settings, Console.Out);
appInsightsClient.Flush ();
}
} catch (Exception e) {
Console.WriteLine ($"EXCEPTION: Sending error telemetry: {e.Message}");
Console.WriteLine (e.StackTrace);
retCode = 3;
}
return retCode;
}
static Dictionary<string, decimal> ProcessCSVFile (string csvPathAndFilename, out List<string> warningMessages, TextWriter tw)
{
warningMessages = new List<string> ();
if (string.IsNullOrEmpty (csvPathAndFilename)) {
throw new ArgumentNullException (nameof (csvPathAndFilename));
}
if (!File.Exists (csvPathAndFilename)) {
throw new FileNotFoundException (csvPathAndFilename);
}
if (tw == null) {
throw new ArgumentNullException (nameof (tw));
}
var plots = new Dictionary<string, decimal> (StringComparer.OrdinalIgnoreCase);
var lines = File.ReadAllLines (csvPathAndFilename);
lines = lines.Where (line => !string.IsNullOrWhiteSpace (line)).ToArray (); // Remove any empty lines
var duplicateLabelsHavingDifferentValues = new List<string> ();
if (lines.Length == 2) {
var labels = lines [0].Split (new [] { ',' });
var values = lines [1].Split (new [] { ',' });
if (labels.Length == values.Length) {
var index = 0;
foreach (var label in labels) {
if (decimal.TryParse (values [index], out var value)) {
if (plots.TryGetValue (label, out var existingValue)) {
var csvFilename = Path.GetFileName (csvPathAndFilename);
var warningMessage = $"The plot file contains a duplicate plot for '{label}'. value1: {existingValue} / value2: {value}. File: {csvFilename}";
warningMessages.Add (warningMessage);
tw.WriteLine ($"WARNING: {nameof (ProcessCSVFile)}: {warningMessage}");
if (value != existingValue) {
tw.WriteLine ($"ERROR: {nameof (ProcessCSVFile)}: The duplicate plots have different values. value1: {existingValue} / value2: {value}. File: {csvFilename}");
duplicateLabelsHavingDifferentValues.Add (label); // This will result in an exception being thrown
}
} else {
plots [label] = value; // The CSV file might contain redundant information
}
} else {
throw new Exception ($"{nameof (ProcessCSVFile)}: Failed to parse the value '{values [index]}' for the {label} label in 0-based column {index}. Expecting a numeric value");
}
index++;
}
if (duplicateLabelsHavingDifferentValues.Any ()) {
var distinctLabels = duplicateLabelsHavingDifferentValues.Distinct ();
throw new Exception ($"{nameof (ProcessCSVFile)}: Duplicate plot labels having different values: {string.Join (",", distinctLabels)}");
}
} else {
throw new Exception ($"{nameof (ProcessCSVFile)}: Mismatch between plot labels (line 1) and values (line 2). Count of labels and values should be identical. Number of labels: {labels.Length} / number of values: {values.Length}");
}
} else {
throw new Exception ($"{nameof (ProcessCSVFile)}: Plot CSV file is expected to have two lines. First line should include the comma-delimited labels where the second line should include the comma-limited values. Instead the CSV file has {lines.Length} line(s)");
}
return plots;
}
static string GetEventName (string environment, string eventName)
{
if (string.IsNullOrEmpty (eventName)) {
throw new ArgumentNullException (nameof (eventName));
}
if (string.IsNullOrEmpty (environment)) {
environment = "Dev";
}
if (!environment.Equals ("Production", StringComparison.OrdinalIgnoreCase)) {
eventName = $"{eventName}.{environment.ToLowerInvariant ()}";
}
return eventName;
}
static Dictionary<string, string> GetBaseTelemetryProperties (Settings settings)
{
if (settings == null) {
throw new ArgumentNullException (nameof (settings));
}
var schemaVersion = "1.0";
var sessionTimestamp = DateTime.UtcNow.ToString (Constants.AzureTimestampFormatUtc);
var properties = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase) {
{ "Version", schemaVersion },
{ "Environment", settings.Environment },
{ "SessionTimestamp", sessionTimestamp },
{ "BuildSystem", settings.BuildSystem },
{ "BuildPipeline", settings.BuildPipelineName },
{ "BuildType", settings.BuildType },
{ "BuildId", settings.BuildId },
{ "BuildNumber", settings.BuildNumber },
{ "BuildUrl", settings.BuildUrl },
{ "BuildDate", settings.BuildDateUtc.ToString(Constants.AzureTimestampFormatUtc) },
{ "Filename", Path.GetFileName(settings.CsvPathAndFilename) },
{ "Title", settings.PlotTitle },
{ "Group", settings.PlotGroup }
};
return properties;
}
static void SendTelemtry (AppInsights appInsightsClient, string eventName, Dictionary<string, decimal> plots, Settings settings, TextWriter tw)
{
if (appInsightsClient == null) {
throw new ArgumentNullException (nameof (appInsightsClient));
}
if (string.IsNullOrEmpty (eventName)) {
throw new ArgumentNullException (nameof (eventName));
}
if (plots == null || !plots.Any()) {
throw new ArgumentNullException (nameof (plots));
}
if (settings == null) {
throw new ArgumentNullException (nameof (settings));
}
if (tw == null) {
throw new ArgumentNullException (nameof (tw));
}
var ctPlot = 0;
foreach (var plot in plots) {
var properties = GetBaseTelemetryProperties (settings);
var plotProperties = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase) {
{ "SeriesLabel", plot.Key }
};
// Add plot information to the properties dictionary
plotProperties.ToList ().ForEach (x => properties [x.Key] = x.Value);
var metrics = new Dictionary<string, double> (StringComparer.OrdinalIgnoreCase) {
{ "Value", (double) plot.Value }
};
appInsightsClient.SendTelemetry (eventName, properties, metrics);
ctPlot++;
tw.WriteLine ($"Telemetry sent: Plot {ctPlot}");
tw.WriteLine ($" Event name: {eventName}");
tw.WriteLine (" Properties:");
foreach (var property in properties.OrderBy (x => x.Key)) {
tw.WriteLine ($" {property.Key}: {property.Value}");
}
tw.WriteLine (" Metrics:");
foreach (var metric in metrics.OrderBy (x => x.Key)) {
tw.WriteLine ($" {metric.Key}: {metric.Value}");
}
}
}
static void SendTelemetryWarnings (AppInsights appInsightsClient, string eventName, List<string> warnings, Settings settings, TextWriter tw)
{
if (appInsightsClient == null) {
throw new ArgumentNullException (nameof (appInsightsClient));
}
if (string.IsNullOrEmpty (eventName)) {
throw new ArgumentNullException (nameof (eventName));
}
if (warnings == null) { // Note: It's okay if warnings is empty
throw new ArgumentNullException (nameof (warnings));
}
if (settings == null) {
throw new ArgumentNullException (nameof (settings));
}
if (tw == null) {
throw new ArgumentNullException (nameof (tw));
}
foreach (var warning in warnings) {
var properties = GetBaseTelemetryProperties (settings);
var warningProperties = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase) {
{ "Message", warning }
};
// Add warning to the properties dictionary
warningProperties.ToList ().ForEach (x => properties [x.Key] = x.Value);
var metrics = new Dictionary<string, double> (StringComparer.OrdinalIgnoreCase);
appInsightsClient.SendTelemetry (eventName, properties, metrics);
tw.WriteLine ($"Telemetry sent: Warning: {warning}");
tw.WriteLine ($" Event name: {eventName}");
tw.WriteLine (" Properties:");
foreach (var property in properties.OrderBy (x => x.Key)) {
tw.WriteLine ($" {property.Key}: {property.Value}");
}
}
}
static void SendTelemetryError (AppInsights appInsightsClient, string eventName, string errMessage, Settings settings, TextWriter tw)
{
if (appInsightsClient == null) {
throw new ArgumentNullException (nameof (appInsightsClient));
}
if (string.IsNullOrEmpty(eventName)) {
throw new ArgumentNullException (nameof (eventName));
}
if (string.IsNullOrEmpty(errMessage)) {
throw new ArgumentNullException (nameof (errMessage));
}
if (settings == null) {
throw new ArgumentNullException (nameof (settings));
}
if (tw == null) {
throw new ArgumentNullException (nameof(tw));
}
var properties = GetBaseTelemetryProperties (settings);
var errorProperties = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase) {
{ "Message", errMessage }
};
// Add error to the properties dictionary
errorProperties.ToList ().ForEach (x => properties [x.Key] = x.Value);
var metrics = new Dictionary<string, double> (StringComparer.OrdinalIgnoreCase);
appInsightsClient.SendTelemetry (eventName, properties, metrics);
tw.WriteLine ($"Telemetry sent: Error: {errMessage}");
tw.WriteLine ($" Event name: {eventName}");
tw.WriteLine (" Properties:");
foreach (var property in properties.OrderBy (x => x.Key)) {
tw.WriteLine ($" {property.Key}: {property.Value}");
}
}
}
}