Skip to content

Commit

Permalink
Added support for tracking remaining work in Cosmos triggers
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesRandall committed Feb 16, 2019
1 parent 51f3234 commit 3a7466b
Show file tree
Hide file tree
Showing 24 changed files with 341 additions and 64 deletions.
120 changes: 120 additions & 0 deletions Exemplars/StandardFunctions/CosmosMonitorExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Documents;
using System.Collections.Generic;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.ChangeFeedProcessor.PartitionManagement;
using Microsoft.Azure.Documents.ChangeFeedProcessor;
using System.Data.Common;
using System.Threading;

namespace FunctionApp2
{
public static class MonitorTrigger
{
// Just a normal trigger
[FunctionName("Trigger")]
public static void Trigger([CosmosDBTrigger(
databaseName: "%MonitoredDatabase%",
collectionName: "%MonitoredCollection%",
ConnectionStringSetting = "CosmosDB",
LeaseCollectionPrefix = "%MonitoredDatabaseLeasePrefix%",
LeaseCollectionName = "leases",
CreateLeaseCollectionIfNotExists = true)] IReadOnlyList<Document> input, ILogger log)
{
if (input != null && input.Count > 0)
{
log.LogInformation("Documents modified " + input.Count);

// We add a delay to simulate some work being doing
Thread.Sleep(1000);
}
}

[FunctionName("Monitor")]
public static async Task Monitor(
[TimerTrigger("*/1 * * * * *", RunOnStartup = true)] TimerInfo timer, // Timer will trigger every 1 second, adjust CRON expression
[CosmosDB("%MonitoredDatabase%", "%MonitoredCollection%", ConnectionStringSetting = "CosmosDB")] DocumentClient monitoredCollectionClient,
[CosmosDB("%MonitoredDatabase%", "leases", ConnectionStringSetting = "CosmosDB")] DocumentClient leaseCollectionClient,
ILogger log)
{
var estimator = GetRemainingWorkEstimator(monitoredCollectionClient, leaseCollectionClient);
var remainingWork = await estimator.GetEstimatedRemainingWork();
// Send custom metric to App Insight
log.LogInformation(remainingWork.ToString());
log.LogMetric("RemainingWork", remainingWork);
}

// Make it Lazy to reuse between calls
private static Lazy<IRemainingWorkEstimator> remainingWorkEstimator;

public static IRemainingWorkEstimator GetRemainingWorkEstimator(
DocumentClient monitoredCollectionClient,
DocumentClient leaseCollectionClient)
{
if (remainingWorkEstimator == null)
{
// Pull the Connection string from the environment, Environment.GetEnvironmentVariable will read the local.settings.json file or the deployed Function App configuration
CosmosDBConnectionString cosmosDBConnectionString = new CosmosDBConnectionString(Environment.GetEnvironmentVariable("CosmosDB"));
remainingWorkEstimator = new Lazy<IRemainingWorkEstimator>(() =>
{
var builder = new ChangeFeedProcessorBuilder()
.WithHostName("monitor") // Can be a random name
.WithProcessorOptions(new ChangeFeedProcessorOptions()
{
LeasePrefix = Environment.GetEnvironmentVariable("MonitoredDatabaseLeasePrefix")
})
.WithFeedCollection(new DocumentCollectionInfo()
{
Uri = cosmosDBConnectionString.ServiceEndpoint,
MasterKey = cosmosDBConnectionString.AuthKey,
CollectionName = Environment.GetEnvironmentVariable("MonitoredCollection"),
DatabaseName = Environment.GetEnvironmentVariable("MonitoredDatabase")
})
.WithLeaseCollection(new DocumentCollectionInfo()
{
Uri = cosmosDBConnectionString.ServiceEndpoint,
MasterKey = cosmosDBConnectionString.AuthKey,
CollectionName = "leases",
DatabaseName = Environment.GetEnvironmentVariable("MonitoredDatabase")
})
.WithFeedDocumentClient(monitoredCollectionClient)
.WithLeaseDocumentClient(leaseCollectionClient);

return builder.BuildEstimatorAsync().Result;

});
}

return remainingWorkEstimator.Value;
}

private class CosmosDBConnectionString
{
public CosmosDBConnectionString(string connectionString)
{
// Use this generic builder to parse the connection string
DbConnectionStringBuilder builder = new DbConnectionStringBuilder
{
ConnectionString = connectionString
};

if (builder.TryGetValue("AccountKey", out object key))
{
AuthKey = key.ToString();
}

if (builder.TryGetValue("AccountEndpoint", out object uri))
{
ServiceEndpoint = new Uri(uri.ToString());
}
}

public Uri ServiceEndpoint { get; set; }

public string AuthKey { get; set; }
}
}
}
2 changes: 1 addition & 1 deletion Exemplars/StandardFunctions/StandardFunctions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.EventHubs" Version="3.0.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.0.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageReference Include="FunctionMonkey" Version="0.18.1-beta000" />
<PackageReference Include="FunctionMonkey.Compiler" Version="0.18.7-beta000" />
<PackageReference Include="FunctionMonkey.FluentValidation" Version="0.18.1-beta000" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ItemGroup>
<PackageReference Include="FunctionMonkey" Version="0.16.1-beta000" />
<PackageReference Include="FunctionMonkey.Compiler" Version="0.16.1-beta000" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageReference Include="FunctionMonkey" Version="0.16.1-beta000" />
<PackageReference Include="FunctionMonkey.Compiler" Version="0.16.1-beta000" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.0.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
Expand Down
4 changes: 2 additions & 2 deletions Samples/Scratch/SwaggerBuildOut/FunctionAppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ public void Build(IFunctionHostBuilder builder)
.HttpFunction<FormCommand>(HttpMethod.Post)
)
.OpenApiDescription("A route description")
/*.CosmosDb("CosmosConnection", cosmos => cosmos
.CosmosDb("CosmosConnection", cosmos => cosmos
.ChangeFeedFunction<CosmosCommand, ExampleCosmosErrorHandler>("Items", "ToDoList", leaseCollectionPrefix:"fn1")//, convertToPascalCase:true)
//.ChangeFeedFunction<CosmosDocumentCommand>("Items", "ToDoList")
//.ChangeFeedFunction<CosmosDocumentBatchCommand>("Items", "ToDoList", leaseCollectionPrefix:"fn2")
)*/
)
.HttpRoute("/Add", route => route
.HttpFunction<AddCommand>(AuthorizationTypeEnum.Anonymous,HttpMethod.Post)
.OpenApiDescription("Adds two numbers together")
Expand Down
2 changes: 1 addition & 1 deletion Samples/Scratch/SwaggerBuildOut/SwaggerBuildOut.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.CosmosDB" Version="3.0.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="3.0.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.1" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.23" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Source\FunctionMonkey.Abstractions\FunctionMonkey.Abstractions.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public interface ICosmosDbFunctionBuilder
/// <param name="leaseRenewInterval">When set, it defines, in milliseconds, the renew interval for all leases for partitions currently held by an instance. Default is 17000 (17 seconds).</param>
/// <param name="checkpointFrequency">When set, it defines, in milliseconds, the interval between lease checkpoints. Default is always after a successful Function call.</param>
/// <param name="leasesCollectionThroughput">Defines the amount of Request Units to assign when the leases collection is created. This setting is only used When createLeaseCollectionIfNotExists is set to true. This parameter is automatically set when the binding is created using the portal.</param>
/// <param name="trackRemainingWork">If true (default value) this will create a timer function that will output a remaining work estimate to the log - the metric name will be of the form {functionName}RemainingWork </param>
/// <param name="remainingWorkCronExpression">The frequency that the monitor timer runs - defaults to once per every 5 seconds</param>
/// <returns></returns>
ICosmosDbFunctionOptionBuilder ChangeFeedFunction<TCommand>(string collectionName,
string databaseName,
Expand All @@ -50,7 +52,9 @@ ICosmosDbFunctionOptionBuilder ChangeFeedFunction<TCommand>(string collectionNam
int? leaseExpirationInterval=null,
int? leaseRenewInterval=null,
int? checkpointFrequency=null,
int? leasesCollectionThroughput=null
int? leasesCollectionThroughput=null,
bool trackRemainingWork=false,
string remainingWorkCronExpression = "*/5 * * * * *"
)
where TCommand : ICommand;

Expand Down Expand Up @@ -85,6 +89,8 @@ ICosmosDbFunctionOptionBuilder ChangeFeedFunction<TCommand>(string collectionNam
/// <param name="leaseRenewInterval">When set, it defines, in milliseconds, the renew interval for all leases for partitions currently held by an instance. Default is 17000 (17 seconds).</param>
/// <param name="checkpointFrequency">When set, it defines, in milliseconds, the interval between lease checkpoints. Default is always after a successful Function call.</param>
/// <param name="leasesCollectionThroughput">Defines the amount of Request Units to assign when the leases collection is created. This setting is only used When createLeaseCollectionIfNotExists is set to true. This parameter is automatically set when the binding is created using the portal.</param>
/// <param name="trackRemainingWork">If true (default value) this will create a timer function that will output a remaining work estimate to the log - the metric name will be of the form {functionName}RemainingWork </param>
/// <param name="remainingWorkCronExpression">The frequency that the monitor timer runs - defaults to once per every 5 seconds</param>
/// <returns></returns>
ICosmosDbFunctionOptionBuilder ChangeFeedFunction<TCommand, TCosmosDbErrorHandler>(string collectionName,
string databaseName,
Expand All @@ -100,7 +106,9 @@ ICosmosDbFunctionOptionBuilder ChangeFeedFunction<TCommand, TCosmosDbErrorHandle
int? leaseExpirationInterval = null,
int? leaseRenewInterval = null,
int? checkpointFrequency = null,
int? leasesCollectionThroughput = null
int? leasesCollectionThroughput = null,
bool trackRemainingWork = false,
string remainingWorkCronExpression = "*/5 * * * * *"
)
where TCommand : ICommand where TCosmosDbErrorHandler : ICosmosDbErrorHandler;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.20.0-beta000</Version>
<Version>0.21.5-beta000</Version>
<Authors>James Randall</Authors>
<PackageLicenseUrl>https://raw.githubusercontent.com/JamesRandall/AzureFromTheTrenches.Commanding/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://commanding.azurefromthetrenches.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/JamesRandall/FunctionMonkey.git</RepositoryUrl>
<AssemblyVersion>0.20.0.0</AssemblyVersion>
<FileVersion>0.20.0.0</FileVersion>
<AssemblyVersion>0.21.5.0</AssemblyVersion>
<FileVersion>0.21.5.0</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.20.0-beta000</Version>
<Version>0.21.5-beta000</Version>
<Company>James Randall</Company>
<Authors>James Randall</Authors>
<PackageLicenseUrl>https://raw.githubusercontent.com/JamesRandall/AzureFromTheTrenches.Commanding/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://commanding.azurefromthetrenches.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/JamesRandall/FunctionMonkey.git</RepositoryUrl>
<AssemblyVersion>0.20.0.0</AssemblyVersion>
<FileVersion>0.20.0.0</FileVersion>
<AssemblyVersion>0.21.5.0</AssemblyVersion>
<FileVersion>0.21.5.0</FileVersion>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.20.0-beta000</Version>
<AssemblyVersion>0.20.0.0</AssemblyVersion>
<FileVersion>0.20.0.0</FileVersion>
<Version>0.21.5-beta000</Version>
<AssemblyVersion>0.21.5.0</AssemblyVersion>
<FileVersion>0.21.5.0</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
8 changes: 5 additions & 3 deletions Source/FunctionMonkey.Compiler/FunctionMonkey.Compiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<Version>0.20.0-beta000</Version>
<Version>0.21.5-beta000</Version>
<AssemblyName>FunctionMonkey.Compiler</AssemblyName>
<PackageId>FunctionMonkey.Compiler</PackageId>
</PropertyGroup>
Expand Down Expand Up @@ -144,6 +144,7 @@
<None Remove="references\netstandard2.0\System.Xml.XPath.XDocument.dll" />
<None Remove="Templates\cosmosdb.csharp.handlebars" />
<None Remove="Templates\cosmosdb.json.handlebars" />
<None Remove="Templates\cosmosdbMonitor.json.handlebars" />
<None Remove="Templates\eventhubqueue.csharp.handlebars" />
<None Remove="Templates\eventhubqueue.json.handlebars" />
<None Remove="Templates\forcereference.csharp.handlebars" />
Expand Down Expand Up @@ -296,6 +297,7 @@
<EmbeddedResource Include="references\netstandard2.0\System.Xml.XmlSerializer.dll" />
<EmbeddedResource Include="references\netstandard2.0\System.Xml.XPath.dll" />
<EmbeddedResource Include="references\netstandard2.0\System.Xml.XPath.XDocument.dll" />
<EmbeddedResource Include="Templates\cosmosdbMonitor.json.handlebars" />
<EmbeddedResource Include="Templates\cosmosdb.json.handlebars" />
<EmbeddedResource Include="Templates\forcereference.csharp.handlebars" />
<EmbeddedResource Include="Templates\eventhubqueue.csharp.handlebars" />
Expand Down Expand Up @@ -358,8 +360,8 @@
<IntermediatePackDir>$(MSBuildProjectDirectory)/bin/$(Configuration)/publish/</IntermediatePackDir>
<PublishDir>$(IntermediatePackDir)$(TargetFramework)/</PublishDir>
<NuspecProperties>publishDir=$([MSBuild]::NormalizeDirectory($(IntermediatePackDir)))</NuspecProperties>
<AssemblyVersion>0.20.0.0</AssemblyVersion>
<FileVersion>0.20.0.0</FileVersion>
<AssemblyVersion>0.21.5.0</AssemblyVersion>
<FileVersion>0.21.5.0</FileVersion>
<PackageReleaseNotes></PackageReleaseNotes>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>FunctionMonkey.Compiler</id>
<version>0.20.0-beta000</version>
<version>0.21.5-beta000</version>
<authors>James Randall</authors>
<description>Generates Azure Functions from command registrations</description>
<licenseUrl>https://raw.githubusercontent.com/JamesRandall/AzureFromTheTrenches.Commanding/master/LICENSE</licenseUrl>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.IO;
using System.Linq;
Expand All @@ -15,6 +16,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.ChangeFeedProcessor;
using Microsoft.Azure.ServiceBus;
using Microsoft.Azure.WebJobs;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -265,7 +267,10 @@ private static IReadOnlyCollection<string> BuildCandidateReferenceList(IReadOnly
typeof(StringValues).GetTypeInfo().Assembly.Location,
typeof(ExecutionContext).GetTypeInfo().Assembly.Location,
typeof(Document).GetTypeInfo().Assembly.Location,
typeof(Message).GetTypeInfo().Assembly.Location
typeof(Message).GetTypeInfo().Assembly.Location,
typeof(ChangeFeedProcessorBuilder).Assembly.Location,
typeof(TimerInfo).Assembly.Location,
typeof(DbConnectionStringBuilder).Assembly.Location
};

if (target == FunctionCompiler.TargetEnum.NETCore21)
Expand Down
20 changes: 20 additions & 0 deletions Source/FunctionMonkey.Compiler/Implementation/JsonCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ public void Compile(IReadOnlyCollection<AbstractFunctionDefinition> functionDefi

string json = template(functionDefinition);
WriteFunctionTemplate(outputBinaryFolder, functionDefinition.Name, json);

if (functionDefinition is CosmosDbFunctionDefinition cosmosDbFunctionDefinition)
{
if (cosmosDbFunctionDefinition.TrackRemainingWork)
{
TimerFunctionDefinition cosmosMonitorDefinition = new TimerFunctionDefinition(functionDefinition.CommandType)
{
AssemblyName = cosmosDbFunctionDefinition.AssemblyName,
CommandDeserializerType = null,
CommandType = null,
CronExpression = cosmosDbFunctionDefinition.RemainingWorkCronExpression,
FunctionClassTypeName = $"{functionDefinition.Namespace}.Monitor{functionDefinition.Name}"
};
string timerTemplateSource = _templateProvider.GetJsonTemplate(cosmosMonitorDefinition);
Func<object, string> timerTemplate = Handlebars.Compile(timerTemplateSource);

string timerJson = timerTemplate(cosmosMonitorDefinition);
WriteFunctionTemplate(outputBinaryFolder, $"Monitor{functionDefinition.Name}", timerJson);
}
}
}

if (openApiOutputModel != null && openApiOutputModel.IsConfiguredForUserInterface)
Expand Down
Loading

0 comments on commit 3a7466b

Please sign in to comment.