Skip to content

Commit 205417b

Browse files
Cold Start Optimization (#10776)
* Cold Start Optimization * Removing HTTP Client Instrumentation.
1 parent f5ad41b commit 205417b

File tree

7 files changed

+169
-253
lines changed

7 files changed

+169
-253
lines changed

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

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

44
using System;
5-
using System.ComponentModel;
6-
using System.Diagnostics;
75
using System.Diagnostics.Tracing;
86
using Azure.Core;
97
using Azure.Identity;
@@ -23,15 +21,29 @@ namespace Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry
2321
{
2422
internal static class OpenTelemetryConfigurationExtensions
2523
{
26-
internal static void ConfigureOpenTelemetry(this ILoggingBuilder loggingBuilder, HostBuilderContext context)
24+
internal static void ConfigureOpenTelemetry(this ILoggingBuilder loggingBuilder, HostBuilderContext context, TelemetryMode telemetryMode)
2725
{
28-
string azMonConnectionString = GetConfigurationValue(EnvironmentSettingNames.AppInsightsConnectionString, context.Configuration);
29-
TokenCredential credential = GetTokenCredential(context.Configuration);
30-
31-
bool enableOtlp = false;
32-
if (!string.IsNullOrEmpty(GetConfigurationValue(EnvironmentSettingNames.OtlpEndpoint, context.Configuration)))
26+
var connectionString = GetConfigurationValue(EnvironmentSettingNames.AppInsightsConnectionString, context.Configuration);
27+
var (azMonConnectionString, credential, enableOtlp, enableAzureMonitor) = telemetryMode switch
28+
{
29+
// Initializing OTel services during placeholder mode as well to avoid the cost of JITting these objects during specialization.
30+
// Azure Monitor Exporter requires a connection string to be initialized. Use placeholder connection string if in placeholder mode.
31+
TelemetryMode.Placeholder => (
32+
"InstrumentationKey=00000000-0000-0000-0000-000000000000;",
33+
null,
34+
true,
35+
true),
36+
_ => (
37+
connectionString,
38+
GetTokenCredential(context.Configuration),
39+
!string.IsNullOrEmpty(GetConfigurationValue(EnvironmentSettingNames.OtlpEndpoint, context.Configuration)),
40+
!string.IsNullOrEmpty(connectionString))
41+
};
42+
43+
// If neither OTLP nor Azure Monitor is enabled, don't configure OpenTelemetry.
44+
if (!enableOtlp && !enableAzureMonitor)
3345
{
34-
enableOtlp = true;
46+
return;
3547
}
3648

3749
loggingBuilder
@@ -42,82 +54,75 @@ internal static void ConfigureOpenTelemetry(this ILoggingBuilder loggingBuilder,
4254
{
4355
o.AddOtlpExporter();
4456
}
45-
if (!string.IsNullOrEmpty(azMonConnectionString))
57+
if (enableAzureMonitor)
4658
{
4759
o.AddAzureMonitorLogExporter(options => ConfigureAzureMonitorOptions(options, azMonConnectionString, credential));
4860
}
4961
o.IncludeFormattedMessage = true;
5062
o.IncludeScopes = false;
5163
})
52-
// These are messages piped back to the host from the worker - we don't handle these anymore if the worker has OpenTelemetry enabled.
53-
// Instead, we expect the user's own code to be logging these where they want them to go.
54-
.AddFilter<OpenTelemetryLoggerProvider>("Function.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
55-
.AddFilter<OpenTelemetryLoggerProvider>("Azure.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
56-
// Host.Results and Host.Aggregator are used to emit metrics, ignoring these categories.
57-
.AddFilter<OpenTelemetryLoggerProvider>("Host.Results", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
58-
.AddFilter<OpenTelemetryLoggerProvider>("Host.Aggregator", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
59-
// Ignoring all Microsoft.Azure.WebJobs.* logs like /getScriptTag and /lock.
60-
.AddFilter<OpenTelemetryLoggerProvider>("Microsoft.Azure.WebJobs.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled);
64+
.AddDefaultOpenTelemetryFilters();
6165

6266
// Azure SDK instrumentation is experimental.
6367
AppContext.SetSwitch("Azure.Experimental.EnableActivitySource", true);
6468

69+
ConfigureTracing(loggingBuilder, enableOtlp, enableAzureMonitor, azMonConnectionString, credential);
70+
71+
ConfigureEventLogLevel(loggingBuilder, context.Configuration);
72+
}
73+
74+
private static void ConfigureTracing(ILoggingBuilder loggingBuilder, bool enableOtlp, bool enableAzureMonitor, string azMonConnectionString, TokenCredential credential)
75+
{
6576
loggingBuilder.Services.AddOpenTelemetry()
6677
.ConfigureResource(r => ConfigureResource(r))
67-
.WithTracing(b =>
78+
.WithTracing(builder =>
6879
{
69-
b.AddSource("Azure.*");
70-
b.AddAspNetCoreInstrumentation();
71-
b.AddHttpClientInstrumentation(o =>
72-
{
73-
o.FilterHttpRequestMessage = _ =>
74-
{
75-
Activity activity = Activity.Current?.Parent;
76-
return activity == null || !activity.Source.Name.Equals("Azure.Core.Http");
77-
};
78-
});
80+
builder.AddSource("Azure.*")
81+
.AddAspNetCoreInstrumentation();
7982

8083
if (enableOtlp)
8184
{
82-
b.AddOtlpExporter();
85+
builder.AddOtlpExporter();
8386
}
8487

85-
if (!string.IsNullOrEmpty(azMonConnectionString))
88+
if (enableAzureMonitor)
8689
{
87-
b.AddAzureMonitorTraceExporter(options => ConfigureAzureMonitorOptions(options, azMonConnectionString, credential));
88-
b.AddLiveMetrics(options => ConfigureAzureMonitorOptions(options, azMonConnectionString, credential));
90+
builder.AddAzureMonitorTraceExporter(opt => ConfigureAzureMonitorOptions(opt, azMonConnectionString, credential));
91+
builder.AddLiveMetrics(opt => ConfigureAzureMonitorOptions(opt, azMonConnectionString, credential));
8992
}
9093

91-
b.AddProcessor(ActivitySanitizingProcessor.Instance);
92-
b.AddProcessor(TraceFilterProcessor.Instance);
94+
builder.AddProcessor(ActivitySanitizingProcessor.Instance);
9395
});
96+
}
9497

95-
string eventLogLevel = GetConfigurationValue(EnvironmentSettingNames.OpenTelemetryEventListenerLogLevel, context.Configuration);
96-
if (!string.IsNullOrEmpty(eventLogLevel))
97-
{
98-
if (Enum.TryParse(eventLogLevel, ignoreCase: true, out EventLevel level))
99-
{
100-
loggingBuilder.Services.AddHostedService(service => new OpenTelemetryEventListenerService(level));
101-
}
102-
else
103-
{
104-
throw new InvalidEnumArgumentException($"Invalid '{EnvironmentSettingNames.OpenTelemetryEventListenerLogLevel}' of '{eventLogLevel}'.");
105-
}
106-
}
107-
else
108-
{
109-
// Log all warnings and above by default.
110-
loggingBuilder.Services.AddHostedService(service => new OpenTelemetryEventListenerService(EventLevel.Warning));
111-
}
98+
private static ILoggingBuilder AddDefaultOpenTelemetryFilters(this ILoggingBuilder loggingBuilder)
99+
{
100+
return loggingBuilder
101+
// These are messages piped back to the host from the worker - we don't handle these anymore if the worker has OpenTelemetry enabled.
102+
// Instead, we expect the user's own code to be logging these where they want them to go.
103+
.AddFilter<OpenTelemetryLoggerProvider>("Function.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
104+
.AddFilter<OpenTelemetryLoggerProvider>("Azure.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled)
105+
// 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)
108+
// Ignoring all Microsoft.Azure.WebJobs.* logs like /getScriptTag and /lock.
109+
.AddFilter<OpenTelemetryLoggerProvider>("Microsoft.Azure.WebJobs.*", _ => !ScriptHost.WorkerOpenTelemetryEnabled);
110+
}
112111

113-
static ResourceBuilder ConfigureResource(ResourceBuilder r)
114-
{
115-
r.AddDetector(new FunctionsResourceDetector());
112+
private static void ConfigureEventLogLevel(ILoggingBuilder loggingBuilder, IConfiguration configuration)
113+
{
114+
string eventLogLevel = GetConfigurationValue(EnvironmentSettingNames.OpenTelemetryEventListenerLogLevel, configuration);
115+
EventLevel level = !string.IsNullOrEmpty(eventLogLevel) &&
116+
Enum.TryParse(eventLogLevel, ignoreCase: true, out EventLevel parsedLevel)
117+
? parsedLevel
118+
: EventLevel.Warning;
116119

117-
// Set the AI SDK to a key so we know all the telemetry came from the Functions Host
118-
// NOTE: This ties to \azure-sdk-for-net\sdk\monitor\Azure.Monitor.OpenTelemetry.Exporter\src\Internals\ResourceExtensions.cs :: AiSdkPrefixKey used in CreateAzureMonitorResource()
119-
return r;
120-
}
120+
loggingBuilder.Services.AddHostedService(_ => new OpenTelemetryEventListenerService(level));
121+
}
122+
123+
private static ResourceBuilder ConfigureResource(ResourceBuilder builder)
124+
{
125+
return builder.AddDetector(new FunctionsResourceDetector());
121126
}
122127

123128
private static string GetConfigurationValue(string key, IConfiguration configuration = null)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ namespace Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry
66
internal enum TelemetryMode
77
{
88
None = 0, // or Default
9-
ApplicationInsights = 1,
10-
OpenTelemetry = 2
9+
Placeholder = 1,
10+
ApplicationInsights = 2,
11+
OpenTelemetry = 3
1112
}
1213
}

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

Lines changed: 0 additions & 74 deletions
This file was deleted.

src/WebJobs.Script/ScriptHostBuilderExtensions.cs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -419,38 +419,37 @@ public static IHostBuilder SetAzureFunctionsConfigurationRoot(this IHostBuilder
419419

420420
internal static void ConfigureTelemetry(this ILoggingBuilder loggingBuilder, HostBuilderContext context)
421421
{
422-
TelemetryMode mode;
423-
var telemetryModeSection = context.Configuration.GetSection(ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, ConfigurationSectionNames.TelemetryMode));
424-
if (telemetryModeSection.Exists() && Enum.TryParse(telemetryModeSection.Value, true, out TelemetryMode telemetryMode))
425-
{
426-
mode = telemetryMode;
427-
}
428-
else
429-
{
430-
// Default to ApplicationInsights.
431-
mode = TelemetryMode.ApplicationInsights;
432-
}
422+
var telemetryMode = GetTelemetryMode(context);
433423

434424
// Use switch statement so any change to the enum results in a build error if we don't handle it.
435-
switch (mode)
425+
switch (telemetryMode)
436426
{
437427
case TelemetryMode.ApplicationInsights:
438428
case TelemetryMode.None:
439-
loggingBuilder.ConfigureApplicationInsights(context);
429+
loggingBuilder.ConfigureApplicationInsights(context, telemetryMode);
440430
break;
431+
441432
case TelemetryMode.OpenTelemetry:
442-
loggingBuilder.ConfigureOpenTelemetry(context);
433+
loggingBuilder.ConfigureOpenTelemetry(context, telemetryMode);
434+
break;
435+
436+
case TelemetryMode.Placeholder:
437+
loggingBuilder.ConfigureApplicationInsights(context, telemetryMode);
438+
loggingBuilder.ConfigureOpenTelemetry(context, telemetryMode);
443439
break;
440+
441+
default:
442+
throw new NotSupportedException($"Unhandled TelemetryMode: {telemetryMode}");
444443
}
445444
}
446445

447-
internal static void ConfigureApplicationInsights(this ILoggingBuilder builder, HostBuilderContext context)
446+
internal static void ConfigureApplicationInsights(this ILoggingBuilder builder, HostBuilderContext context, TelemetryMode telemetryMode)
448447
{
449448
string appInsightsInstrumentationKey = GetConfigurationValue(EnvironmentSettingNames.AppInsightsInstrumentationKey, context.Configuration);
450449
string appInsightsConnectionString = GetConfigurationValue(EnvironmentSettingNames.AppInsightsConnectionString, context.Configuration);
451450

452451
// Initializing AppInsights services during placeholder mode as well to avoid the cost of JITting these objects during specialization
453-
if (!string.IsNullOrEmpty(appInsightsInstrumentationKey) || !string.IsNullOrEmpty(appInsightsConnectionString) || SystemEnvironment.Instance.IsPlaceholderModeEnabled())
452+
if (!string.IsNullOrEmpty(appInsightsInstrumentationKey) || !string.IsNullOrEmpty(appInsightsConnectionString) || telemetryMode == TelemetryMode.Placeholder)
454453
{
455454
string eventLogLevel = GetConfigurationValue(EnvironmentSettingNames.AppInsightsEventListenerLogLevel, context.Configuration);
456455
string authString = GetConfigurationValue(EnvironmentSettingNames.AppInsightsAuthenticationString, context.Configuration);
@@ -616,5 +615,25 @@ private static string GetConfigurationValue(string key, IConfiguration configura
616615
return null;
617616
}
618617
}
618+
619+
private static TelemetryMode GetTelemetryMode(HostBuilderContext context)
620+
{
621+
var telemetryModeSection = context.Configuration.GetSection(ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, ConfigurationSectionNames.TelemetryMode));
622+
623+
if (telemetryModeSection.Exists())
624+
{
625+
if (Enum.TryParse(telemetryModeSection.Value, true, out TelemetryMode telemetryMode))
626+
{
627+
return telemetryMode;
628+
}
629+
630+
throw new InvalidEnumArgumentException($"Invalid TelemetryMode: `{telemetryModeSection.Value}`.");
631+
}
632+
633+
// Initialize AppInsights SDK and OTel services during placeholder mode to avoid JIT cost during specialization.
634+
return SystemEnvironment.Instance.IsPlaceholderModeEnabled()
635+
? TelemetryMode.Placeholder
636+
: TelemetryMode.ApplicationInsights;
637+
}
619638
}
620639
}

test/WebJobs.Script.Tests/Configuration/ApplicationInsightsConfigurationTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.ApplicationInsights.DependencyCollector;
77
using Microsoft.ApplicationInsights.Extensibility;
88
using Microsoft.Azure.WebJobs.Logging.ApplicationInsights;
9+
using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry;
910
using Microsoft.Extensions.DependencyInjection;
1011
using Microsoft.Extensions.Hosting;
1112
using Microsoft.Extensions.Options;
@@ -24,7 +25,7 @@ public void ServicesDisabled_InPlaceholderMode()
2425
host = new HostBuilder()
2526
.ConfigureLogging((context, builder) =>
2627
{
27-
builder.ConfigureApplicationInsights(context);
28+
builder.ConfigureApplicationInsights(context, TelemetryMode.Placeholder);
2829
})
2930
.ConfigureServices(s =>
3031
{

0 commit comments

Comments
 (0)