1010using System . Linq ;
1111using System . Reactive . Linq ;
1212using System . Text ;
13+ using System . Threading ;
1314using System . Threading . Tasks ;
1415using System . Threading . Tasks . Dataflow ;
1516using Microsoft . Azure . WebJobs . Script . Description ;
@@ -40,6 +41,8 @@ internal class GrpcWorkerChannel : IRpcWorkerChannel, IDisposable
4041 private readonly IEnvironment _environment ;
4142 private readonly IOptionsMonitor < ScriptApplicationHostOptions > _applicationHostOptions ;
4243 private readonly ISharedMemoryManager _sharedMemoryManager ;
44+ private readonly List < TimeSpan > _workerStatusLatencyHistory = new List < TimeSpan > ( ) ;
45+ private readonly IOptions < WorkerConcurrencyOptions > _workerConcurrencyOptions ;
4346
4447 private IDisposable _functionLoadRequestResponseEvent ;
4548 private bool _disposed ;
@@ -68,6 +71,9 @@ internal class GrpcWorkerChannel : IRpcWorkerChannel, IDisposable
6871 private TimeSpan _functionLoadTimeout = TimeSpan . FromMinutes ( 10 ) ;
6972 private bool _isSharedMemoryDataTransferEnabled ;
7073
74+ private object _syncLock = new object ( ) ;
75+ private System . Timers . Timer _timer ;
76+
7177 internal GrpcWorkerChannel (
7278 string workerId ,
7379 IScriptEventManager eventManager ,
@@ -79,7 +85,8 @@ internal GrpcWorkerChannel(
7985 IEnvironment environment ,
8086 IOptionsMonitor < ScriptApplicationHostOptions > applicationHostOptions ,
8187 ISharedMemoryManager sharedMemoryManager ,
82- IFunctionDataCache functionDataCache )
88+ IFunctionDataCache functionDataCache ,
89+ IOptions < WorkerConcurrencyOptions > workerConcurrencyOptions )
8390 {
8491 _workerId = workerId ;
8592 _eventManager = eventManager ;
@@ -91,6 +98,7 @@ internal GrpcWorkerChannel(
9198 _environment = environment ;
9299 _applicationHostOptions = applicationHostOptions ;
93100 _sharedMemoryManager = sharedMemoryManager ;
101+ _workerConcurrencyOptions = workerConcurrencyOptions ;
94102
95103 _workerCapabilities = new GrpcCapabilities ( _workerChannelLogger ) ;
96104
@@ -134,7 +142,7 @@ public bool IsChannelReadyForInvocations()
134142 return ! _disposing && ! _disposed && _state . HasFlag ( RpcWorkerChannelState . InvocationBuffersInitialized | RpcWorkerChannelState . Initialized ) ;
135143 }
136144
137- public async Task StartWorkerProcessAsync ( )
145+ public async Task StartWorkerProcessAsync ( CancellationToken cancellationToken )
138146 {
139147 _startSubscription = _inboundWorkerEvents . Where ( msg => msg . MessageType == MsgType . StartStream )
140148 . Timeout ( _workerConfig . CountOptions . ProcessStartupTimeout )
@@ -174,6 +182,12 @@ public async Task<WorkerStatus> GetWorkerStatusAsync()
174182 }
175183 }
176184
185+ workerStatus . IsReady = IsChannelReadyForInvocations ( ) ;
186+ if ( _environment . IsWorkerDynamicConcurrencyEnabled ( ) )
187+ {
188+ workerStatus . LatencyHistory = GetLatencies ( ) ;
189+ }
190+
177191 return workerStatus ;
178192 }
179193
@@ -750,6 +764,7 @@ protected virtual void Dispose(bool disposing)
750764 {
751765 _startLatencyMetric ? . Dispose ( ) ;
752766 _startSubscription ? . Dispose ( ) ;
767+ _timer ? . Dispose ( ) ;
753768
754769 // unlink function inputs
755770 foreach ( var link in _inputLinks )
@@ -843,5 +858,64 @@ internal bool IsSharedMemoryDataTransferEnabled()
843858 _workerChannelLogger . LogDebug ( "IsSharedMemoryDataTransferEnabled: {SharedMemoryDataTransferEnabled}" , capabilityEnabled ) ;
844859 return capabilityEnabled ;
845860 }
861+
862+ internal void EnsureTimerStarted ( )
863+ {
864+ if ( _environment . IsWorkerDynamicConcurrencyEnabled ( ) )
865+ {
866+ lock ( _syncLock )
867+ {
868+ if ( _timer == null )
869+ {
870+ _timer = new System . Timers . Timer ( )
871+ {
872+ AutoReset = false ,
873+ Interval = _workerConcurrencyOptions . Value . CheckInterval . TotalMilliseconds ,
874+ } ;
875+
876+ _timer . Elapsed += OnTimer ;
877+ _timer . Start ( ) ;
878+ }
879+ }
880+ }
881+ }
882+
883+ internal IEnumerable < TimeSpan > GetLatencies ( )
884+ {
885+ EnsureTimerStarted ( ) ;
886+ return _workerStatusLatencyHistory ;
887+ }
888+
889+ internal async void OnTimer ( object sender , System . Timers . ElapsedEventArgs e )
890+ {
891+ if ( _disposed )
892+ {
893+ return ;
894+ }
895+
896+ try
897+ {
898+ WorkerStatus workerStatus = await GetWorkerStatusAsync ( ) ;
899+ AddSample ( _workerStatusLatencyHistory , workerStatus . Latency ) ;
900+ }
901+ catch
902+ {
903+ // Don't allow background execptions to escape
904+ // E.g. when a rpc channel is shutting down we can process exceptions
905+ }
906+ _timer . Start ( ) ;
907+ }
908+
909+ private void AddSample < T > ( List < T > samples , T sample )
910+ {
911+ lock ( _syncLock )
912+ {
913+ if ( samples . Count == _workerConcurrencyOptions . Value . HistorySize )
914+ {
915+ samples . RemoveAt ( 0 ) ;
916+ }
917+ samples . Add ( sample ) ;
918+ }
919+ }
846920 }
847921}
0 commit comments