Профилирование скриптов

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

  1. Хеширование: Используйте стабильное хеширование для идентификации скриптов
  2. Периодический сброс: Очищайте статистику для избежания утечек памяти
  3. Conditional compilation: Отключайте профилирование в релизных сборках
  4. Агрегация: Собирайте статистику за периоды времени
  5. Оповещения: Настройте 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());
    }
}