Skip to content
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

Update loadgen functions #1441

Merged
merged 10 commits into from
Jun 6, 2024
Merged
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
2 changes: 1 addition & 1 deletion .ado/pipelines/azure-deploy-loadgenerator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,4 @@ stages:

echo "*** Deploying to Master Function App $masterFunctionName in resource group $rgName"
cd "$(workingDirectory)/AzureFunctions/GlobalOrchestrator"
func azure functionapp publish $masterFunctionName --csharp
func azure functionapp publish $masterFunctionName --dotnet-isolated
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.13.4" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.2.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.4.0" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.22.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.1.4" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.3.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.3.1" />
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
</ItemGroup>

<ItemGroup>
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext"/>
</ItemGroup>

<ItemGroup>
<None Update="daily_load_profile.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
using GlobalOrchestrator.Model;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;

namespace GlobalOrchestrator
{
public class LoadSetter
{
private static HttpClient _httpClient = new HttpClient();
private static readonly HttpClient _httpClient = new HttpClient();

private const string regionalLoadgenFunctionBaseUrl = @"https://{0}.azurewebsites.net/api/StartRegionalUserflows?numberofusers={1}";
private const string RegionalLoadgenFunctionBaseUrl =
@"https://{0}.azurewebsites.net/api/StartRegionalUserflows?numberofusers={1}";

public static async Task<List<string>> LoadSetterInternalAsync(ExecutionContext context, ILogger log)
public static async Task<List<string>> LoadSetterInternalAsync(ILogger log)
{
try
{
string fileName = "daily_load_profile.json";

string jsonLocation = Path.Combine(context.FunctionAppDirectory, fileName);
var rootDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string jsonLocation = Path.Combine(rootDirectory!, fileName);
string jsonString = await File.ReadAllTextAsync(jsonLocation);
var loadProfile = JsonSerializer.Deserialize<LoadProfile>(jsonString);

var invokedFunctions = new List<string>();

foreach (var geo in loadProfile.geos)

Check warning on line 28 in src/testing/userload-generator/AzureFunctions/GlobalOrchestrator/LoadSetter.cs

View workflow job for this annotation

GitHub Actions / Build global orchestrator

Dereference of a possibly null reference.

Check warning on line 28 in src/testing/userload-generator/AzureFunctions/GlobalOrchestrator/LoadSetter.cs

View workflow job for this annotation

GitHub Actions / Build global orchestrator

Dereference of a possibly null reference.
{
if (!geo.enabled)
{
Expand All @@ -42,35 +37,43 @@
DateTime geoDateNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, geo.TimeZone);
var geoNowTime = TimeOnly.FromDateTime(geoDateNow);

log.LogInformation("Start processing load profile for geo {geo}. Time of day for this geo: {geoNowTime}", geo.name, geoNowTime);
log.LogInformation(
"Start processing load profile for geo {geo}. Time of day for this geo: {geoNowTime}", geo.name,
geoNowTime);

// Get the currently valid load profile for this geo (if there is any)
// IsBetween() supports ranges that span midnight, so End can be lower than Start (e.g. 23:00-01:00)
var currentTimeframe = geo.timeframes
.Where(t => geoNowTime.IsBetween(t.Start, t.End))
.FirstOrDefault();
.FirstOrDefault(t => geoNowTime.IsBetween(t.Start, t.End));

int currentUserLoad;

if (currentTimeframe != null)
{
log.LogInformation("Found current load profile {loadprofile} for current geo-time {geoTime} for geo {geo}", currentTimeframe, geoNowTime, geo.name);
log.LogInformation(
"Found current load profile {loadprofile} for current geo-time {geoTime} for geo {geo}",
currentTimeframe, geoNowTime, geo.name);

// Check if we are still in the transition period
var transitionTimeEnd = currentTimeframe.Start.AddMinutes(currentTimeframe.transitionTimeMinutes);
var transitionTimeEnd =
currentTimeframe.Start.AddMinutes(currentTimeframe.transitionTimeMinutes);

if (geoNowTime.IsBetween(currentTimeframe.Start, transitionTimeEnd))
{
var minutesSinceTimeframeStart = (int)(geoNowTime - currentTimeframe.Start).TotalMinutes;

// Check if we need to ramp up/down from a previous timeframe which is directly adjecent to the current one
var previousAdjecentTimeframe = geo.timeframes.SingleOrDefault(t => t.End == currentTimeframe.Start);
var previousAdjecentTimeframe =
geo.timeframes.SingleOrDefault(t => t.End == currentTimeframe.Start);
if (previousAdjecentTimeframe != null)
{
if (previousAdjecentTimeframe.numberOfUsers != currentTimeframe.numberOfUsers)
{
// Ramp up/down
currentUserLoad = (int)Math.Round(previousAdjecentTimeframe.numberOfUsers + currentTimeframe.RampUpPerMinute(previousAdjecentTimeframe.numberOfUsers) * minutesSinceTimeframeStart);
currentUserLoad = (int)Math.Round(previousAdjecentTimeframe.numberOfUsers +
currentTimeframe.RampUpPerMinute(
previousAdjecentTimeframe.numberOfUsers) *
minutesSinceTimeframeStart);
}
else
{
Expand All @@ -81,9 +84,9 @@
else
{
// ramp up from 0
currentUserLoad = (int)Math.Round(currentTimeframe.RampUpPerMinute() * minutesSinceTimeframeStart);
currentUserLoad =
(int)Math.Round(currentTimeframe.RampUpPerMinute() * minutesSinceTimeframeStart);
}

}
else
{
Expand All @@ -95,10 +98,13 @@
{
// Turn off any load for right now
currentUserLoad = 0;
log.LogInformation("No loadProfile found for current geo-time {geoTime} for geo {geo}. Setting user load to zero", geoNowTime, geo.name);
log.LogInformation(
"No loadProfile found for current geo-time {geoTime} for geo {geo}. Setting user load to zero",
geoNowTime, geo.name);
}

log.LogInformation("Calculated current total user load for geo {geo}: {currentUserLoad}", geo.name, currentUserLoad);
log.LogInformation("Calculated current total user load for geo {geo}: {currentUserLoad}", geo.name,
currentUserLoad);

var functionsInGeo = Environment.GetEnvironmentVariable($"FUNCTIONS_{geo.name}");
if (string.IsNullOrEmpty(functionsInGeo))
Expand All @@ -116,13 +122,18 @@
for (int i = 0; i < functionNames.Length; i++)
{
string functionName = functionNames[i].ToUpper();
var functionKey = Environment.GetEnvironmentVariable($"FUNCTIONKEY_{functionName.Replace("-", "_")}"); // Env var will have underscores for any dashes
var functionKey =
Environment.GetEnvironmentVariable(
$"FUNCTIONKEY_{functionName.Replace("-", "_")}"); // Env var will have underscores for any dashes
if (string.IsNullOrEmpty(functionKey))
{
log.LogError("No Function Key configured for Function {functionName} functionName", functionName);
log.LogError("No Function Key configured for Function {functionName} functionName",
functionName);
continue;
}
var fullFunctionUrl = string.Format(regionalLoadgenFunctionBaseUrl, functionName, usersPerGroup[i]);

var fullFunctionUrl =
string.Format(RegionalLoadgenFunctionBaseUrl, functionName, usersPerGroup[i]);
log.LogInformation("Calling Function URL: {url}", fullFunctionUrl);

var request = new HttpRequestMessage(HttpMethod.Get, fullFunctionUrl);
Expand All @@ -138,16 +149,19 @@
}
else
{
log.LogWarning("Unsuccessful call to Function {function}. Status code:{code}", functionName, response.StatusCode);
log.LogWarning("Unsuccessful call to Function {function}. Status code:{code}",
functionName, response.StatusCode);
}
}
catch (Exception e)
{
log.LogError(e, "Error calling remote Function at {url}", fullFunctionUrl);
}
}

log.LogInformation("Finished processing geo {geo}", geo.name);
}

log.LogInformation("Finished processing all geos");

return invokedFunctions;
Expand Down Expand Up @@ -185,4 +199,4 @@
return groups;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace GlobalOrchestrator
{
public static class LoadSettingFunctionHttp
public class LoadSettingFunctionHttp(ILogger<LoadSettingFunctionHttp> logger)
{

[FunctionName(nameof(LoadSettingFunctionHttp))]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ExecutionContext context,
ILogger log)
[Function(nameof(LoadSettingFunctionHttp))]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req)
{
var res = await LoadSetter.LoadSetterInternalAsync(context, log);
var res = await LoadSetter.LoadSetterInternalAsync(logger);
return new ObjectResult(res);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace GlobalOrchestrator
{
public static class LoadSettingFunctionTimer
public class LoadSettingFunctionTimer(ILogger<LoadSettingFunctionTimer> logger)
{
[FunctionName(nameof(LoadSettingFunctionTimer))]
public static async Task Run([TimerTrigger("0 */10 * * * *")] TimerInfo myTimer, // run every 10min
ExecutionContext context,
ILogger log)
[Function(nameof(LoadSettingFunctionTimer))]
public async Task Run([TimerTrigger("0 */10 * * * *")] TimerInfo myTimer) // run every 10min
{
log.LogInformation($"{nameof(LoadSettingFunctionTimer)} executed at: {DateTime.Now}");
await LoadSetter.LoadSetterInternalAsync(context, log);
logger.LogInformation($"{nameof(LoadSettingFunctionTimer)} executed at: {DateTime.Now}");
await LoadSetter.LoadSetterInternalAsync(logger);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureServices(services =>
{
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();
})
.Build();

host.Run();
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
"version": "2.0",
"logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information",
"Microsoft.Identity.Web": "Warning"
},
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
},
"enableLiveMetricsFilters": true
}
}
}

This file was deleted.

Loading
Loading