diff --git a/src/WebJobs.Script.WebHost/Diagnostics/WebHostMetricsLogger.cs b/src/WebJobs.Script.WebHost/Diagnostics/WebHostMetricsLogger.cs new file mode 100644 index 0000000000..011aae9052 --- /dev/null +++ b/src/WebJobs.Script.WebHost/Diagnostics/WebHostMetricsLogger.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Azure.WebJobs.Script.Diagnostics; + +namespace WebJobs.Script.WebHost.Diagnostics +{ + public class WebHostMetricsLogger : IMetricsLogger + { + public void BeginEvent(MetricEvent metricEvent) + { + // TODO + FunctionStartedEvent startedEvent = metricEvent as FunctionStartedEvent; + if (startedEvent != null) + { + startedEvent.StartTime = DateTime.Now; + } + } + + public void EndEvent(MetricEvent metricEvent) + { + // TODO + FunctionStartedEvent startedEvent = metricEvent as FunctionStartedEvent; + if (startedEvent != null) + { + startedEvent.EndTime = DateTime.Now; + } + } + } +} \ No newline at end of file diff --git a/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj b/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj index 3a6e11805b..69204244ce 100644 --- a/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj +++ b/src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj @@ -237,6 +237,7 @@ + diff --git a/src/WebJobs.Script.WebHost/WebScriptHostManager.cs b/src/WebJobs.Script.WebHost/WebScriptHostManager.cs index 6926ee775d..a7e2886098 100644 --- a/src/WebJobs.Script.WebHost/WebScriptHostManager.cs +++ b/src/WebJobs.Script.WebHost/WebScriptHostManager.cs @@ -8,8 +8,11 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Script; using Microsoft.Azure.WebJobs.Script.Description; +using Microsoft.Azure.WebJobs.Script.Diagnostics; +using WebJobs.Script.WebHost.Diagnostics; namespace WebJobs.Script.WebHost { @@ -74,6 +77,14 @@ public FunctionDescriptor GetHttpFunctionOrNull(Uri uri) return function; } + protected override void OnInitializeConfig(JobHostConfiguration config) + { + base.OnInitializeConfig(config); + + // Add our WebHost specific services + config.AddService(new WebHostMetricsLogger()); + } + protected override void OnHostStarted() { base.OnHostStarted(); diff --git a/src/WebJobs.Script/Description/CSharp/CSharpFunctionInvoker.cs b/src/WebJobs.Script/Description/CSharp/CSharpFunctionInvoker.cs index b3982c6939..5c9056e86d 100644 --- a/src/WebJobs.Script/Description/CSharp/CSharpFunctionInvoker.cs +++ b/src/WebJobs.Script/Description/CSharp/CSharpFunctionInvoker.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.Script.Binding; +using Microsoft.Azure.WebJobs.Script.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Scripting; @@ -31,6 +32,7 @@ public class CSharpFunctionInvoker : ScriptFunctionInvokerBase private readonly Collection _inputBindings; private readonly Collection _outputBindings; private readonly IFunctionEntryPointResolver _functionEntryPointResolver; + private readonly IMetricsLogger _metrics; private MethodInfo _function; private CSharpFunctionSignature _functionSignature; @@ -53,6 +55,7 @@ internal CSharpFunctionInvoker(ScriptHost host, FunctionMetadata functionMetadat _inputBindings = inputBindings; _outputBindings = outputBindings; _triggerInputName = GetTriggerInputName(functionMetadata); + _metrics = host.ScriptConfig.HostConfig.GetService(); InitializeFileWatcherIfEnabled(); _resultProcessor = CreateResultProcessor(); @@ -150,6 +153,9 @@ private void RestorePackages() public override async Task Invoke(object[] parameters) { + FunctionStartedEvent startedEvent = new FunctionStartedEvent(Metadata); + _metrics.BeginEvent(startedEvent); + try { TraceWriter.Verbose("Function started"); @@ -175,9 +181,17 @@ public override async Task Invoke(object[] parameters) catch (Exception ex) { TraceWriter.Error(ex.Message, ex is CompilationErrorException ? null : ex); + + startedEvent.Success = false; + TraceWriter.Error(ex.Message, ex); + TraceWriter.Verbose("Function completed (Failure)"); throw; } + finally + { + _metrics.EndEvent(startedEvent); + } } private object[] ProcessInputParameters(object[] parameters) diff --git a/src/WebJobs.Script/Description/NodeFunctionInvoker.cs b/src/WebJobs.Script/Description/NodeFunctionInvoker.cs index dea43b400c..d941d94868 100644 --- a/src/WebJobs.Script/Description/NodeFunctionInvoker.cs +++ b/src/WebJobs.Script/Description/NodeFunctionInvoker.cs @@ -16,6 +16,7 @@ using EdgeJs; using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.Script.Binding; +using Microsoft.Azure.WebJobs.Script.Diagnostics; using Newtonsoft.Json; namespace Microsoft.Azure.WebJobs.Script.Description @@ -28,6 +29,7 @@ public class NodeFunctionInvoker : ScriptFunctionInvokerBase private readonly string _script; private readonly DictionaryJsonConverter _dictionaryJsonConverter = new DictionaryJsonConverter(); private readonly BindingMetadata _trigger; + private readonly IMetricsLogger _metrics; private Func> _scriptFunc; private Func> _clearRequireCache; @@ -55,6 +57,7 @@ internal NodeFunctionInvoker(ScriptHost host, BindingMetadata trigger, FunctionM _script = string.Format(CultureInfo.InvariantCulture, _functionTemplate, scriptFilePath); _inputBindings = inputBindings; _outputBindings = outputBindings; + _metrics = host.ScriptConfig.HostConfig.GetService(); InitializeFileWatcherIfEnabled(); } @@ -93,6 +96,9 @@ public override async Task Invoke(object[] parameters) IBinder binder = (IBinder)parameters[2]; ExecutionContext functionExecutionContext = (ExecutionContext)parameters[3]; + FunctionStartedEvent startedEvent = new FunctionStartedEvent(Metadata); + _metrics.BeginEvent(startedEvent); + try { TraceWriter.Verbose(string.Format("Function started")); @@ -113,10 +119,15 @@ public override async Task Invoke(object[] parameters) } catch (Exception ex) { + startedEvent.Success = false; TraceWriter.Error(ex.Message, ex); TraceWriter.Verbose(string.Format("Function completed (Failure)")); throw; } + finally + { + _metrics.EndEvent(startedEvent); + } } private async Task ProcessInputBindingsAsync(IBinder binder, Dictionary executionContext, Dictionary bindingData) diff --git a/src/WebJobs.Script/Description/ScriptFunctionInvoker.cs b/src/WebJobs.Script/Description/ScriptFunctionInvoker.cs index a4b095b810..1de75fff0d 100644 --- a/src/WebJobs.Script/Description/ScriptFunctionInvoker.cs +++ b/src/WebJobs.Script/Description/ScriptFunctionInvoker.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.Script.Binding; +using Microsoft.Azure.WebJobs.Script.Diagnostics; namespace Microsoft.Azure.WebJobs.Script.Description { @@ -23,6 +24,8 @@ public class ScriptFunctionInvoker : ScriptFunctionInvokerBase private static string[] _supportedScriptTypes = new string[] { "ps1", "cmd", "bat", "py", "php", "sh", "fsx" }; private readonly string _scriptFilePath; private readonly string _scriptType; + private readonly IMetricsLogger _metrics; + private readonly Collection _inputBindings; private readonly Collection _outputBindings; @@ -33,6 +36,7 @@ internal ScriptFunctionInvoker(string scriptFilePath, ScriptHost host, FunctionM _scriptType = Path.GetExtension(_scriptFilePath).ToLower(CultureInfo.InvariantCulture).TrimStart('.'); _inputBindings = inputBindings; _outputBindings = outputBindings; + _metrics = host.ScriptConfig.HostConfig.GetService(); } public static bool IsSupportedScriptType(string extension) @@ -87,6 +91,9 @@ internal async Task ExecuteScriptAsync(string path, string arguments, object[] i IBinder binder = (IBinder)invocationParameters[2]; ExecutionContext functionExecutionContext = (ExecutionContext)invocationParameters[3]; + FunctionStartedEvent startedEvent = new FunctionStartedEvent(Metadata); + _metrics.BeginEvent(startedEvent); + // perform any required input conversions string stdin = null; if (input != null) @@ -131,8 +138,14 @@ internal async Task ExecuteScriptAsync(string path, string arguments, object[] i } process.WaitForExit(); - if (process.ExitCode != 0) + bool failed = process.ExitCode != 0; + startedEvent.Success = !failed; + _metrics.EndEvent(startedEvent); + + if (failed) { + startedEvent.Success = false; + string error = process.StandardError.ReadToEnd(); TraceWriter.Error(error); TraceWriter.Verbose(string.Format("Function completed (Failure)")); diff --git a/src/WebJobs.Script/Diagnostics/FunctionStartedEvent.cs b/src/WebJobs.Script/Diagnostics/FunctionStartedEvent.cs new file mode 100644 index 0000000000..dc361de302 --- /dev/null +++ b/src/WebJobs.Script/Diagnostics/FunctionStartedEvent.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Azure.WebJobs.Script.Description; + +namespace Microsoft.Azure.WebJobs.Script.Diagnostics +{ + public class FunctionStartedEvent : MetricEvent + { + public FunctionStartedEvent(FunctionMetadata functionMetadata) + { + FunctionMetadata = functionMetadata; + Success = true; + } + + public FunctionMetadata FunctionMetadata { get; private set; } + + public bool Success { get; set; } + } +} diff --git a/src/WebJobs.Script/Diagnostics/IMetricsLogger.cs b/src/WebJobs.Script/Diagnostics/IMetricsLogger.cs new file mode 100644 index 0000000000..0bc09c3522 --- /dev/null +++ b/src/WebJobs.Script/Diagnostics/IMetricsLogger.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.WebJobs.Script.Diagnostics +{ + /// + /// Defines an interface for emitting metric events from the + /// script runtime for later aggregation and reporting. + /// + public interface IMetricsLogger + { + /// + /// Begins an event. + /// + /// The event. + void BeginEvent(MetricEvent metricEvent); + + /// + /// Completes a previously started event. + /// + /// A previously started event. + void EndEvent(MetricEvent metricEvent); + } +} diff --git a/src/WebJobs.Script/Diagnostics/MetricEvent.cs b/src/WebJobs.Script/Diagnostics/MetricEvent.cs new file mode 100644 index 0000000000..d9f0db7b2e --- /dev/null +++ b/src/WebJobs.Script/Diagnostics/MetricEvent.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Azure.WebJobs.Script.Diagnostics +{ + public abstract class MetricEvent + { + public DateTime StartTime { get; set; } + + public DateTime EndTime { get; set; } + } +} diff --git a/src/WebJobs.Script/Diagnostics/MetricsLogger.cs b/src/WebJobs.Script/Diagnostics/MetricsLogger.cs new file mode 100644 index 0000000000..f75e9ab87e --- /dev/null +++ b/src/WebJobs.Script/Diagnostics/MetricsLogger.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.WebJobs.Script.Diagnostics +{ + /// + /// Default implementation of that doesn't do any logging. + /// + public class MetricsLogger : IMetricsLogger + { + public void BeginEvent(MetricEvent metricEvent) + { + } + + public void EndEvent(MetricEvent metricEvent) + { + } + } +} diff --git a/src/WebJobs.Script/Host/ScriptHost.cs b/src/WebJobs.Script/Host/ScriptHost.cs index 5280173b55..af3f2e5a74 100644 --- a/src/WebJobs.Script/Host/ScriptHost.cs +++ b/src/WebJobs.Script/Host/ScriptHost.cs @@ -14,6 +14,7 @@ using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.Script.Config; using Microsoft.Azure.WebJobs.Script.Description; +using Microsoft.Azure.WebJobs.Script.Diagnostics; using Microsoft.Azure.WebJobs.ServiceBus; using Newtonsoft.Json.Linq; @@ -119,6 +120,12 @@ public AutoResetEvent RestartEvent protected virtual void Initialize() { + IMetricsLogger metricsLogger = ScriptConfig.HostConfig.GetService(); + if (metricsLogger == null) + { + ScriptConfig.HostConfig.AddService(new MetricsLogger()); + } + List descriptionProviders = new List() { new ScriptFunctionDescriptorProvider(this, ScriptConfig), diff --git a/src/WebJobs.Script/Host/ScriptHostManager.cs b/src/WebJobs.Script/Host/ScriptHostManager.cs index 7abf2454e8..eed54c35d4 100644 --- a/src/WebJobs.Script/Host/ScriptHostManager.cs +++ b/src/WebJobs.Script/Host/ScriptHostManager.cs @@ -74,6 +74,7 @@ public ScriptHost Instance { HostId = _config.HostConfig.HostId }; + OnInitializeConfig(_config.HostConfig); newInstance = _scriptHostFactory.Create(_config); _traceWriter = newInstance.TraceWriter; @@ -218,6 +219,10 @@ private ScriptHost[] GetLiveInstancesAndClear() return instances; } + protected virtual void OnInitializeConfig(JobHostConfiguration config) + { + } + protected virtual void OnHostStarted() { } diff --git a/src/WebJobs.Script/WebJobs.Script.csproj b/src/WebJobs.Script/WebJobs.Script.csproj index 0e1a094bae..69aa3f8625 100644 --- a/src/WebJobs.Script/WebJobs.Script.csproj +++ b/src/WebJobs.Script/WebJobs.Script.csproj @@ -235,6 +235,10 @@ + + + +