Skip to content

Commit 2aae5b9

Browse files
authored
[PlaceholderMode]Read RuntimeAssemblyFiles (#8335)
1 parent 0d6662c commit 2aae5b9

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

src/WebJobs.Script.WebHost/Middleware/HostWarmupMiddleware.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.IO;
6+
using System.Linq;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using Microsoft.AspNetCore.Http;
@@ -20,6 +21,7 @@ public class HostWarmupMiddleware
2021
private readonly IEnvironment _environment;
2122
private readonly IScriptHostManager _hostManager;
2223
private readonly ILogger _logger;
24+
private string _assemblyLocalPath;
2325

2426
public HostWarmupMiddleware(RequestDelegate next, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, IScriptHostManager hostManager, ILogger<HostWarmupMiddleware> logger)
2527
{
@@ -28,6 +30,7 @@ public HostWarmupMiddleware(RequestDelegate next, IScriptWebHostEnvironment webH
2830
_environment = environment;
2931
_hostManager = hostManager;
3032
_logger = logger;
33+
_assemblyLocalPath = Path.GetDirectoryName(new Uri(typeof(HostWarmupMiddleware).Assembly.Location).LocalPath);
3134
}
3235

3336
public async Task Invoke(HttpContext httpContext)
@@ -40,17 +43,62 @@ public async Task Invoke(HttpContext httpContext)
4043
PreJitPrepare(WarmUpConstants.LinuxJitTraceFileName);
4144
}
4245

46+
ReadRuntimeAssemblyFiles();
47+
4348
await WarmUp(httpContext.Request);
4449
}
4550

4651
await _next.Invoke(httpContext);
4752
}
4853

54+
internal void ReadRuntimeAssemblyFiles()
55+
{
56+
try
57+
{
58+
string[] allFiles = Directory.GetFiles(_assemblyLocalPath, "*.dll", SearchOption.TopDirectoryOnly);
59+
// Read File content in 4K chunks
60+
int maxBuffer = 4 * 1024;
61+
byte[] chunk = new byte[maxBuffer];
62+
Random random = new Random();
63+
foreach (string file in allFiles)
64+
{
65+
// Read file content to avoid disk reads during specialization. This is only to page-in bytes.
66+
ReadFileInChunks(file, chunk, maxBuffer, random);
67+
}
68+
_logger.LogDebug(new EventId(100, nameof(ReadRuntimeAssemblyFiles)), "Number of files read: '{allFilesCount}'. AssemblyLocalPath: '{assemblyLocalPath}' ", allFiles.Count(), _assemblyLocalPath);
69+
}
70+
catch (Exception ex)
71+
{
72+
_logger.LogError(new EventId(100, nameof(ReadRuntimeAssemblyFiles)), ex, "Reading ReadRuntimeAssemblyFiles failed. AssemblyLocalPath: '{assemblyLocalPath}'", _assemblyLocalPath);
73+
}
74+
}
75+
76+
private void ReadFileInChunks(string file, byte[] chunk, int maxBuffer, Random random)
77+
{
78+
try
79+
{
80+
using (FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
81+
{
82+
int bytesRead;
83+
while ((bytesRead = fileStream.Read(chunk, 0, maxBuffer)) != 0)
84+
{
85+
// Read one random byte for every 4K bytes - 4K is default OS page size. This will help avoid disk read during specialization
86+
// see for details on OS page buffering in Windows - https://docs.microsoft.com/en-us/windows/win32/fileio/file-buffering
87+
var randomByte = Convert.ToInt32(chunk[random.Next(0, bytesRead - 1)]);
88+
}
89+
}
90+
}
91+
catch (Exception ex)
92+
{
93+
_logger.LogError(new EventId(100, nameof(ReadFileInChunks)), ex, "Reading file '{file}' failed. AssemblyLocalPath: '{assemblyLocalPath}'", file, _assemblyLocalPath);
94+
}
95+
}
96+
4997
private void PreJitPrepare(string jitTraceFileName)
5098
{
5199
// This is to PreJIT all methods captured in coldstart.jittrace file to improve cold start time
52100
var path = Path.Combine(
53-
Path.GetDirectoryName(new Uri(typeof(HostWarmupMiddleware).Assembly.Location).LocalPath),
101+
_assemblyLocalPath,
54102
WarmUpConstants.PreJitFolderName, jitTraceFileName);
55103

56104
var file = new FileInfo(path);

test/WebJobs.Script.Tests/Middleware/HostWarmupMiddlewareTests.cs

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

44
using System;
5+
using System.Linq;
6+
using Microsoft.AspNetCore.Hosting;
57
using Microsoft.Azure.WebJobs.Script.WebHost;
8+
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.Extensions;
69
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
10+
using Microsoft.Extensions.Logging;
711
using Microsoft.WebJobs.Script.Tests;
812
using Moq;
913
using Xunit;
@@ -53,5 +57,21 @@ public void IsWarmUpRequest_ReturnsExpectedValue()
5357
Assert.True(environment.IsLinuxConsumption());
5458
Assert.True(HostWarmupMiddleware.IsWarmUpRequest(request, hostEnvironment.InStandbyMode, environment));
5559
}
60+
61+
[Fact]
62+
public void ReadRuntimeAssemblyFiles_VerifyLogs()
63+
{
64+
var environment = new TestEnvironment();
65+
var hostEnvironment = new ScriptWebHostEnvironment(environment);
66+
var testLoggerFactory = new LoggerFactory();
67+
TestLoggerProvider testLoggerProvider = new TestLoggerProvider();
68+
testLoggerFactory.AddProvider(testLoggerProvider);
69+
ILogger<HostWarmupMiddleware> testLogger = testLoggerFactory.CreateLogger<HostWarmupMiddleware>();
70+
HostWarmupMiddleware hostWarmupMiddleware = new HostWarmupMiddleware(null, new Mock<IScriptWebHostEnvironment>().Object, environment, new Mock<IScriptHostManager>().Object, testLogger);
71+
hostWarmupMiddleware.ReadRuntimeAssemblyFiles();
72+
// Assert
73+
var traces = testLoggerProvider.GetAllLogMessages();
74+
Assert.True(traces.Any(m => m.FormattedMessage.Contains("Number of files read:")));
75+
}
5676
}
5777
}

0 commit comments

Comments
 (0)