Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
18a5e71
Update Tracer.cs
thedavl May 27, 2023
d99700b
mini agent dotnet tracing
thedavl Jun 1, 2023
25f19e4
Update Serverless.cs
thedavl Jun 1, 2023
865495c
testing
thedavl Jun 1, 2023
161a843
unit tests
thedavl Jun 2, 2023
a1f76fc
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 5, 2023
fbe5375
add unit tests
thedavl Jun 5, 2023
c793f56
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 5, 2023
600f7d4
Update TracerManagerFactory.cs
thedavl Jun 5, 2023
2fef8f0
fix mini agent path & log redirection
thedavl Jun 5, 2023
1007974
fix mini agent log handling
thedavl Jun 6, 2023
df23e5b
fix gcp dotnet path, unit test
thedavl Jun 6, 2023
0c6c462
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 6, 2023
f8d0bbc
fix unit test
thedavl Jun 6, 2023
8d8dbb0
Update ServerlessTests.cs
thedavl Jun 6, 2023
257ae75
Update MiniAgentManager.cs
thedavl Jun 6, 2023
1b87cc8
clean up
thedavl Jun 12, 2023
bead95f
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 12, 2023
4a91000
remove unecessary azure function env var check
thedavl Jun 12, 2023
dfcf5a8
default logging paths for azure & gcp functions
thedavl Jun 12, 2023
2a93f23
cleanup
thedavl Jun 12, 2023
42af89f
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 12, 2023
99d7d2e
Update ServerlessTests.cs
thedavl Jun 13, 2023
bde9bc4
refactor
thedavl Jun 13, 2023
6bd2410
remove unused vars
thedavl Jun 13, 2023
66ed2ca
rename mini agent manager tests
thedavl Jun 13, 2023
8925271
remove generated
thedavl Jun 13, 2023
f992bd2
add auto gen files
thedavl Jun 13, 2023
6081595
test
thedavl Jun 13, 2023
8a0afcf
Update AspectsDefinitions.g.cs
thedavl Jun 13, 2023
06bcc51
re-add auto gen files
thedavl Jun 13, 2023
ee3fd49
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 13, 2023
c175333
generated files bug
thedavl Jun 13, 2023
c5b797d
Update ServerlessTests.cs
thedavl Jun 13, 2023
8c7d1b2
refactor & address PR comments
thedavl Jun 14, 2023
12d7c67
keep a reference to mini agent process, so it doesnt get GC-ed
thedavl Jun 14, 2023
a714a85
revert accidentally committing auto-gen files
thedavl Jun 14, 2023
6a1effa
address PR comments, refactor #2
thedavl Jun 14, 2023
43ef86f
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 14, 2023
2aa4f0a
check specifically for consumption plan
thedavl Jun 14, 2023
30db120
extra unit tests
thedavl Jun 14, 2023
6bf1858
Update Tracer.cs
thedavl Jun 15, 2023
933663c
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 15, 2023
b3ec283
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jun 16, 2023
ad38598
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jul 13, 2023
f3cf5f8
address PR comments
thedavl Jul 17, 2023
756b550
more fluent assertions
thedavl Jul 17, 2023
ce5fd6b
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jul 17, 2023
9df6d1a
move mini agent spawning to trace manager
thedavl Jul 19, 2023
40fb69d
Update ConfigurationKeys.AzureAppService.cs
thedavl Jul 19, 2023
fb358b1
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jul 19, 2023
fee0b66
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Jul 26, 2023
bc7da60
cleanup unused imports + newline
thedavl Aug 1, 2023
5620a0f
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Aug 1, 2023
1cc5a32
address PR comments
thedavl Aug 1, 2023
8aa499f
Apply suggestions from code review
thedavl Aug 1, 2023
95512b8
Apply suggestions from code review
thedavl Aug 2, 2023
70fb7d4
address more PR comments
thedavl Aug 2, 2023
bef9495
address PR comments
thedavl Aug 2, 2023
1412986
fix bugs
thedavl Aug 2, 2023
13aca05
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Aug 2, 2023
f9cd577
Update tracer/src/Datadog.Trace/ClrProfiler/ServerlessInstrumentation…
thedavl Aug 2, 2023
5b2485c
remove unnecessary StatsComputationEnabled tests
thedavl Aug 2, 2023
f5bcff7
fix unit tests failing on windows (because of hardcoded "/" in paths)
thedavl Aug 2, 2023
902c99e
go all in on the tracer configuration system
thedavl Aug 3, 2023
c270108
don't need setters for these fields
thedavl Aug 3, 2023
8f8afdf
or this one
thedavl Aug 3, 2023
d388dc8
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Aug 3, 2023
fb9ea5b
remove unused variable
thedavl Aug 3, 2023
b196d63
Merge branch 'david.lee/spawn-mini-agent-gcp-azure' of github.com:Dat…
thedavl Aug 3, 2023
73cc3ea
Update TracerSettings.cs
thedavl Aug 3, 2023
0984e26
test az (AAS) function integration test failing
thedavl Aug 3, 2023
7a95b95
revert testing changes
thedavl Aug 4, 2023
14b5c45
add WEBSITE_SKU var to function integration tests
thedavl Aug 4, 2023
881b788
Merge branch 'master' into david.lee/spawn-mini-agent-gcp-azure
thedavl Aug 4, 2023
6fca67a
make WebsiteSKU nullable var
thedavl Aug 4, 2023
e397bd8
Merge branch 'david.lee/spawn-mini-agent-gcp-azure' of github.com:Dat…
thedavl Aug 4, 2023
cbd1c80
Update ImmutableAzureAppServiceSettings.cs
thedavl Aug 4, 2023
388206d
further mysterious debugging
thedavl Aug 4, 2023
4e868ff
revert testing changes
thedavl Aug 4, 2023
9b5604f
fix integration tests, once and for all
thedavl Aug 4, 2023
410231f
remove unneeded integration test launch settings
thedavl Aug 4, 2023
9372dee
address PR comments
thedavl Aug 7, 2023
e2d5767
address nit
thedavl Aug 7, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
<type fullname="System.Diagnostics.DiagnosticListener" />
</assembly>
<assembly fullname="System.Diagnostics.Process">
<type fullname="System.Diagnostics.DataReceivedEventArgs" />
<type fullname="System.Diagnostics.DataReceivedEventHandler" />
<type fullname="System.Diagnostics.Process" />
<type fullname="System.Diagnostics.ProcessStartInfo" />
<type fullname="System.Diagnostics.ProcessThreadCollection" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// <copyright file="ServerlessMiniAgent.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using System;
using System.Diagnostics;
using System.IO;
using Datadog.Trace.Configuration;
using Datadog.Trace.Logging;
using Datadog.Trace.Util;

namespace Datadog.Trace.ClrProfiler.ServerlessInstrumentation;

internal static class ServerlessMiniAgent
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(ServerlessMiniAgent));

internal static string GetMiniAgentPath(PlatformID os, ImmutableTracerSettings settings)
{
if (!settings.IsRunningInGCPFunctions && !settings.IsRunningInAzureFunctionsConsumptionPlan)
{
return null;
}

if (EnvironmentHelpers.GetEnvironmentVariable("DD_MINI_AGENT_PATH") is { } path)
{
return path;
}

// Environment.OSVersion.Platform can return PlatformID.Unix on MacOS, this is OK as GCP & Azure don't have MacOs functions.
if (os != PlatformID.Unix && os != PlatformID.Win32NT)
{
Log.Error("Serverless Mini Agent is only supported on Windows and Linux.");
return null;
}

string rustBinaryPathRoot;
if (settings.IsRunningInGCPFunctions)
{
rustBinaryPathRoot = Path.Combine(Path.DirectorySeparatorChar.ToString(), "layers", "google.dotnet.publish", "publish", "bin");
}
else
{
rustBinaryPathRoot = Path.Combine(Path.DirectorySeparatorChar.ToString(), "home", "site", "wwwroot");
}

var isWindows = os == PlatformID.Win32NT;

string rustBinaryPathOsFolder = isWindows ? "datadog-serverless-agent-windows-amd64" : "datadog-serverless-agent-linux-amd64";
string rustBinaryName = isWindows ? "datadog-serverless-trace-mini-agent.exe" : "datadog-serverless-trace-mini-agent";
return Path.Combine(rustBinaryPathRoot, rustBinaryPathOsFolder, rustBinaryName);
}

internal static void StartServerlessMiniAgent(ImmutableTracerSettings settings)
{
var serverlessMiniAgentPath = ServerlessMiniAgent.GetMiniAgentPath(Environment.OSVersion.Platform, settings);
if (string.IsNullOrEmpty(serverlessMiniAgentPath))
{
return;
}

if (!File.Exists(serverlessMiniAgentPath))
{
Log.Error("Serverless Mini Agent does not exist: {Path}", serverlessMiniAgentPath);
return;
}

try
{
Log.Debug("Trying to spawn the Serverless Mini Agent at path: {Path}", serverlessMiniAgentPath);
Process process = new Process();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it's impossible for a process to stay alive after the execution of a function, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a function executes azure/gcp will scale down the instance & throttle CPU, though we still have time after the function execution to flush data. So the process will stay alive until the instance is fully stopped (which takes about 15 mins for azure, and a bit longer for GCP)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is there any risk to start several processes on the same instance?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New requests routed to an existing instance shouldn't trigger the mini agent to be launched again, as the Tracer instance should still be alive. But if an additional process does try to start, it will shut down quickly when it detects the port is already being used.

process.StartInfo.FileName = serverlessMiniAgentPath;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += MiniAgentDataReceivedHandler;

process.Start();
process.BeginOutputReadLine();
}
catch (Exception ex)
{
Log.Error(ex, "Error spawning the Serverless Mini Agent.");
}
}

// Tries to clean Mini Agent logs and log to the correct level, otherwise just logs the data as-is to Info
// Mini Agent logs will be prefixed with "[Datadog Serverless Mini Agent]"
private static void MiniAgentDataReceivedHandler(object sender, DataReceivedEventArgs outLine)
{
var data = outLine.Data;
if (string.IsNullOrEmpty(data))
{
return;
}

var level = "INFO";
var log = data;
ProcessMiniAgentLog(data, out level, out log);

switch (level)
{
case "ERROR":
Log.Error("[Datadog Serverless Mini Agent] {Data}", log);
break;
case "WARN":
Log.Warning("[Datadog Serverless Mini Agent] {Data}", log);
break;
case "INFO":
Log.Information("[Datadog Serverless Mini Agent] {Data}", log);
break;
case "DEBUG":
Log.Debug("[Datadog Serverless Mini Agent] {Data}", log);
break;
default:
Log.Information("[Datadog Serverless Mini Agent] {Data}", data);
break;
}
}

// Processes a raw log from the mini agent, modifying two "out" parameters level and log.
// For example, given this raw log:
// [2023-06-06T01:31:30Z DEBUG datadog_trace_mini_agent::mini_agent] Random log
// level and log will be the following values:
// level == "DEBUG", log == "Random log"
internal static void ProcessMiniAgentLog(string rawLog, out string level, out string log)
{
level = "INFO";
log = rawLog;

int logPrefixCutoff = rawLog.IndexOf("]");
if (logPrefixCutoff < 0 || logPrefixCutoff == rawLog.Length - 1)
{
return;
}

int levelLeftBound = rawLog.IndexOf(" ");
if (levelLeftBound < 0)
{
return;
}

int levelRightBound = rawLog.IndexOf(" ", levelLeftBound + 1);
if (levelRightBound < 0 || levelRightBound - levelLeftBound < 1)
{
return;
}

string parsedLevel = rawLog.Substring(levelLeftBound + 1, levelRightBound - levelLeftBound - 1);

if (!(parsedLevel is "ERROR" or "WARN" or "INFO" or "DEBUG"))
{
return;
}

level = parsedLevel;
log = rawLog.Substring(logPrefixCutoff + 2);

if (level is "DEBUG")
{
level = "INFO";
log = $"[DEBUG] {log}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ internal class AzureAppService
/// Used to force the loader to start dogstatsd (in case automatic instrumentation is disabled)
/// </summary>
public const string AasEnableCustomMetrics = "DD_AAS_ENABLE_CUSTOM_METRICS";

/// <summary>
/// Used to identify consumption plan functions. Consumption plans will either not have this variable,
/// or will have a value of "dynamic".
/// </summary>
public const string WebsiteSKU = "WEBSITE_SKU";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, please make sure you add all these new configuration keys to the intake allow list 🙂

You can create a PR for the changes to this file:

https://github.com/DataDog/dd-go/blob/prod/trace/apps/tracer-telemetry-intake/telemetry-payload/static/config_norm_rules.json

and then tag apm-dotnet and instrumentation-telemetry channels for review 🙂 Thanks!

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// <copyright file="ConfigurationKeys.GCPFunction.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

namespace Datadog.Trace.Configuration
{
internal partial class ConfigurationKeys
{
// GCP Function auto-set env var reference: https://cloud.google.com/functions/docs/configuring/env-var#runtime_environment_variables_set_automatically
internal class GCPFunction
{
/// <summary>
/// The name of functions running deprecated runtimes.
/// </summary>
internal const string DeprecatedFunctionNameKey = "FUNCTION_NAME";

/// <summary>
/// The name of the gcp project a deprecated function belongs to. Only set in functions running deprecated runtimes.
/// Used to tell whether or not we are in a deprecated function environment.
/// </summary>
internal const string DeprecatedProjectKey = "GCP_PROJECT";

/// <summary>
/// The name of functions running non-deprecated runtimes.
/// </summary>
internal const string FunctionNameKey = "K_SERVICE";

/// <summary>
/// The name of the function handler to be executed when the function is invoked. Only set in functions running non-deprecated runtimes.
/// Used to tell whether or not we are in a non-deprecated function environment.
/// </summary>
internal const string FunctionTargetKey = "FUNCTION_TARGET";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public ImmutableAzureAppServiceSettings(IConfigurationSource? source, IConfigura
FunctionsWorkerRuntime = config.WithKeys(ConfigurationKeys.AzureAppService.FunctionsWorkerRuntimeKey).AsString();
FunctionsExtensionVersion = config.WithKeys(ConfigurationKeys.AzureAppService.FunctionsExtensionVersionKey).AsString();

WebsiteSKU = config.WithKeys(ConfigurationKeys.AzureAppService.WebsiteSKU).AsString();

if (FunctionsWorkerRuntime is not null || FunctionsExtensionVersion is not null)
{
AzureContext = AzureContext.AzureFunctions;
Expand All @@ -68,6 +70,7 @@ public ImmutableAzureAppServiceSettings(IConfigurationSource? source, IConfigura
SiteKind = "functionapp";
SiteType = "function";
IsFunctionsApp = true;
IsFunctionsAppConsumptionPlan = GetIsFunctionsAppConsumptionPlan(source, telemetry);
IsIsolatedFunctionsApp = FunctionsWorkerRuntime?.EndsWith("-isolated", StringComparison.OrdinalIgnoreCase) == true;
PlatformStrategy.ShouldSkipClientSpan = ShouldSkipClientSpanWithinFunctions;
break;
Expand Down Expand Up @@ -115,6 +118,10 @@ public ImmutableAzureAppServiceSettings(IConfigurationSource? source, IConfigura

public bool IsFunctionsApp { get; private set; }

public bool IsFunctionsAppConsumptionPlan { get; }

public string? WebsiteSKU { get; }

public string? FunctionsExtensionVersion { get; }

public string? FunctionsWorkerRuntime { get; }
Expand Down Expand Up @@ -183,5 +190,17 @@ private static bool ShouldSkipClientSpanWithinFunctions(Scope? scope)

return null;
}

public static bool GetIsFunctionsAppConsumptionPlan(IConfigurationSource source, IConfigurationTelemetry telemetry)
{
var config = new ConfigurationBuilder(source, telemetry);
var websiteSKU = config.WithKeys(ConfigurationKeys.AzureAppService.WebsiteSKU).AsString();

var functionsExtensionVersion = config.WithKeys(ConfigurationKeys.AzureAppService.FunctionsExtensionVersionKey).AsString();
var functionsWorkerRuntime = config.WithKeys(ConfigurationKeys.AzureAppService.FunctionsWorkerRuntimeKey).AsString();
var isFunctionApp = functionsExtensionVersion is not null || functionsWorkerRuntime is not null;

return websiteSKU is "Dynamic" or null && isFunctionApp;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// <copyright file="ImmutableGCPFunctionSettings.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using Datadog.Trace.Configuration.Telemetry;

namespace Datadog.Trace.Configuration
{
/// <summary>
/// Settings class for gathering metadata about the execution context in Google Cloud Functions
/// References:
/// https://cloud.google.com/functions/docs/configuring/env-var#runtime_environment_variables_set_automatically
/// </summary>
internal class ImmutableGCPFunctionSettings
Comment thread
thedavl marked this conversation as resolved.
{
public ImmutableGCPFunctionSettings(IConfigurationSource? source, IConfigurationTelemetry telemetry)
{
source ??= NullConfigurationSource.Instance;
var config = new ConfigurationBuilder(source, telemetry);

var deprecatedFunctionKey = config.WithKeys(ConfigurationKeys.GCPFunction.DeprecatedFunctionNameKey).AsString();
var deprecatedProjectKey = config.WithKeys(ConfigurationKeys.GCPFunction.DeprecatedProjectKey).AsString();
IsDeprecatedFunction = deprecatedFunctionKey != null && deprecatedProjectKey != null;

var functionNameKey = config.WithKeys(ConfigurationKeys.GCPFunction.FunctionNameKey).AsString();
var functionTargetKey = config.WithKeys(ConfigurationKeys.GCPFunction.FunctionTargetKey).AsString();
IsNewerFunction = functionNameKey != null && functionTargetKey != null;

IsGCPFunction = IsDeprecatedFunction || IsNewerFunction;
}

public bool IsGCPFunction { get; }

public bool IsDeprecatedFunction { get; }

public bool IsNewerFunction { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
IsRunningInAzureAppService = settings.IsRunningInAzureAppService;
AzureAppServiceMetadata = settings.AzureAppServiceMetadata;

IsRunningInAzureFunctionsConsumptionPlan = settings.IsRunningInAzureFunctionsConsumptionPlan;

IsRunningInGCPFunctions = settings.IsRunningInGCPFunctions;

TraceId128BitGenerationEnabled = settings.TraceId128BitGenerationEnabled;
TraceId128BitLoggingEnabled = settings.TraceId128BitLoggingEnabled;

Expand Down Expand Up @@ -492,6 +496,17 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
/// </summary>
internal bool IsRunningInAzureAppService { get; }

/// <summary>
/// Gets a value indicating whether the tracer is running in Azure Functions
/// on a consumption plan
/// </summary>
internal bool IsRunningInAzureFunctionsConsumptionPlan { get; }

/// <summary>
/// Gets a value indicating whether the tracer is running in Google Cloud Functions
/// </summary>
internal bool IsRunningInGCPFunctions { get; }

/// <summary>
/// Gets a value indicating whether the tracer should propagate service data in db queries
/// </summary>
Expand All @@ -515,6 +530,11 @@ internal ImmutableTracerSettings(TracerSettings settings, bool unusedParamNotToU
/// </summary>
internal ImmutableAzureAppServiceSettings? AzureAppServiceMetadata { get; }

/// <summary>
/// Gets the GCP Function settings
/// </summary>
internal ImmutableGCPFunctionSettings? GCPFunctionSettings { get; }

/// <summary>
/// Gets a value indicating whether to calculate the peer.service tag from predefined precursor attributes when using the v0 schema.
/// </summary>
Expand Down
Loading