using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFESystem.Browser;
// Cold and warm full editor initialization, including real view text and paint.
// Editing checks are outside the timed interval. No pre-warming or host browser.
internal static class MonacoProfile
{
internal static void Run(string root, string label, bool profileRegex = false)
{
string output = Path.Combine(root, "build/MonacoInit", label + "-" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
Directory.CreateDirectory(output);
var inputs = new[] { "Source/XFESystem.Browser", "Source/XFESystem.Collections", "Tools/JintCleanupPatch", "assets/Browser/WebsiteCode", "assets/Browser/Monaco/0.52.2/Core" }
.SelectMany(directory => Directory.EnumerateFiles(Path.Combine(root, directory)))
.Where(path => Path.GetExtension(path) is ".cs" or ".js" or ".css" or ".json")
.ToDictionary(path => Path.GetRelativePath(root, path).Replace('\\', '/'), Hash);
const string shortSource = "using System;\nnamespace XFE;\npublic class Test { int value = 42; }";
string longSource = "using System;\nclass Example {\n" + string.Concat(Enumerable.Range(0, 256).Select(i => $" public int Value{i} => 42;\n")) + "}";
var cases = new List<object>();
bool passed = true;
foreach (var (kind, csharp) in new[] { ("ShortCSharp", shortSource), ("LongCSharp", longSource) })
for (int attempt = 1; attempt <= 5; attempt++)
{
using var canvas = new WindowsTextCanvas(Path.Combine(root, "assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf"), 796, 404);
var timer = Stopwatch.StartNew();
var trace = new List<object>();
var regex = profileRegex ? new MonacoRegexProfile() : null;
BrowserService browser = null!;
bool installed = false;
browser = new BrowserService { Trace = line =>
{
trace.Add(new { Ticks = timer.ElapsedTicks, Line = line.TrimEnd() });
if (regex is not null && !installed && line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN", StringComparison.Ordinal))
{ installed = true; regex.Install(browser); }
} };
browser.SetCanvas(canvas); browser.ResizeViewport(796, 404);
long allocated = GC.GetTotalAllocatedBytes();
int[] collections = Enumerable.Range(0, 3).Select(GC.CollectionCount).ToArray();
timer.Restart();
browser.NavigateEditor(csharp, "Test.cs");
double navigationMs = timer.Elapsed.TotalMilliseconds;
for (int frame = 1; frame <= 8; frame++) browser.AdvanceTime(frame * 20);
string proof = browser.ExecuteJavaScript("xfeMonacoEditor.render(true);JSON.stringify({text: Array.from(document.querySelectorAll('.view-lines .view-line')).map(e=>e.textContent.replace(/\\u00a0/g,' ')),language:xfeMonacoEditor.getModel().getLanguageId(),version:xfeMonacoEditor.getModel().getVersionId(),value:xfeMonacoEditor.getValue()})");
browser.Paint(canvas, new(0, 0, 796, 404), 0); browser.FramePresented();
double readyMs = timer.Elapsed.TotalMilliseconds;
long bytes = GC.GetTotalAllocatedBytes() - allocated;
var dom = typeof(BrowserService).GetField("dom", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(browser)!;
long Counter(object instance, string name) => (long?)instance.GetType().GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)?.GetValue(instance) ?? 0;
var workCounters = new { browser.LayoutPassCount, LayoutMilliseconds = Counter(browser, "LayoutTicks") * 1000.0 / Stopwatch.Frequency,
BudgetValidations = Counter(dom, "BudgetValidationCount"), BudgetNodeVisits = Counter(dom, "BudgetNodeVisits"),
BudgetMilliseconds = Counter(dom, "BudgetValidationTicks") * 1000.0 / Stopwatch.Frequency,
Collections = Enumerable.Range(0, 3).Select(i => GC.CollectionCount(i) - collections[i]).ToArray() };
using var parsed = JsonDocument.Parse(proof);
var data = parsed.RootElement;
string[] visible = data.GetProperty("text").EnumerateArray().Select(e => e.GetString()!.TrimEnd()).ToArray();
bool ready = data.GetProperty("value").GetString() == csharp && data.GetProperty("language").GetString() == "csharp" &&
visible.Length >= 3 && visible.SequenceEqual(csharp.Split('\n').Take(visible.Length));
string renderHash = HashText(browser.ExecuteJavaScript("JSON.stringify(Array.from(document.querySelectorAll('.view-lines .view-line')).map(e=>e.innerHTML))"));
string tokenWork = browser.ExecuteJavaScript("(function(){const model=xfeMonacoEditor.getModel(),value=model.getValue(),original=model.getValue;let reads=0;model.getValue=function(){reads++;return original.apply(this,arguments)};try{const lines=monaco.editor.tokenize(value,'csharp');return JSON.stringify({Lines:lines.length,FullModelReads:reads})}finally{model.getValue=original}})()");
browser.ExecuteJavaScript("xfeMonacoEditor.focus();xfeMonacoEditor.setPosition({lineNumber:1,column:1});'ready'");
browser.Key(new("keydown", "x", "KeyX", "x")); browser.Key(new("keyup", "x", "KeyX"));
bool edited = browser.ReadEditorText() == "x" + csharp;
canvas.SavePng(Path.Combine(output, kind + "-" + attempt + ".png"));
using var work = JsonDocument.Parse(tokenWork);
var row = new { Kind = kind, Attempt = attempt, Cold = kind == "ShortCSharp" && attempt == 1, Passed = ready && edited, NavigationMilliseconds = navigationMs,
FullReadyMilliseconds = readyMs, AllocatedBytes = bytes, browser.PackagedScriptCacheHits, browser.PackagedScriptPreparations,
RenderSha256 = renderHash, Proof = data.Clone(), TokenizationWork = work.RootElement.Clone(), Work = workCounters, Regex = regex?.Results(), Trace = trace };
passed &= row.Passed;
cases.Add(row);
Console.WriteLine(JsonSerializer.Serialize(new { row.Kind, row.Attempt, row.Passed, navigationMs, readyMs, bytes, renderHash, row.TokenizationWork, row.Work }));
browser.Release();
}
bool unchanged = inputs.All(pair => Hash(Path.Combine(root, pair.Key)) == pair.Value);
string result = Path.Combine(output, "Result.json");
File.WriteAllText(result, JsonSerializer.Serialize(new { Kind = "MonacoFullInitializationHostProfile", Passed = passed && unchanged,
HostOnly = true, NativeAcceptance = false, Frequency = Stopwatch.Frequency, Sources = inputs,
ProbeSha256 = Hash(ProbeSource()),
RuntimeJintSha256 = Hash(typeof(Jint.Engine).Assembly.Location),
RuntimePatches = new { FailedRegexSuffix = typeof(Jint.Engine).Assembly.GetType("XFESystem.JintFailedRegexSuffixV1") is not null },
Cases = cases }, new JsonSerializerOptions { WriteIndented = true }));
Console.WriteLine(result);
if (!passed || !unchanged) throw new IOException("The full Monaco initialization profile failed.");
}
private static string Hash(string path) => Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path)));
private static string ProbeSource([System.Runtime.CompilerServices.CallerFilePath] string source = "") => source;
private static string HashText(string value) => Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(value)));
}
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using XFESystem.Browser;
// Cold and warm full editor initialization, including real view text and paint.
// Editing checks are outside the timed interval. No pre-warming or host browser.
internal static class MonacoProfile
{
internal static void Run(string root, string label, bool profileRegex = false)
{
string output = Path.Combine(root, "build/MonacoInit", label + "-" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
Directory.CreateDirectory(output);
var inputs = new[] { "Source/XFESystem.Browser", "Source/XFESystem.Collections", "Tools/JintCleanupPatch", "assets/Browser/WebsiteCode", "assets/Browser/Monaco/0.52.2/Core" }
.SelectMany(directory => Directory.EnumerateFiles(Path.Combine(root, directory)))
.Where(path => Path.GetExtension(path) is ".cs" or ".js" or ".css" or ".json")
.ToDictionary(path => Path.GetRelativePath(root, path).Replace('\\', '/'), Hash);
const string shortSource = "using System;\nnamespace XFE;\npublic class Test { int value = 42; }";
string longSource = "using System;\nclass Example {\n" + string.Concat(Enumerable.Range(0, 256).Select(i => $" public int Value{i} => 42;\n")) + "}";
var cases = new List<object>();
bool passed = true;
foreach (var (kind, csharp) in new[] { ("ShortCSharp", shortSource), ("LongCSharp", longSource) })
for (int attempt = 1; attempt <= 5; attempt++)
{
using var canvas = new WindowsTextCanvas(Path.Combine(root, "assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf"), 796, 404);
var timer = Stopwatch.StartNew();
var trace = new List<object>();
var regex = profileRegex ? new MonacoRegexProfile() : null;
BrowserService browser = null!;
bool installed = false;
browser = new BrowserService { Trace = line =>
{
trace.Add(new { Ticks = timer.ElapsedTicks, Line = line.TrimEnd() });
if (regex is not null && !installed && line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN", StringComparison.Ordinal))
{ installed = true; regex.Install(browser); }
} };
browser.SetCanvas(canvas); browser.ResizeViewport(796, 404);
long allocated = GC.GetTotalAllocatedBytes();
int[] collections = Enumerable.Range(0, 3).Select(GC.CollectionCount).ToArray();
timer.Restart();
browser.NavigateEditor(csharp, "Test.cs");
double navigationMs = timer.Elapsed.TotalMilliseconds;
for (int frame = 1; frame <= 8; frame++) browser.AdvanceTime(frame * 20);
string proof = browser.ExecuteJavaScript("xfeMonacoEditor.render(true);JSON.stringify({text: Array.from(document.querySelectorAll('.view-lines .view-line')).map(e=>e.textContent.replace(/\\u00a0/g,' ')),language:xfeMonacoEditor.getModel().getLanguageId(),version:xfeMonacoEditor.getModel().getVersionId(),value:xfeMonacoEditor.getValue()})");
browser.Paint(canvas, new(0, 0, 796, 404), 0); browser.FramePresented();
double readyMs = timer.Elapsed.TotalMilliseconds;
long bytes = GC.GetTotalAllocatedBytes() - allocated;
var dom = typeof(BrowserService).GetField("dom", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(browser)!;
long Counter(object instance, string name) => (long?)instance.GetType().GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)?.GetValue(instance) ?? 0;
var workCounters = new { browser.LayoutPassCount, LayoutMilliseconds = Counter(browser, "LayoutTicks") * 1000.0 / Stopwatch.Frequency,
BudgetValidations = Counter(dom, "BudgetValidationCount"), BudgetNodeVisits = Counter(dom, "BudgetNodeVisits"),
BudgetMilliseconds = Counter(dom, "BudgetValidationTicks") * 1000.0 / Stopwatch.Frequency,
Collections = Enumerable.Range(0, 3).Select(i => GC.CollectionCount(i) - collections[i]).ToArray() };
using var parsed = JsonDocument.Parse(proof);
var data = parsed.RootElement;
string[] visible = data.GetProperty("text").EnumerateArray().Select(e => e.GetString()!.TrimEnd()).ToArray();
bool ready = data.GetProperty("value").GetString() == csharp && data.GetProperty("language").GetString() == "csharp" &&
visible.Length >= 3 && visible.SequenceEqual(csharp.Split('\n').Take(visible.Length));
string renderHash = HashText(browser.ExecuteJavaScript("JSON.stringify(Array.from(document.querySelectorAll('.view-lines .view-line')).map(e=>e.innerHTML))"));
string tokenWork = browser.ExecuteJavaScript("(function(){const model=xfeMonacoEditor.getModel(),value=model.getValue(),original=model.getValue;let reads=0;model.getValue=function(){reads++;return original.apply(this,arguments)};try{const lines=monaco.editor.tokenize(value,'csharp');return JSON.stringify({Lines:lines.length,FullModelReads:reads})}finally{model.getValue=original}})()");
browser.ExecuteJavaScript("xfeMonacoEditor.focus();xfeMonacoEditor.setPosition({lineNumber:1,column:1});'ready'");
browser.Key(new("keydown", "x", "KeyX", "x")); browser.Key(new("keyup", "x", "KeyX"));
bool edited = browser.ReadEditorText() == "x" + csharp;
canvas.SavePng(Path.Combine(output, kind + "-" + attempt + ".png"));
using var work = JsonDocument.Parse(tokenWork);
var row = new { Kind = kind, Attempt = attempt, Cold = kind == "ShortCSharp" && attempt == 1, Passed = ready && edited, NavigationMilliseconds = navigationMs,
FullReadyMilliseconds = readyMs, AllocatedBytes = bytes, browser.PackagedScriptCacheHits, browser.PackagedScriptPreparations,
RenderSha256 = renderHash, Proof = data.Clone(), TokenizationWork = work.RootElement.Clone(), Work = workCounters, Regex = regex?.Results(), Trace = trace };
passed &= row.Passed;
cases.Add(row);
Console.WriteLine(JsonSerializer.Serialize(new { row.Kind, row.Attempt, row.Passed, navigationMs, readyMs, bytes, renderHash, row.TokenizationWork, row.Work }));
browser.Release();
}
bool unchanged = inputs.All(pair => Hash(Path.Combine(root, pair.Key)) == pair.Value);
string result = Path.Combine(output, "Result.json");
File.WriteAllText(result, JsonSerializer.Serialize(new { Kind = "MonacoFullInitializationHostProfile", Passed = passed && unchanged,
HostOnly = true, NativeAcceptance = false, Frequency = Stopwatch.Frequency, Sources = inputs,
ProbeSha256 = Hash(ProbeSource()),
RuntimeJintSha256 = Hash(typeof(Jint.Engine).Assembly.Location),
RuntimePatches = new { FailedRegexSuffix = typeof(Jint.Engine).Assembly.GetType("XFESystem.JintFailedRegexSuffixV1") is not null },
Cases = cases }, new JsonSerializerOptions { WriteIndented = true }));
Console.WriteLine(result);
if (!passed || !unchanged) throw new IOException("The full Monaco initialization profile failed.");
}
private static string Hash(string path) => Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path)));
private static string ProbeSource([System.Runtime.CompilerServices.CallerFilePath] string source = "") => source;
private static string HashText(string value) => Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(value)));
}