Skip to content

Commit d1e06f1

Browse files
Adding support for faas.invoke_duration metric (#10929)
* Adding support for faas.invoke_duration metric
1 parent a6aac43 commit d1e06f1

File tree

10 files changed

+199
-69
lines changed

10 files changed

+199
-69
lines changed

release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
- Adjusting the logic to determine the warmup call in placeholder simulation mode to align with the production flow (#10918)
1212
- Fixing invalid DateTimes in status blobs when invoking via portal (#10916)
1313
- Bug fix for platform release channel bundles resolution casing issue and additional logging (#10921)
14+
- Adding support for faas.invoke_duration metric and other spec related updates (#10929)

src/WebJobs.Script.Grpc/Channel/GrpcWorkerChannel.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,6 @@ private void AddAdditionalTraceContext(MapField<string, string> attributes, Scri
17181718
if (isOtelEnabled)
17191719
{
17201720
Activity.Current?.AddTag(ResourceSemanticConventions.FaaSName, context.FunctionMetadata.Name);
1721-
Activity.Current?.AddTag(ResourceSemanticConventions.FaaSTrigger, OpenTelemetryConstants.ResolveTriggerType(context.FunctionMetadata?.Trigger?.Type));
17221721
}
17231722
}
17241723

src/WebJobs.Script.WebHost/Diagnostics/FunctionInstanceLogger.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ private void EndFunction(FunctionInstanceLogEntry item)
131131
}
132132

133133
_metrics.EndEvent(invokeLatencyEvent);
134+
_hostMetrics.TrackFunctionExecutionDuration(item.Duration.TotalSeconds, functionName);
134135
}
135136

136137
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken)) => Task.CompletedTask;

src/WebJobs.Script/Diagnostics/OpenTelemetry/FunctionsResourceDetector.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7-
using System.Reflection;
87
using OpenTelemetry.Resources;
98

109
namespace Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry
@@ -22,7 +21,6 @@ public Resource Detect()
2221
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.ServiceVersion, version));
2322
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.AISDKPrefix, $@"{OpenTelemetryConstants.SDKPrefix}:{version}"));
2423
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.ProcessId, Process.GetCurrentProcess().Id));
25-
attributeList.Add(new KeyValuePair<string, object>(ResourceSemanticConventions.FaaSVersion, version));
2624

2725
// Add these attributes only if running in Azure.
2826
if (!string.IsNullOrEmpty(serviceName))

src/WebJobs.Script/Diagnostics/OpenTelemetry/OpenTelemetryConfigurationExtensions.cs

Lines changed: 91 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics;
56
using System.Diagnostics.Tracing;
7+
using System.Linq;
68
using Azure.Core;
79
using Azure.Identity;
810
using Azure.Monitor.OpenTelemetry.Exporter;
911
using Azure.Monitor.OpenTelemetry.LiveMetrics;
12+
using Microsoft.AspNetCore.Routing;
13+
using Microsoft.Azure.WebJobs.Script.Metrics;
1014
using Microsoft.Extensions.Configuration;
1115
using Microsoft.Extensions.DependencyInjection;
1216
using Microsoft.Extensions.Hosting;
1317
using Microsoft.Extensions.Logging;
18+
using OpenTelemetry;
1419
using OpenTelemetry.Logs;
1520
using OpenTelemetry.Metrics;
1621
using OpenTelemetry.Resources;
@@ -47,52 +52,94 @@ internal static void ConfigureOpenTelemetry(this ILoggingBuilder loggingBuilder,
4752
}
4853

4954
loggingBuilder
50-
.AddOpenTelemetry(o =>
51-
{
52-
o.SetResourceBuilder(ConfigureResource(ResourceBuilder.CreateDefault()));
53-
if (enableOtlp)
54-
{
55-
o.AddOtlpExporter();
56-
}
57-
if (enableAzureMonitor)
58-
{
59-
o.AddAzureMonitorLogExporter(options => ConfigureAzureMonitorOptions(options, azMonConnectionString, credential));
60-
}
61-
o.IncludeFormattedMessage = true;
62-
o.IncludeScopes = false;
63-
})
64-
.AddDefaultOpenTelemetryFilters();
55+
.ConfigureLogging(enableOtlp, enableAzureMonitor, azMonConnectionString, credential).Services
56+
.AddOpenTelemetry()
57+
.ConfigureResource(r => ConfigureResource(r))
58+
.ConfigureMetrics(enableOtlp, enableAzureMonitor, azMonConnectionString, credential)
59+
.ConfigureTracing(enableOtlp, enableAzureMonitor, azMonConnectionString, credential)
60+
.ConfigureEventLogLevel(context.Configuration);
6561

6662
// Azure SDK instrumentation is experimental.
6763
AppContext.SetSwitch("Azure.Experimental.EnableActivitySource", true);
64+
}
6865

69-
ConfigureTracing(loggingBuilder, enableOtlp, enableAzureMonitor, azMonConnectionString, credential);
66+
private static IOpenTelemetryBuilder ConfigureMetrics(this IOpenTelemetryBuilder builder, bool enableOtlp, bool enableAzureMonitor, string azMonConnectionString, TokenCredential credential)
67+
{
68+
return builder.WithMetrics(builder =>
69+
{
70+
builder.AddAspNetCoreInstrumentation();
71+
builder.AddMeter(HostMetrics.FaasMeterName);
72+
builder.AddView(HostMetrics.FaasInvokeDuration, new ExplicitBucketHistogramConfiguration
73+
{
74+
Boundaries = new double[] { 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 }
75+
});
7076

71-
ConfigureEventLogLevel(loggingBuilder, context.Configuration);
77+
if (enableOtlp)
78+
{
79+
builder.AddOtlpExporter();
80+
}
81+
if (enableAzureMonitor)
82+
{
83+
builder.AddAzureMonitorMetricExporter(opt => ConfigureAzureMonitorOptions(opt, azMonConnectionString, credential));
84+
}
85+
});
7286
}
7387

74-
private static void ConfigureTracing(ILoggingBuilder loggingBuilder, bool enableOtlp, bool enableAzureMonitor, string azMonConnectionString, TokenCredential credential)
88+
private static IOpenTelemetryBuilder ConfigureTracing(this IOpenTelemetryBuilder builder, bool enableOtlp, bool enableAzureMonitor, string azMonConnectionString, TokenCredential credential)
7589
{
76-
loggingBuilder.Services.AddOpenTelemetry()
77-
.ConfigureResource(r => ConfigureResource(r))
78-
.WithTracing(builder =>
90+
return builder.WithTracing(builder =>
91+
{
92+
builder.AddSource("Azure.*")
93+
.AddAspNetCoreInstrumentation(o =>
7994
{
80-
builder.AddSource("Azure.*")
81-
.AddAspNetCoreInstrumentation();
82-
83-
if (enableOtlp)
95+
o.EnrichWithHttpResponse = (activity, httpResponse) =>
8496
{
85-
builder.AddOtlpExporter();
86-
}
97+
if (Activity.Current != null)
98+
{
99+
var routingFeature = httpResponse.HttpContext.Features.Get<AspNetCore.Routing.IRoutingFeature>();
100+
var template = routingFeature.RouteData.Routers.FirstOrDefault(r => r is Route) as Route;
101+
102+
Activity.Current.DisplayName = $"{Activity.Current.DisplayName} {template?.RouteTemplate}";
103+
Activity.Current.AddTag(ResourceSemanticConventions.HttpRoute, template?.RouteTemplate);
104+
Activity.Current.AddTag(ResourceSemanticConventions.FaaSTrigger, OpenTelemetryConstants.HttpTriggerType);
105+
}
106+
};
107+
});
87108

88-
if (enableAzureMonitor)
89-
{
90-
builder.AddAzureMonitorTraceExporter(opt => ConfigureAzureMonitorOptions(opt, azMonConnectionString, credential));
91-
builder.AddLiveMetrics(opt => ConfigureAzureMonitorOptions(opt, azMonConnectionString, credential));
92-
}
109+
if (enableOtlp)
110+
{
111+
builder.AddOtlpExporter();
112+
}
93113

94-
builder.AddProcessor(ActivitySanitizingProcessor.Instance);
95-
});
114+
if (enableAzureMonitor)
115+
{
116+
builder.AddAzureMonitorTraceExporter(opt => ConfigureAzureMonitorOptions(opt, azMonConnectionString, credential));
117+
builder.AddLiveMetrics(opt => ConfigureAzureMonitorOptions(opt, azMonConnectionString, credential));
118+
}
119+
120+
builder.AddProcessor(ActivitySanitizingProcessor.Instance);
121+
});
122+
}
123+
124+
private static ILoggingBuilder ConfigureLogging(this ILoggingBuilder builder, bool enableOtlp, bool enableAzureMonitor, string azMonConnectionString, TokenCredential credential)
125+
{
126+
builder.AddOpenTelemetry(o =>
127+
{
128+
o.SetResourceBuilder(ConfigureResource(ResourceBuilder.CreateDefault()));
129+
if (enableOtlp)
130+
{
131+
o.AddOtlpExporter();
132+
}
133+
if (enableAzureMonitor)
134+
{
135+
o.AddAzureMonitorLogExporter(options => ConfigureAzureMonitorOptions(options, azMonConnectionString, credential));
136+
}
137+
o.IncludeFormattedMessage = true;
138+
o.IncludeScopes = false;
139+
});
140+
builder.AddDefaultOpenTelemetryFilters();
141+
142+
return builder;
96143
}
97144

98145
private static ILoggingBuilder AddDefaultOpenTelemetryFilters(this ILoggingBuilder loggingBuilder)
@@ -101,23 +148,27 @@ private static ILoggingBuilder AddDefaultOpenTelemetryFilters(this ILoggingBuild
101148
// These are messages piped back to the host from the worker - we don't handle these anymore if the worker has OpenTelemetry enabled.
102149
// Instead, we expect the user's own code to be logging these where they want them to go.
103150
.AddFilter<OpenTelemetryLoggerProvider>("Function.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
104-
.AddFilter<OpenTelemetryLoggerProvider>("Azure.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
151+
152+
// Always filter out these logs
153+
.AddFilter<OpenTelemetryLoggerProvider>("Azure.*", _ => false)
105154
// Host.Results and Host.Aggregator are used to emit metrics, ignoring these categories.
106-
.AddFilter<OpenTelemetryLoggerProvider>("Host.Results", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
107-
.AddFilter<OpenTelemetryLoggerProvider>("Host.Aggregator", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
155+
.AddFilter<OpenTelemetryLoggerProvider>("Host.Results", _ => false)
156+
.AddFilter<OpenTelemetryLoggerProvider>("Host.Aggregator", _ => false)
108157
// Ignoring all Microsoft.Azure.WebJobs.* logs like /getScriptTag and /lock.
109-
.AddFilter<OpenTelemetryLoggerProvider>("Microsoft.Azure.WebJobs.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled);
158+
.AddFilter<OpenTelemetryLoggerProvider>("Microsoft.Azure.WebJobs.*", _ => false);
110159
}
111160

112-
private static void ConfigureEventLogLevel(ILoggingBuilder loggingBuilder, IConfiguration configuration)
161+
private static IOpenTelemetryBuilder ConfigureEventLogLevel(this IOpenTelemetryBuilder builder, IConfiguration configuration)
113162
{
114163
string eventLogLevel = GetConfigurationValue(EnvironmentSettingNames.OpenTelemetryEventListenerLogLevel, configuration);
115164
EventLevel level = !string.IsNullOrEmpty(eventLogLevel) &&
116165
Enum.TryParse(eventLogLevel, ignoreCase: true, out EventLevel parsedLevel)
117166
? parsedLevel
118167
: EventLevel.Warning;
119168

120-
loggingBuilder.Services.AddHostedService(_ => new OpenTelemetryEventListenerService(level));
169+
builder.Services.AddHostedService(_ => new OpenTelemetryEventListenerService(level));
170+
171+
return builder;
121172
}
122173

123174
private static ResourceBuilder ConfigureResource(ResourceBuilder builder)

src/WebJobs.Script/Diagnostics/OpenTelemetry/OpenTelemetryConstants.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,6 @@ internal static class OpenTelemetryConstants
1313
internal const string ResourceGroupEnvVar = "WEBSITE_RESOURCE_GROUP";
1414
internal const string OwnerNameEnvVar = "WEBSITE_OWNER_NAME";
1515
internal const string AzureFunctionsGroup = "azure.functions.group";
16-
17-
internal static string ResolveTriggerType(string trigger)
18-
{
19-
switch (trigger)
20-
{
21-
case "httpTrigger":
22-
return "http";
23-
default:
24-
return trigger;
25-
}
26-
}
16+
internal const string HttpTriggerType = "http";
2717
}
2818
}

src/WebJobs.Script/Diagnostics/OpenTelemetry/ResourceSemanticConventions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ internal static class ResourceSemanticConventions
1414
internal const string CloudProvider = "cloud.provider";
1515
internal const string CloudPlatform = "cloud.platform";
1616
internal const string CloudRegion = "cloud.region";
17-
internal const string CloudResourceId = "cloud.resource.id";
17+
internal const string CloudResourceId = "cloud.resource_id";
1818

1919
//FaaS
2020
internal const string FaaSTrigger = "faas.trigger";
2121
internal const string FaaSInvocationId = "faas.invocation_id";
2222
internal const string FaaSColdStart = "faas.coldstart";
2323
internal const string FaaSName = "faas.name";
24-
internal const string FaaSVersion = "faas.version";
2524
internal const string FaaSInstance = "faas.instance";
2625

2726
// Process
@@ -30,6 +29,7 @@ internal static class ResourceSemanticConventions
3029
// Http
3130
internal const string QueryUrl = "url.query";
3231
internal const string FullUrl = "url.full";
32+
internal const string HttpRoute = "http.route";
3333

3434
// AI
3535
internal const string AISDKPrefix = "ai.sdk.prefix";

src/WebJobs.Script/Metrics/HostMetrics.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ public class HostMetrics : IHostMetrics
1616
private readonly ILogger _logger;
1717

1818
public const string MeterName = "Microsoft.Azure.WebJobs.Script.Host.Internal";
19+
public const string FaasMeterName = "Microsoft.Azure.Functions.Host";
1920
public const string CloudPlatformName = "azure_functions";
2021
public const string AppFailureCount = "azure.functions.app_failures";
2122
public const string ActiveInvocationCount = "azure.functions.active_invocations";
2223
public const string StartedInvocationCount = "azure.functions.started_invocations";
2324

25+
// FaaS Metrics
26+
public const string FaasInvokeDuration = "faas.invoke_duration";
27+
2428
private Counter<long> _appFailureCount;
2529
private Counter<long> _startedInvocationCount;
30+
private Histogram<double> _faasInvokeDuration;
2631

2732
private KeyValuePair<string, object>? _cachedFunctionGroupTag = null;
2833

@@ -59,6 +64,12 @@ public HostMetrics(IMeterFactory meterFactory, IEnvironment environment, ILogger
5964

6065
_appFailureCount = meter.CreateCounter<long>(AppFailureCount, "numeric", "Number of times the host has failed to start.");
6166
_startedInvocationCount = meter.CreateCounter<long>(StartedInvocationCount, "numeric", "Number of function invocations that have started.");
67+
68+
var faasMeter = meterFactory.Create(new MeterOptions(FaasMeterName));
69+
_faasInvokeDuration = faasMeter.CreateHistogram<double>(
70+
name: FaasInvokeDuration,
71+
unit: "s",
72+
description: "Measures the duration of the function's logic execution.");
6273
}
6374

6475
private KeyValuePair<string, object> FunctionGroupTag
@@ -95,5 +106,14 @@ private KeyValuePair<string, object> FunctionGroupTag
95106
public void AppFailure() => _appFailureCount.Add(1, FunctionGroupTag);
96107

97108
public void IncrementStartedInvocationCount() => _startedInvocationCount.Add(1, FunctionGroupTag);
109+
110+
public void TrackFunctionExecutionDuration(double duration, string functionName)
111+
{
112+
var tags = new TagList()
113+
{
114+
{ ResourceSemanticConventions.FaaSName, functionName }
115+
};
116+
_faasInvokeDuration.Record(duration, tags);
117+
}
98118
}
99119
}

src/WebJobs.Script/Metrics/IHostMetrics.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,12 @@ public interface IHostMetrics
1919
/// that have started on a given instance.
2020
/// </summary>
2121
public void IncrementStartedInvocationCount();
22+
23+
/// <summary>
24+
/// Measures the duration of the function’s logic execution.
25+
/// </summary>
26+
/// <param name="duration">Invoke duration.</param>
27+
/// <param name="functionName">Function name.</param>
28+
void TrackFunctionExecutionDuration(double duration, string functionName);
2229
}
2330
}

0 commit comments

Comments
 (0)