Files
RobustToolbox/Robust.Shared/Profiling/ProfManager.cs
T
83c2a1be11 [Dependency] source generator part 2 (#6550)
* [Dependency] source generator

No more reflection, no more codegen at runtime

Also various changes to Roslyn helpers to make this easier to write.

Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback.

No fallback is available for [field: Dependency] properties, due to a Roslyn bug.

Code Fixes exist. We love Roslyn

* Apply dependencies generator changes to all code

* Release notes

* Preprocessor got hands

* Handle nullable dependencies

These are bad but gotta deal with it.

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Fine, let's not use collection expressions

---------

Co-authored-by: Moony <moony@hellomouse.net>
2026-05-08 12:38:33 +02:00

281 lines
8.7 KiB
C#

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!;
/// <summary>
/// Proxy to <c>prof.enabled</c> CVar.
/// </summary>
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<string, int> _stringTreeIndices = new();
private ValueList<string> _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);
}
/// <summary>
/// Write an index covering the region from <paramref name="start"/> to the current write position.
/// </summary>
/// <param name="start">The absolute start index of </param>
/// <param name="type">The type of index to mark.</param>
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;
}
/// <summary>
/// Write a single profiling value to the log.
/// </summary>
/// <param name="text">The name of the entry.</param>
/// <param name="value">The value to write.</param>
/// <returns>The absolute position of the written log entry.</returns>
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;
}
/// <summary>
/// Write a single profiling value to the log. Automatically samples the given sampler.
/// </summary>
/// <param name="text">The name of the entry.</param>
/// <param name="sampler">The value of the sampler is measured to store a <see cref="TimeAndAllocSample"/>.</param>
/// <returns>The absolute position of the written log entry.</returns>
public long WriteValue(string text, in ProfSampler sampler) => WriteValue(text, ProfData.TimeAlloc(sampler));
/// <summary>
/// Write a single profiling value to the log.
/// </summary>
/// <param name="text">The name of the entry.</param>
/// <param name="int32">The value to write.</param>
/// <returns>The absolute position of the written log entry.</returns>
public long WriteValue(string text, int int32) => WriteValue(text, ProfData.Int32(int32));
/// <summary>
/// Write a single profiling value to the log.
/// </summary>
/// <param name="text">The name of the entry.</param>
/// <param name="int64">The value to write.</param>
/// <returns>The absolute position of the written log entry.</returns>
public long WriteValue(string text, long int64) => WriteValue(text, ProfData.Int64(int64));
/// <summary>
/// Make a guarded value for usage with using blocks.
/// </summary>
public ValueGuard Value(string text)
{
return new ValueGuard(this, text);
}
/// <summary>
/// Write the start of a new log group.
/// </summary>
/// <returns>The absolute position of the written log entry.</returns>
public long WriteGroupStart()
{
if (!IsEnabled)
return 0;
var idx = Buffer.LogWriteOffset;
ref var cmd = ref WriteCmd();
cmd = default;
cmd.Type = ProfLogType.GroupStart;
return idx;
}
/// <summary>
/// Write the end of a log group.
/// </summary>
/// <param name="startIndex">The index of the matching group start.</param>
/// <param name="text">The name of the group.</param>
/// <param name="value">The value of the group.</param>
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;
}
/// <summary>
/// Write the end of a log group.
/// </summary>
/// <param name="startIndex">The index of the matching group start.</param>
/// <param name="text">The name of the group.</param>
/// <param name="sampler">The value of the sampler is measured to store a <see cref="TimeAndAllocSample"/>.</param>
public void WriteGroupEnd(long startIndex, string text, in ProfSampler sampler)
{
WriteGroupEnd(startIndex, text, ProfData.TimeAlloc(sampler));
}
/// <summary>
/// Make a guarded group for usage with using blocks.
/// </summary>
public GroupGuard Group(string name)
{
var start = WriteGroupStart();
return new GroupGuard(this, start, name);
}
/// <summary>
/// Get the value for a string in the string tree.
/// </summary>
public string GetString(int stringId) => _stringTree[stringId];
/// <summary>
/// Get the index of a string in the string tree.
/// </summary>
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);
}
}
}