using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Shared.Profiling;
// No interfaces here, don't want the interface dispatch overhead.
// See ProfData.cs for description of profiling data layout.
public sealed partial class ProfManager
{
[IoC.Dependency] private IConfigurationManager _cfg = default!;
[IoC.Dependency] private ILogManager _logManager = default!;
///
/// Proxy to prof.enabled CVar.
///
public bool IsEnabled { get; private set; }
// I don't care that this isn't a tree I will call upon the string tree just like in BYOND.
private readonly Dictionary _stringTreeIndices = new();
private ValueList _stringTree;
public ProfBuffer Buffer;
private ISawmill? _sawmill = default!;
internal void Initialize()
{
_sawmill = _logManager.GetSawmill("prof");
_cfg.OnValueChanged(CVars.ProfIndexSize, i =>
{
if (!BitOperations.IsPow2(i))
{
_sawmill.Warning("Rounding prof.index_size to next POT");
i = BufferHelpers.FittingPowerOfTwo(i);
}
Buffer.IndexBuffer = new ProfIndex[i];
Buffer.IndexWriteOffset = 0;
}, true);
_cfg.OnValueChanged(CVars.ProfBufferSize, i =>
{
if (!BitOperations.IsPow2(i))
{
_sawmill.Warning("Rounding prof.buffer_size to next POT");
i = BufferHelpers.FittingPowerOfTwo(i);
}
Buffer.LogBuffer = new ProfLog[i];
// Invalidate all indices by artificially incrementing the write position.
Buffer.LogWriteOffset += i;
}, true);
_cfg.OnValueChanged(CVars.ProfEnabled, b => IsEnabled = b, true);
}
///
/// Write an index covering the region from to the current write position.
///
/// The absolute start index of
/// The type of index to mark.
public void MarkIndex(long start, ProfIndexType type)
{
if (!IsEnabled)
return;
// Ha
var indexIdx = Buffer.IndexWriteOffset++;
ProfIndex index = default;
index.StartPos = start;
index.EndPos = Buffer.LogWriteOffset;
index.Type = type;
Buffer.Index(indexIdx) = index;
}
///
/// Write a single profiling value to the log.
///
/// The name of the entry.
/// The value to write.
/// The absolute position of the written log entry.
public long WriteValue(string text, in ProfValue value)
{
if (!IsEnabled)
return 0;
var stringRef = InsertString(text);
var idx = Buffer.LogWriteOffset;
ref var cmd = ref WriteCmd();
cmd = default;
cmd.Type = ProfLogType.Value;
cmd.Value.Value = value;
cmd.Value.StringId = stringRef;
return idx;
}
///
/// Write a single profiling value to the log. Automatically samples the given sampler.
///
/// The name of the entry.
/// The value of the sampler is measured to store a .
/// The absolute position of the written log entry.
public long WriteValue(string text, in ProfSampler sampler) => WriteValue(text, ProfData.TimeAlloc(sampler));
///
/// Write a single profiling value to the log.
///
/// The name of the entry.
/// The value to write.
/// The absolute position of the written log entry.
public long WriteValue(string text, int int32) => WriteValue(text, ProfData.Int32(int32));
///
/// Write a single profiling value to the log.
///
/// The name of the entry.
/// The value to write.
/// The absolute position of the written log entry.
public long WriteValue(string text, long int64) => WriteValue(text, ProfData.Int64(int64));
///
/// Make a guarded value for usage with using blocks.
///
public ValueGuard Value(string text)
{
return new ValueGuard(this, text);
}
///
/// Write the start of a new log group.
///
/// The absolute position of the written log entry.
public long WriteGroupStart()
{
if (!IsEnabled)
return 0;
var idx = Buffer.LogWriteOffset;
ref var cmd = ref WriteCmd();
cmd = default;
cmd.Type = ProfLogType.GroupStart;
return idx;
}
///
/// Write the end of a log group.
///
/// The index of the matching group start.
/// The name of the group.
/// The value of the group.
public void WriteGroupEnd(long startIndex, string text, in ProfValue value)
{
if (!IsEnabled)
return;
var stringRef = InsertString(text);
ref var cmd = ref WriteCmd();
cmd = default;
cmd.Type = ProfLogType.GroupEnd;
cmd.GroupEnd.StringId = stringRef;
cmd.GroupEnd.StartIndex = startIndex;
cmd.GroupEnd.Value = value;
}
///
/// Write the end of a log group.
///
/// The index of the matching group start.
/// The name of the group.
/// The value of the sampler is measured to store a .
public void WriteGroupEnd(long startIndex, string text, in ProfSampler sampler)
{
WriteGroupEnd(startIndex, text, ProfData.TimeAlloc(sampler));
}
///
/// Make a guarded group for usage with using blocks.
///
public GroupGuard Group(string name)
{
var start = WriteGroupStart();
return new GroupGuard(this, start, name);
}
///
/// Get the value for a string in the string tree.
///
public string GetString(int stringId) => _stringTree[stringId];
///
/// Get the index of a string in the string tree.
///
public int? GetStringIdx(string stringValue)
{
if (_stringTreeIndices.TryGetValue(stringValue, out var idx))
return idx;
return null;
}
private int InsertString(string text)
{
ref var stringRef = ref CollectionsMarshal.GetValueRefOrAddDefault(_stringTreeIndices, text, out var exists);
if (!exists)
{
stringRef = _stringTree.Count;
_stringTree.Add(text);
}
return stringRef;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref ProfLog WriteCmd()
{
var buf = Buffer.LogBuffer;
// This uses LongLength because it saves a single instruction to load the value from memory.
// I spent more time on this function in sharplab than was probably worth it.
var idx = Buffer.LogWriteOffset & (buf.LongLength - 1);
Buffer.LogWriteOffset += 1;
return ref buf[(int)idx];
}
public readonly struct GroupGuard : IDisposable
{
private readonly ProfManager _mgr;
private readonly long _startIndex;
private readonly string _groupName;
private readonly ProfSampler _sampler;
public GroupGuard(ProfManager mgr, long startIndex, string groupName)
{
_mgr = mgr;
_startIndex = startIndex;
_groupName = groupName;
_sampler = ProfSampler.StartNew();
}
public void Dispose()
{
_mgr.WriteGroupEnd(_startIndex, _groupName, _sampler);
}
}
public readonly struct ValueGuard : IDisposable
{
private readonly ProfManager _mgr;
private readonly string _text;
private readonly ProfSampler _sampler;
public ValueGuard(ProfManager mgr, string text)
{
_mgr = mgr;
_text = text;
_sampler = ProfSampler.StartNew();
}
public void Dispose()
{
_mgr.WriteValue(_text, _sampler);
}
}
}