Профилирование скриптов
IgdrasilScripting предоставляет встроенные инструменты для мониторинга производительности скриптов через IScriptEngineProfiler.
Основы профилирования
Получение профилера
var engine = new LuaEngine();
var profiler = engine.Profiler;
if (profiler != null)
{
// Профилирование доступно
}
Примечание: По умолчанию профилер может быть
null. Для реализации профилирования см. раздел "Создание профилера".
Статистика компиляции
// Скомпилировать скрипт
var script = engine.Compile("return 2 + 2");
// Получить статистику
var stats = profiler?.GetCompilationStats(scriptHash);
if (stats != null)
{
Console.WriteLine($"Время компиляции: {stats.ElapsedMilliseconds}ms");
Console.WriteLine($"Успешная: {stats.Success}");
Console.WriteLine($"Дата: {stats.CompiledAt}");
if (!stats.Success)
{
Console.WriteLine($"Ошибка: {stats.ErrorMessage}");
}
}
Статистика выполнения
// Выполнить скрипт несколько раз
for (int i = 0; i < 1000; i++)
{
engine.Execute("return math.sqrt(144)");
}
// Получить статистику
var execStats = profiler?.GetExecutionStats(scriptHash);
if (execStats != null)
{
Console.WriteLine($"Количество вызовов: {execStats.InvocationCount}");
Console.WriteLine($"Среднее время: {execStats.AverageMilliseconds}ms");
Console.WriteLine($"Минимальное время: {execStats.MinMilliseconds}ms");
Console.WriteLine($"Максимальное время: {execStats.MaxMilliseconds}ms");
Console.WriteLine($"Количество ошибок: {execStats.ErrorCount}");
}
Общая статистика
var totalStats = profiler?.GetTotalStats();
if (totalStats != null)
{
Console.WriteLine($"Всего скомпилировано: {totalStats.TotalCompiledScripts}");
Console.WriteLine($"Всего выполнений: {totalStats.TotalExecutions}");
Console.WriteLine($"Всего ошибок: {totalStats.TotalErrors}");
Console.WriteLine($"Общее время компиляции: {totalStats.TotalCompilationMs}ms");
Console.WriteLine($"Общее время выполнения: {totalStats.TotalExecutionMs}ms");
}
Сброс статистики
// Сбросить всю накопленную статистику
profiler?.Reset();
Создание собственного профилера
public class CustomScriptProfiler : IScriptEngineProfiler
{
private readonly Dictionary<string, CompilationStatistics> _compilationStats = new();
private readonly Dictionary<string, ExecutionStatsTracker> _executionStats = new();
public CompilationStatistics? GetCompilationStats(string scriptHash)
{
return _compilationStats.TryGetValue(scriptHash, out var stats) ? stats : null;
}
public ExecutionStatistics? GetExecutionStats(string scriptHash)
{
if (!_executionStats.TryGetValue(scriptHash, out var tracker))
return null;
return new ExecutionStatistics
{
InvocationCount = tracker.Count,
AverageMilliseconds = tracker.TotalMs / (double)tracker.Count,
MinMilliseconds = tracker.MinMs,
MaxMilliseconds = tracker.MaxMs,
ErrorCount = tracker.Errors
};
}
public ProfilerStatistics GetTotalStats()
{
return new ProfilerStatistics
{
TotalCompiledScripts = _compilationStats.Count,
TotalExecutions = _executionStats.Values.Sum(t => t.Count),
TotalErrors = _executionStats.Values.Sum(t => t.Errors),
TotalCompilationMs = _compilationStats.Values.Sum(s => s.ElapsedMilliseconds),
TotalExecutionMs = _executionStats.Values.Sum(t => t.TotalMs)
};
}
public void Reset()
{
_compilationStats.Clear();
_executionStats.Clear();
}
// Методы для записи данных
public void RecordCompilation(string scriptHash, long elapsedMs, bool success, string? error = null)
{
_compilationStats[scriptHash] = new CompilationStatistics
{
ElapsedMilliseconds = elapsedMs,
CompiledAt = DateTime.UtcNow,
Success = success,
ErrorMessage = error
};
}
public void RecordExecution(string scriptHash, long elapsedMs, bool success)
{
if (!_executionStats.TryGetValue(scriptHash, out var tracker))
{
tracker = new ExecutionStatsTracker();
_executionStats[scriptHash] = tracker;
}
tracker.Count++;
tracker.TotalMs += elapsedMs;
tracker.MinMs = Math.Min(tracker.MinMs, elapsedMs);
tracker.MaxMs = Math.Max(tracker.MaxMs, elapsedMs);
if (!success)
tracker.Errors++;
}
private class ExecutionStatsTracker
{
public long Count;
public long TotalMs;
public long MinMs = long.MaxValue;
public long MaxMs;
public long Errors;
}
}
Интеграция с кастомным движком
public class ProfiledLuaEngine : LuaEngine
{
private readonly CustomScriptProfiler _profiler = new();
public override IScriptEngineProfiler? Profiler => _profiler;
public override IScript<object[]> Compile(string script)
{
var sw = Stopwatch.StartNew();
var scriptHash = ComputeHash(script);
try
{
var compiled = base.Compile(script);
sw.Stop();
_profiler.RecordCompilation(scriptHash, sw.ElapsedMilliseconds, true);
return compiled;
}
catch (Exception ex)
{
sw.Stop();
_profiler.RecordCompilation(scriptHash, sw.ElapsedMilliseconds, false, ex.Message);
throw;
}
}
public override object[] Evaluate(string script)
{
var sw = Stopwatch.StartNew();
var scriptHash = ComputeHash(script);
try
{
var result = base.Evaluate(script);
sw.Stop();
_profiler.RecordExecution(scriptHash, sw.ElapsedMilliseconds, true);
return result;
}
catch
{
sw.Stop();
_profiler.RecordExecution(scriptHash, sw.ElapsedMilliseconds, false);
throw;
}
}
private static string ComputeHash(string script)
{
using var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(script);
var hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
Мониторинг в реальном времени
public class PerformanceMonitor
{
private readonly IScriptEngineProfiler _profiler;
private readonly Timer _timer;
public PerformanceMonitor(IScriptEngineProfiler profiler, TimeSpan interval)
{
_profiler = profiler;
_timer = new Timer(_ => PrintStats(), null, interval, interval);
}
private void PrintStats()
{
var stats = _profiler.GetTotalStats();
Console.WriteLine("=== Script Performance ===");
Console.WriteLine($"Scripts: {stats.TotalCompiledScripts}");
Console.WriteLine($"Executions: {stats.TotalExecutions}");
Console.WriteLine($"Errors: {stats.TotalErrors}");
Console.WriteLine($"Avg Compilation: {stats.TotalCompilationMs / (double)stats.TotalCompiledScripts:F2}ms");
Console.WriteLine($"Avg Execution: {stats.TotalExecutionMs / (double)stats.TotalExecutions:F2}ms");
}
}
// Использование
var monitor = new PerformanceMonitor(engine.Profiler!, TimeSpan.FromSeconds(5));
Best Practices
- Хеширование: Используйте стабильное хеширование для идентификации скриптов
- Периодический сброс: Очищайте статистику для избежания утечек памяти
- Conditional compilation: Отключайте профилирование в релизных сборках
- Агрегация: Собирайте статистику за периоды времени
- Оповещения: Настройте alerts для аномальных показателей
Пример отчета
public class ProfilerReport
{
public static void GenerateReport(IScriptEngineProfiler profiler, string outputPath)
{
var report = new StringBuilder();
var stats = profiler.GetTotalStats();
report.AppendLine("Script Performance Report");
report.AppendLine($"Generated: {DateTime.Now}");
report.AppendLine();
report.AppendLine($"Total Compiled Scripts: {stats.TotalCompiledScripts}");
report.AppendLine($"Total Executions: {stats.TotalExecutions}");
report.AppendLine($"Total Errors: {stats.TotalErrors}");
report.AppendLine($"Error Rate: {stats.TotalErrors / (double)stats.TotalExecutions * 100:F2}%");
report.AppendLine();
report.AppendLine($"Total Compilation Time: {stats.TotalCompilationMs}ms");
report.AppendLine($"Total Execution Time: {stats.TotalExecutionMs}ms");
report.AppendLine($"Average Compilation: {stats.TotalCompilationMs / (double)stats.TotalCompiledScripts:F2}ms");
report.AppendLine($"Average Execution: {stats.TotalExecutionMs / (double)stats.TotalExecutions:F2}ms");
File.WriteAllText(outputPath, report.ToString());
}
}