Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 85 additions & 34 deletions src/ScriptEngine/Machine/CodeStat/CodeStatProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,86 +5,137 @@ This Source Code Form is subject to the terms of the
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace ScriptEngine.Machine
{
/// <summary>
/// Сборщик статистики исполнения кода. Его вызывают все потоки, где исполняется код,
/// в том числе фоновые задания: коллекции потокобезопасные, а время каждый поток меряет сам.
/// </summary>
public class CodeStatProcessor : ICodeStatCollector
{
private Dictionary<CodeStatEntry, int> _codeStat = new Dictionary<CodeStatEntry, int>();
private Dictionary<CodeStatEntry, Stopwatch> _watchers = new Dictionary<CodeStatEntry, Stopwatch>();
private Stopwatch _activeStopwatch = null;
private HashSet<string> _preparedScripts = new HashSet<string>();
private readonly ConcurrentDictionary<CodeStatEntry, EntryStat> _stats =
new ConcurrentDictionary<CodeStatEntry, EntryStat>();

private readonly ConcurrentDictionary<string, bool> _preparedScripts =
new ConcurrentDictionary<string, bool>();

// Точка, которую сейчас исполняет поток: ей идет время до перехода к следующей точке
private readonly ThreadLocal<ActiveEntry> _activeEntry =
new ThreadLocal<ActiveEntry>(() => new ActiveEntry());

// Делегат создается один раз: лямбда в вызове GetOrAdd выделяла бы память на каждую строку
private readonly Func<CodeStatEntry, EntryStat> _newStat;

// Отчет идет в порядке регистрации точек
private long _registrationOrder;

public CodeStatProcessor()
{
_newStat = _ => new EntryStat(Interlocked.Increment(ref _registrationOrder));
}

public bool IsPrepared(string ScriptFileName)
{
return _preparedScripts.Contains(ScriptFileName);
return _preparedScripts.ContainsKey(ScriptFileName);
}

public void MarkEntryReached(CodeStatEntry entry, int count = 1)
{
int oldValue = 0;
_codeStat.TryGetValue(entry, out oldValue);
_codeStat[entry] = oldValue + count;

var stat = _stats.GetOrAdd(entry, _newStat);
if (count == 0)
{
if (!_watchers.ContainsKey(entry))
{
_watchers.Add(entry, new Stopwatch());
}
}
else
{
_activeStopwatch?.Stop();
_activeStopwatch = _watchers[entry];
_activeStopwatch.Start();
}
return;

Interlocked.Add(ref stat.Count, count);
SwitchTo(stat);
}

public void MarkPrepared(string scriptFileName)
{
_preparedScripts.Add(scriptFileName);
_preparedScripts.TryAdd(scriptFileName, true);
}

/// <summary>
/// Снимок статистики. Можно брать, пока код еще исполняется в других потоках.
/// </summary>
public CodeStatDataCollection GetStatData()
{
CodeStatDataCollection data = new CodeStatDataCollection();
foreach (var item in _codeStat)
var data = new CodeStatDataCollection();
foreach (var item in _stats.ToArray().OrderBy(x => x.Value.Order))
{
if (!IsPrepared(item.Key.ScriptFileName))
{
continue;
}
data.Add(new CodeStatData(item.Key, _watchers[item.Key].ElapsedMilliseconds, item.Value));
data.Add(new CodeStatData(item.Key, item.Value.ElapsedMilliseconds, Volatile.Read(ref item.Value.Count)));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return data;
}

/// <summary>
/// Завершает замер времени в текущем потоке
/// </summary>
public void EndCodeStat()
{
_activeStopwatch?.Stop();
StopCurrentWatch();
}

public void StopCurrentWatch()
{
SwitchTo(null);
}

public void StopWatch(CodeStatEntry entry)
{
if (_watchers.ContainsKey(entry))
if (_stats.TryGetValue(entry, out var stat) && _activeEntry.Value.Stat == stat)
{
_watchers[entry].Stop();
SwitchTo(null);
}
}

public void ResumeWatch(CodeStatEntry entry)
{
_activeStopwatch?.Stop();
_stats.TryGetValue(entry, out var stat);
SwitchTo(stat);
}

private void SwitchTo(EntryStat next)
{
var active = _activeEntry.Value;
var now = Stopwatch.GetTimestamp();
active.Stat?.AddElapsed(now - active.Since);
active.Stat = next;
active.Since = now;
}

if (_watchers.ContainsKey(entry))
private sealed class EntryStat
{
public readonly long Order;
public int Count;
private long _elapsedTicks;

public EntryStat(long order)
{
_activeStopwatch = _watchers[entry];
_activeStopwatch.Start();
Order = order;
}

public void AddElapsed(long ticks) => Interlocked.Add(ref _elapsedTicks, ticks);

// Так же, как Stopwatch.ElapsedMilliseconds
public long ElapsedMilliseconds =>
Stopwatch.GetElapsedTime(0, Interlocked.Read(ref _elapsedTicks)).Ticks / TimeSpan.TicksPerMillisecond;
}

private sealed class ActiveEntry
{
public EntryStat Stat;
public long Since;
}
}
}
6 changes: 6 additions & 0 deletions src/ScriptEngine/Machine/CodeStat/ICodeStatCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,11 @@ public interface ICodeStatCollector

void StopWatch(CodeStatEntry entry);
void ResumeWatch(CodeStatEntry entry);

/// <summary>
/// Текущий поток закончил исполнять код: время до следующего исполнения на нем
/// не относится к последней достигнутой точке
/// </summary>
void StopCurrentWatch();
}
}
4 changes: 4 additions & 0 deletions src/ScriptEngine/Machine/StackMachineExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public void BeforeProcessStart(IBslProcess process)

public void AfterProcessExit(IBslProcess process)
{
// Поток фонового задания или запроса веб-сервера возвращается в пул,
// и время простоя не должно уйти последней исполненной строке
process.Services.TryResolve<ICodeStatCollector>()?.StopCurrentWatch();

var debugger = process.Services.TryResolve<IDebugger>();
if (debugger?.IsEnabled != true)
return;
Expand Down
4 changes: 3 additions & 1 deletion src/ScriptEngine/Machine/TypeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This Source Code Form is subject to the terms of the

using ScriptEngine.Machine.Contexts;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
Expand All @@ -25,7 +26,8 @@ public class TypeFactory : ITypeFactory
{
private readonly TypeDescriptor _systemType;

private Dictionary<int, InstanceConstructor> _constructorsCache = new Dictionary<int, InstanceConstructor>();
// Фабрика одна на тип, а объекты создаются из разных потоков
private readonly ConcurrentDictionary<int, InstanceConstructor> _constructorsCache = new ConcurrentDictionary<int, InstanceConstructor>();
private static readonly Refl.MethodInfo CopyMethod = typeof(TypeFactory)
.GetMethod(nameof(CaptureVariantArgs), Refl.BindingFlags.Static | Refl.BindingFlags.InvokeMethod | Refl.BindingFlags.NonPublic);

Expand Down
13 changes: 4 additions & 9 deletions src/ScriptEngine/Types/TypeFactoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,20 @@ This Source Code Form is subject to the terms of the
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/

using System.Collections.Generic;
using System.Collections.Concurrent;
using OneScript.Types;
using ScriptEngine.Machine;

namespace ScriptEngine.Types
{
public class TypeFactoryCache
{
private readonly Dictionary<TypeDescriptor, TypeFactory> _factories = new Dictionary<TypeDescriptor, TypeFactory>();
// Объекты создаются из всех потоков, где исполняется код, в том числе из фоновых заданий
private readonly ConcurrentDictionary<TypeDescriptor, TypeFactory> _factories = new ConcurrentDictionary<TypeDescriptor, TypeFactory>();

public TypeFactory GetFactoryFor(TypeDescriptor type)
{
if (!_factories.TryGetValue(type, out var factory))
{
factory = new TypeFactory(type);
_factories[type] = factory;
}

return factory;
return _factories.GetOrAdd(type, t => new TypeFactory(t));
}
}
}
Loading
Loading