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); } } }