Skip to content

Commit fcdf517

Browse files
committed
Add email notifications for performance tests
1 parent 9630a52 commit fcdf517

File tree

11 files changed

+318
-50
lines changed

11 files changed

+318
-50
lines changed

tools/WebJobs.Script.Performance/WebJobs.Script.Performance.App/Artifacts/PS/run.ps1

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,11 @@ Invoke-WebRequest -Uri $toolNupkgUrl -OutFile $nupkgPath
3838

3939
Copy-Item -Path "C:\Tools\ps\local.settings.json" -Destination $binPath -Force
4040

41-
Push-Location "$binPath\Artifacts\PS"
42-
Invoke-Expression "$binPath\Artifacts\PS\build-jar.ps1"
43-
Pop-Location
4441

4542
Push-Location $binPath
4643
Write-Output "Running tool: dotnet $binPath\WebJobs.Script.Performance.App.dll $toolArgs"
47-
Invoke-Expression "dotnet $binPath\WebJobs.Script.Performance.App.dll $toolArgs"
44+
$output = Invoke-Expression "dotnet $binPath\WebJobs.Script.Performance.App.dll $toolArgs"
45+
Write-Output $output
4846
Pop-Location
4947

5048
Remove-Item -Recurse -Force $tempFolder -ErrorAction SilentlyContinue

tools/WebJobs.Script.Performance/WebJobs.Script.Performance.App/Artifacts/PS/test-throughput.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ $tempJmxPath = "$tempJmxPath\test.jmx"
6060
Write-Host "Downloading '$jmx' to '$tempJmxPath'"
6161
Invoke-WebRequest -Uri $jmx -OutFile $tempJmxPath
6262

63+
Write-Host "$jmeter -jar C:\Tools\apache-jmeter-5.0\bin\ApacheJMeter.jar -n -t $tempJmxPath -l '$outputCSVPath\logs.csv'"
6364
& $jmeter -jar C:\Tools\apache-jmeter-5.0\bin\ApacheJMeter.jar -n -t $tempJmxPath -l "$outputCSVPath\logs.csv"
65+
Write-Host "$jmeter -jar C:\Tools\apache-jmeter-5.0\bin\ApacheJMeter.jar -g '$outputCSVPath\logs.csv' -o $outputHTMLPath"
6466
& $jmeter -jar C:\Tools\apache-jmeter-5.0\bin\ApacheJMeter.jar -g "$outputCSVPath\logs.csv" -o $outputHTMLPath
6567

6668
$matches = Select-String -Pattern "#statisticsTable" -Path "$outputHTMLPath\content\js\dashboard.js"

tools/WebJobs.Script.Performance/WebJobs.Script.Performance.Dashboard/Dashboard.cs

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,29 @@
1-
1+
using System;
22
using System.IO;
3+
using System.Threading.Tasks;
34
using Microsoft.AspNetCore.Mvc;
45
using Microsoft.Azure.WebJobs;
56
using Microsoft.Azure.WebJobs.Extensions.Http;
67
using Microsoft.AspNetCore.Http;
7-
using Microsoft.Azure.WebJobs.Host;
8-
using Newtonsoft.Json;
9-
using Microsoft.WindowsAzure.Storage;
10-
using System;
11-
using Microsoft.WindowsAzure.Storage.Blob;
12-
using System.Linq;
13-
using System.Threading.Tasks;
8+
using Microsoft.Extensions.Logging;
149

1510
namespace WebJobs.Script.Tests.Perf.Dashboard
1611
{
1712
public static class Dashboard
1813
{
1914
[FunctionName("Dashboard")]
20-
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ExecutionContext context, TraceWriter log)
15+
public static async Task<IActionResult> Run(
16+
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ExecutionContext context,
17+
ILogger log)
2118
{
2219
try
2320
{
24-
string month = req.Query["month"];
2521
string year = req.Query["year"];
22+
string month = req.Query["month"];
23+
string day = req.Query["day"];
24+
bool.TryParse(req.Query["onlyWarnings"], out bool onlyWarnings);
2625

27-
string blobConectionString = Environment.GetEnvironmentVariable("PerfStorage", EnvironmentVariableTarget.Process);
28-
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(blobConectionString);
29-
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
30-
CloudBlobContainer container = blobClient.GetContainerReference("dashboard");
31-
32-
BlobResultSegment result = await container.ListBlobsSegmentedAsync($"{year ?? "2018"}-{month ?? "10"}", null);
33-
string tableContent = "";
34-
foreach (var item in result.Results.OrderByDescending(x => x.StorageUri.ToString()))
35-
{
36-
string blobUri = item.Uri.ToString().TrimEnd('/');
37-
string time = blobUri.Split('/').Last().Replace("-", "/").Replace("_", ":");
38-
try
39-
{
40-
CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobUri.Split('/').Last() + "/summary.txt");
41-
string summary = await blockBlob.DownloadTextAsync();
42-
string[] summaryNumbers = summary.Split(',');
43-
int totalRequests = int.Parse(summaryNumbers[2]);
44-
double avgResponseTime = Math.Round(double.Parse(summaryNumbers[5]), 2);
45-
int rps = totalRequests / 120;
46-
tableContent += $"<tr><td>{time}</td><td><a href='{blobUri}/index.html'>{summaryNumbers[0]}</a></td><td>{summaryNumbers[1]}</td><td>{rps}</td><td>{totalRequests}</td><td>{avgResponseTime}</td></tr>";
47-
}
48-
catch
49-
{
50-
tableContent += $"<tr><td>{time}</td><td>Failed</td><td></td><td></td><td></td><td></td></tr>";
51-
}
52-
}
26+
string tableContent = await ReportProcessor.GetHtmlReport(year, month, day, onlyWarnings);
5327

5428
string content = File.ReadAllText($"{context.FunctionAppDirectory}\\template.html");
5529
content = content.Replace("[replace]", tableContent);

tools/WebJobs.Script.Performance/WebJobs.Script.Performance.Dashboard/PerformanceManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static async Task Execute(string testIds, ILogger log)
4848
client.SubscriptionId = subscriptionId;
4949
string command = string.IsNullOrEmpty(testIds) ? string.Empty : $"-t {testIds}";
5050
command += string.IsNullOrEmpty(extensionUrl) ? string.Empty : $" -r {extensionUrl}";
51-
var commandResult = VirtualMachinesOperationsExtensions.RunCommand(client.VirtualMachines, siteResourceGroup, vm,
51+
await VirtualMachinesOperationsExtensions.BeginRunCommandAsync(client.VirtualMachines, siteResourceGroup, vm,
5252
new RunCommandInput("RunPowerShellScript",
5353
new List<string>() { $"& 'C:\\Tools\\ps\\run.ps1' '{appUrl}' '{command}'" }));
5454
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using Microsoft.WindowsAzure.Storage;
2+
using Microsoft.WindowsAzure.Storage.Blob;
3+
using Newtonsoft.Json;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net;
8+
using System.Text;
9+
using System.Text.RegularExpressions;
10+
using System.Threading.Tasks;
11+
12+
namespace WebJobs.Script.Tests.Perf.Dashboard
13+
{
14+
public class ReportProcessor
15+
{
16+
private static Dictionary<string, double> RPSThreshold = new Dictionary<string, double>()
17+
{
18+
{ "C# Ping (VS)", 130},
19+
{ "Java Ping", 40},
20+
{ "JS Ping", 80},
21+
{ "C# Ping", 130},
22+
{ "PS Ping", 20}
23+
};
24+
25+
public static async Task<string> GetLastDaysHtmlReport(int days, bool onlyWarnings)
26+
{
27+
string tableContent = string.Empty;
28+
for (int i = 0; i < days; i++)
29+
{
30+
DateTime startDate = DateTime.UtcNow.AddDays(-i);
31+
tableContent += await ReportProcessor.GetHtmlReport(startDate.Year.ToString("d2"), startDate.Month.ToString("d2"), startDate.Day.ToString("d2"), onlyWarnings);
32+
}
33+
return tableContent;
34+
}
35+
36+
public static async Task<string> GetHtmlReport(string year, string month, string day, bool onlyWarnings)
37+
{
38+
string content = string.Empty;
39+
year = year ?? DateTime.Now.Year.ToString();
40+
month = month ?? DateTime.Now.Month.ToString("d2");
41+
string blobPrefix = $"{year}-{month}";
42+
43+
if (!string.IsNullOrEmpty(day))
44+
{
45+
blobPrefix = $"{blobPrefix}-{day}";
46+
}
47+
48+
string blobConectionString = Environment.GetEnvironmentVariable("PerfStorage", EnvironmentVariableTarget.Process);
49+
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(blobConectionString);
50+
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
51+
CloudBlobContainer container = blobClient.GetContainerReference("dashboard");
52+
53+
BlobResultSegment result = await container.ListBlobsSegmentedAsync(blobPrefix, null);
54+
foreach (var item in result.Results.OrderByDescending(x => x.StorageUri.ToString()))
55+
{
56+
string blobUri = item.Uri.ToString().TrimEnd('/');
57+
string time = blobUri.Split('/').Last().Replace("-", "/").Replace("_", ":");
58+
try
59+
{
60+
CloudBlockBlob reportBlob = container.GetBlockBlobReference(blobUri.Split('/').Last() + "/summary.txt");
61+
string summary = await reportBlob.DownloadTextAsync();
62+
string[] summaryNumbers = summary.Split(',');
63+
int totalRequests = int.Parse(summaryNumbers[2]);
64+
double avgResponseTime = Math.Round(double.Parse(summaryNumbers[5]), 2);
65+
string testName = summaryNumbers[0];
66+
string runtime = summaryNumbers[1];
67+
RPSThreshold.TryGetValue(testName, out double rpsThreshold);
68+
string rpsThresholdString = rpsThreshold != 0 ? $" (Threshold:{RPSThreshold[testName].ToString()})" : string.Empty;
69+
double rps = totalRequests / 120;
70+
string rpsString = $"{Math.Round(rps, 2).ToString()}{rpsThresholdString}";
71+
72+
string style = string.Empty;
73+
if ((rpsThreshold != 0) && (rps < rpsThreshold))
74+
{
75+
style = " style='background:yellow'";
76+
}
77+
78+
string row = $"<tr{style}><td>{time}</td><td><a href='{Uri.EscapeUriString(blobUri)}/index.html'>{testName}</a></td><td>{runtime}</td><td>{rpsString}</td><td>{totalRequests}</td><td>{avgResponseTime}</td></tr>";
79+
if (onlyWarnings)
80+
{
81+
if(!string.IsNullOrEmpty(style))
82+
{
83+
content += row;
84+
}
85+
}
86+
else
87+
{
88+
content += row;
89+
}
90+
}
91+
catch (Exception)
92+
{
93+
// The test run is failed
94+
content += $"<tr style='background:yellow'><td>{time}</td><td>Failed</td><td></td><td></td><td></td><td></td></tr>";
95+
}
96+
}
97+
98+
return content;
99+
}
100+
}
101+
}

tools/WebJobs.Script.Performance/WebJobs.Script.Performance.Dashboard/RunPerfHttp.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,30 @@ public static class RunPerfHttp
1717
[FunctionName("RunPerfHttp")]
1818
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log)
1919
{
20-
log.LogInformation($"Performance tests were started by http trigger at: {DateTime.Now}");
20+
try
21+
{
22+
log.LogInformation($"Performance tests were started by http trigger at: {DateTime.Now}");
2123

22-
string testIds = string.Empty;
23-
req.GetQueryParameterDictionary().TryGetValue("testIds", out testIds);
24+
string testIds = string.Empty;
25+
req.GetQueryParameterDictionary().TryGetValue("testIds", out testIds);
2426

25-
await PerformanceManager.Execute(testIds, log);
27+
await PerformanceManager.Execute(testIds, log);
2628

27-
return new ContentResult()
29+
return new ContentResult()
30+
{
31+
Content = string.IsNullOrEmpty(testIds) ? "All tests started" : $"Tests started: {testIds}",
32+
ContentType = "text/html"
33+
};
34+
}
35+
catch (Exception ex)
2836
{
29-
Content = "Done",
30-
ContentType = "text/html",
31-
};
37+
log.LogError(ex.ToString());
38+
return new ContentResult()
39+
{
40+
Content = "Exception:" + ex,
41+
ContentType = "text/html"
42+
};
43+
}
3244
}
3345
}
3446
}

tools/WebJobs.Script.Performance/WebJobs.Script.Performance.Dashboard/RunPerfNightlyTimer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace WebJobs.Script.Tests.Perf.Dashboard
99
public static class RunPerfNightlyTimer
1010
{
1111
[FunctionName("RunPerfNightlyTimer")]
12-
public static async Task Run([TimerTrigger("0 0 14 * * *")]TimerInfo myTimer, ILogger log)
12+
public static async Task Run([TimerTrigger("0 0 14 * * *", RunOnStartup = false)]TimerInfo myTimer, ILogger log)
1313
{
1414
log.LogInformation($"Performance tests were started by timer trigger at: {DateTime.Now}");
1515

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Azure.WebJobs;
6+
using Microsoft.Azure.WebJobs.Extensions.Http;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.Logging;
9+
using Newtonsoft.Json;
10+
using SendGrid.Helpers.Mail;
11+
12+
namespace WebJobs.Script.Tests.Perf.Dashboard
13+
{
14+
public static class SendReportHttp
15+
{
16+
[FunctionName("SendReportHttp")]
17+
public static async Task<IActionResult> Run(
18+
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
19+
ILogger log, ExecutionContext context, [SendGrid()] IAsyncCollector<SendGridMessage> messageCollector)
20+
{
21+
log.LogInformation("C# HTTP trigger function processed a request.");
22+
23+
string warnings = await ReportProcessor.GetLastDaysHtmlReport(7, true);
24+
string all = await ReportProcessor.GetLastDaysHtmlReport(7, false);
25+
26+
string content = await SendEmail(warnings, all, context, messageCollector);
27+
28+
return new ContentResult()
29+
{
30+
Content = content,
31+
ContentType = "text/html",
32+
};
33+
}
34+
35+
public static async Task<string> SendEmail(string warnings, string all, ExecutionContext context, IAsyncCollector<SendGridMessage> messageCollector)
36+
{
37+
string content = File.ReadAllText($"{context.FunctionAppDirectory}\\TemplateEmail.html");
38+
content = content.Replace("[replace_warning]", warnings);
39+
content = content.Replace("[replace_all]", all);
40+
41+
var message = new SendGridMessage();
42+
string[] toArr = Environment.GetEnvironmentVariable("SendEmailTo").Split(';');
43+
foreach (string email in toArr)
44+
{
45+
if (!string.IsNullOrEmpty(email))
46+
{
47+
message.AddTo(email);
48+
}
49+
}
50+
message.AddContent("text/html", content);
51+
message.SetFrom(Environment.GetEnvironmentVariable("SendEmailFrom"));
52+
message.SetSubject("Daily Functions rutime performance report");
53+
54+
await messageCollector.AddAsync(message);
55+
56+
return content;
57+
}
58+
}
59+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Text.RegularExpressions;
5+
using System.Threading.Tasks;
6+
using Microsoft.Azure.WebJobs;
7+
using Microsoft.Azure.WebJobs.Host;
8+
using Microsoft.Extensions.Logging;
9+
using Microsoft.WindowsAzure.Storage;
10+
using Microsoft.WindowsAzure.Storage.Blob;
11+
using Newtonsoft.Json;
12+
using SendGrid.Helpers.Mail;
13+
14+
namespace WebJobs.Script.Tests.Perf.Dashboard
15+
{
16+
public static class SendReportTimer
17+
{
18+
[FunctionName("SendReportTimer")]
19+
public static async Task Run(
20+
[TimerTrigger("0 0 17 * * *", RunOnStartup=false)]TimerInfo myTimer, ILogger log, ExecutionContext context,
21+
[SendGrid()] IAsyncCollector<SendGridMessage> messageCollector)
22+
{
23+
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
24+
25+
string warnings = await ReportProcessor.GetLastDaysHtmlReport(7, true);
26+
string all = await ReportProcessor.GetLastDaysHtmlReport(7, false);
27+
28+
await SendReportHttp.SendEmail(warnings, all, context, messageCollector);
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)