Skip to content

Commit 008b546

Browse files
authored
Adding an API to get worker process info (#9041)
1 parent 4bd6ac5 commit 008b546

File tree

13 files changed

+169
-7
lines changed

13 files changed

+169
-7
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ internal GrpcWorkerChannel(
139139

140140
public IWorkerProcess WorkerProcess => _rpcWorkerProcess;
141141

142-
internal RpcWorkerConfig Config => _workerConfig;
142+
public RpcWorkerConfig WorkerConfig => _workerConfig;
143143

144144
private void ProcessItem(InboundGrpcEvent msg)
145145
{
@@ -360,6 +360,10 @@ internal void FunctionEnvironmentReloadResponse(FunctionEnvironmentReloadRespons
360360
_workerChannelLogger.LogDebug("Received FunctionEnvironmentReloadResponse from WorkerProcess with Pid: '{0}'", _rpcWorkerProcess.Id);
361361

362362
LogWorkerMetadata(res.WorkerMetadata);
363+
364+
_workerConfig.Description.DefaultRuntimeVersion = _workerConfig.Description.DefaultRuntimeVersion ?? res.WorkerMetadata.RuntimeVersion;
365+
_workerConfig.Description.DefaultRuntimeName = _workerConfig.Description.DefaultRuntimeName ?? res.WorkerMetadata.RuntimeName;
366+
363367
UpdateCapabilities(res.Capabilities);
364368
_cancelCapabilityEnabled ??= !string.IsNullOrEmpty(_workerCapabilities.GetCapabilityState(RpcWorkerConstants.HandlesInvocationCancelMessage));
365369

@@ -386,6 +390,9 @@ internal void WorkerInitResponse(GrpcEvent initEvent)
386390
// to do to track this: https://github.com/Azure/azure-functions-host/issues/9019
387391
LogWorkerMetadata(_initMessage.WorkerMetadata);
388392

393+
_workerConfig.Description.DefaultRuntimeVersion = _workerConfig.Description.DefaultRuntimeVersion ?? _initMessage.WorkerMetadata.RuntimeVersion;
394+
_workerConfig.Description.DefaultRuntimeName = _workerConfig.Description.DefaultRuntimeName ?? _initMessage.WorkerMetadata.RuntimeName;
395+
389396
if (_initMessage.Result.IsFailure(out Exception exc))
390397
{
391398
HandleWorkerInitError(exc);

src/WebJobs.Script.WebHost/Controllers/HostController.cs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Collections.ObjectModel;
77
using System.Diagnostics;
8+
using System.Linq;
89
using System.Net.Http;
910
using System.Threading;
1011
using System.Threading.Tasks;
@@ -21,6 +22,7 @@
2122
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
2223
using Microsoft.Azure.WebJobs.Script.WebHost.Models;
2324
using Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.Policies;
25+
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
2426
using Microsoft.Extensions.DependencyInjection;
2527
using Microsoft.Extensions.Logging;
2628
using Microsoft.Extensions.Options;
@@ -32,7 +34,7 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost.Controllers
3234
{
3335
/// <summary>
3436
/// Controller responsible for handling all administrative requests for host operations
35-
/// example host status, ping, log, etc
37+
/// example host status, ping, log, etc.
3638
/// </summary>
3739
public class HostController : Controller
3840
{
@@ -107,6 +109,77 @@ public async Task<IActionResult> GetHostStatus([FromServices] IScriptHostManager
107109
return Ok(status);
108110
}
109111

112+
/// <summary>
113+
/// Currently, anyone in Reader role can access this information.
114+
/// If this API is extended to include any secrets, it will need to be
115+
/// locked down to only Contributor roles.
116+
/// </summary>
117+
[HttpGet]
118+
[Route("admin/host/processes")]
119+
[Authorize(Policy = PolicyNames.AdminAuthLevelOrInternal)]
120+
public async Task<IActionResult> GetWorkerProcesses([FromServices] IScriptHostManager scriptHostManager)
121+
{
122+
if (!Utility.TryGetHostService(scriptHostManager, out IWebHostRpcWorkerChannelManager webHostLanguageWorkerChannelManager))
123+
{
124+
return StatusCode(StatusCodes.Status503ServiceUnavailable);
125+
}
126+
127+
var hostProcess = Process.GetCurrentProcess();
128+
List<FunctionProcesses.FunctionProcessInfo> processes = new()
129+
{
130+
new FunctionProcesses.FunctionProcessInfo()
131+
{
132+
ProcessId = hostProcess.Id,
133+
DebugEngine = RpcWorkerConstants.DotNetCoreDebugEngine,
134+
IsEligibleForOpenInBrowser = false,
135+
ProcessName = hostProcess.ProcessName
136+
}
137+
};
138+
139+
string workerRuntime = _environment.GetFunctionsWorkerRuntime();
140+
141+
List<IRpcWorkerChannel> channels = null;
142+
if (Utility.TryGetHostService(scriptHostManager, out IJobHostRpcWorkerChannelManager jobHostLanguageWorkerChannelManager))
143+
{
144+
channels = jobHostLanguageWorkerChannelManager.GetChannels(workerRuntime).ToList();
145+
}
146+
147+
var webhostChannelDictionary = webHostLanguageWorkerChannelManager.GetChannels(workerRuntime);
148+
149+
List<Task<IRpcWorkerChannel>> webHostchannelTasks = new List<Task<IRpcWorkerChannel>>();
150+
if (webhostChannelDictionary is not null)
151+
{
152+
foreach (var pair in webhostChannelDictionary)
153+
{
154+
var workerChannel = pair.Value.Task;
155+
webHostchannelTasks.Add(workerChannel);
156+
}
157+
}
158+
159+
var webHostchannels = await Task.WhenAll(webHostchannelTasks);
160+
channels = channels ?? new List<IRpcWorkerChannel>();
161+
channels.AddRange(webHostchannels);
162+
163+
foreach (var channel in channels)
164+
{
165+
var processInfo = new FunctionProcesses.FunctionProcessInfo()
166+
{
167+
ProcessId = channel.WorkerProcess.Process.Id,
168+
ProcessName = channel.WorkerProcess.Process.ProcessName,
169+
DebugEngine = Utility.GetDebugEngineInfo(channel.WorkerConfig, workerRuntime),
170+
IsEligibleForOpenInBrowser = false
171+
};
172+
processes.Add(processInfo);
173+
}
174+
175+
var functionProcesses = new FunctionProcesses()
176+
{
177+
Processes = processes
178+
};
179+
180+
return Ok(functionProcesses);
181+
}
182+
110183
[HttpPost]
111184
[Route("admin/host/drain")]
112185
[Authorize(Policy = PolicyNames.AdminAuthLevelOrInternal)]
@@ -262,7 +335,7 @@ public async Task<IActionResult> GetScaleStatus([FromBody] ScaleStatusContext co
262335
[HttpPost]
263336
[Route("admin/host/log")]
264337
[Authorize(Policy = PolicyNames.AdminAuthLevelOrInternal)]
265-
public IActionResult Log([FromBody]IEnumerable<HostLogEntry> logEntries)
338+
public IActionResult Log([FromBody] IEnumerable<HostLogEntry> logEntries)
266339
{
267340
if (logEntries == null)
268341
{
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.ObjectModel;
7+
using Microsoft.Azure.WebJobs.Script.Models;
8+
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Converters;
10+
11+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Models
12+
{
13+
public class FunctionProcesses
14+
{
15+
public IEnumerable<FunctionProcessInfo> Processes { get; set; }
16+
17+
public class FunctionProcessInfo
18+
{
19+
/// <summary>
20+
/// Gets or sets the worker process id.
21+
/// </summary>
22+
[JsonProperty(PropertyName = "processId", DefaultValueHandling = DefaultValueHandling.Ignore)]
23+
public int ProcessId { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the name of the process.
27+
/// </summary>
28+
[JsonProperty(PropertyName = "processName", DefaultValueHandling = DefaultValueHandling.Ignore)]
29+
public string ProcessName { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets the debug engine string associated with the worker process.
33+
/// </summary>
34+
[JsonProperty(PropertyName = "debugEngine", DefaultValueHandling = DefaultValueHandling.Ignore)]
35+
public string DebugEngine { get; set; }
36+
37+
/// <summary>
38+
/// Gets or sets a value indicating whether the worker process is eligible to be opened in browser.
39+
/// </summary>
40+
[JsonProperty(PropertyName = "isEligibleForOpenInBrowser", DefaultValueHandling = DefaultValueHandling.Include)]
41+
public bool IsEligibleForOpenInBrowser { get; set; }
42+
}
43+
}
44+
}

src/WebJobs.Script/Utility.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,23 @@ internal static Attribute GetHierarchicalAttributeOrNull(MethodInfo method, Type
101101
return null;
102102
}
103103

104+
internal static string GetDebugEngineInfo(RpcWorkerConfig workerConfig, string runtime)
105+
{
106+
if (runtime.Equals(RpcWorkerConstants.DotNetIsolatedLanguageWorkerName, StringComparison.CurrentCultureIgnoreCase))
107+
{
108+
if (workerConfig.Description.DefaultRuntimeName.Contains(RpcWorkerConstants.DotNetFramework))
109+
{
110+
return RpcWorkerConstants.DotNetFrameworkDebugEngine;
111+
}
112+
else
113+
{
114+
return RpcWorkerConstants.DotNetCoreDebugEngine;
115+
}
116+
}
117+
118+
return runtime;
119+
}
120+
104121
internal static async Task InvokeWithRetriesAsync(Action action, int maxRetries, TimeSpan retryInterval)
105122
{
106123
await InvokeWithRetriesAsync(() =>

src/WebJobs.Script/Workers/IWorkerChannel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ public interface IWorkerChannel
1717

1818
Task StartWorkerProcessAsync(CancellationToken cancellationToken = default);
1919
}
20-
}
20+
}

src/WebJobs.Script/Workers/Rpc/IRpcWorkerChannel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace Microsoft.Azure.WebJobs.Script.Workers.Rpc
1212
{
1313
public interface IRpcWorkerChannel : IWorkerChannel
1414
{
15+
RpcWorkerConfig WorkerConfig { get; }
16+
1517
IDictionary<string, BufferBlock<ScriptInvocationContext>> FunctionInputBuffers { get; }
1618

1719
bool IsChannelReadyForInvocations();

src/WebJobs.Script/Workers/Rpc/RpcWorkerConstants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,8 @@ public static class RpcWorkerConstants
7777

7878
// Language worker warmup
7979
public const string WorkerWarmupEnabled = "WORKER_WARMUP_ENABLED";
80+
public const string DotNetCoreDebugEngine = ".NETCore";
81+
public const string DotNetFrameworkDebugEngine = ".NETFramework";
82+
public const string DotNetFramework = "Framework";
8083
}
8184
}

src/WebJobs.Script/Workers/Rpc/RpcWorkerDescription.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ public class RpcWorkerDescription : WorkerDescription
2323
[JsonProperty(PropertyName = "language")]
2424
public string Language { get; set; }
2525

26+
/// <summary>
27+
/// Gets or sets the default runtime Name.
28+
/// </summary>
29+
[JsonProperty(PropertyName = "defaultRuntimeName")]
30+
public string DefaultRuntimeName { get; set; }
31+
2632
/// <summary>
2733
/// Gets or sets the default runtime version.
2834
/// </summary>

src/WebJobs.Script/Workers/WorkerStatus.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class WorkerStatus
2424
public IEnumerable<TimeSpan> LatencyHistory { get; set; }
2525

2626
/// <summary>
27-
/// Gets or sets a value indicating whether worker is ready
27+
/// Gets or sets a value indicating whether worker is ready.
2828
/// </summary>
2929
public bool IsReady { get; set; }
3030
}

test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/SpecializationE2ETests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public async Task StartAsync_SetsCorrectActiveHost_RefreshesLanguageWorkerOption
331331
var workerOptionsPlaceholderMode = host.Services.GetService<IOptions<LanguageWorkerOptions>>();
332332
Assert.Equal(4, workerOptionsPlaceholderMode.Value.WorkerConfigs.Count);
333333
var rpcChannelInPlaceholderMode = (GrpcWorkerChannel)channelFactory.Create("/", "powershell", null, 0, workerOptionsPlaceholderMode.Value.WorkerConfigs);
334-
Assert.Equal(expectedPowerShellVersion, rpcChannelInPlaceholderMode.Config.Description.DefaultRuntimeVersion);
334+
Assert.Equal(expectedPowerShellVersion, rpcChannelInPlaceholderMode.WorkerConfig.Description.DefaultRuntimeVersion);
335335

336336

337337
// TestServer will block in the constructor so pull out the StandbyManager and use it
@@ -359,7 +359,7 @@ public async Task StartAsync_SetsCorrectActiveHost_RefreshesLanguageWorkerOption
359359
var workerOptionsAtJobhostLevel = scriptHostService.Services.GetService<IOptions<LanguageWorkerOptions>>();
360360
Assert.Equal(1, workerOptionsAtJobhostLevel.Value.WorkerConfigs.Count);
361361
var rpcChannelAfterSpecialization = (GrpcWorkerChannel)channelFactory.Create("/", "powershell", null, 0, workerOptionsAtJobhostLevel.Value.WorkerConfigs);
362-
Assert.Equal(expectedPowerShellVersion, rpcChannelAfterSpecialization.Config.Description.DefaultRuntimeVersion);
362+
Assert.Equal(expectedPowerShellVersion, rpcChannelAfterSpecialization.WorkerConfig.Description.DefaultRuntimeVersion);
363363
}
364364

365365
/// <summary>

0 commit comments

Comments
 (0)