Skip to content

Commit 099cea3

Browse files
committed
merge {ryanheath:feature/monitor-memory-usage}: Fix for SebLague#410
1 parent fb6f082 commit 099cea3

File tree

4 files changed

+237
-16
lines changed

4 files changed

+237
-16
lines changed

Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public enum PlayerType
5656
readonly int tokenCount;
5757
readonly int debugTokenCount;
5858
readonly StringBuilder pgns;
59+
long maxMemoryUsed = 0;
5960

6061
public ChallengeController()
6162
{
@@ -235,6 +236,25 @@ void OnMoveChosen(Move chosenMove)
235236
moveToPlay = chosenMove;
236237
isWaitingToPlayMove = true;
237238
playMoveTime = lastMoveMadeTime + MinMoveDelay;
239+
if (MonitorMemoryUsage && PlayerToMove.Bot is MyBot)
240+
{
241+
isPlaying = false;
242+
try
243+
{
244+
maxMemoryUsed = Math.Max(ObjectSizeHelper.GetSize(PlayerToMove.Bot), maxMemoryUsed);
245+
}
246+
catch (Exception e)
247+
{
248+
Log("An error occurred while determining used memory size.\n" + e.ToString(), true,
249+
ConsoleColor.Red);
250+
hasBotTaskException = true;
251+
botExInfo = ExceptionDispatchInfo.Capture(e);
252+
}
253+
finally
254+
{
255+
isPlaying = true;
256+
}
257+
}
238258
}
239259
else
240260
{
@@ -391,7 +411,8 @@ public void Draw()
391411

392412
public void DrawOverlay()
393413
{
394-
BotBrainCapacityUI.Draw(tokenCount, debugTokenCount, MaxTokenCount);
414+
BotBrainCapacityUI.DrawTokenUsage(tokenCount, debugTokenCount, MaxTokenCount);
415+
BotBrainCapacityUI.DrawMemoryUsage(maxMemoryUsed, MaxMemoryUsage);
395416
MenuUI.DrawButtons(this);
396417
MatchStatsUI.DrawMatchStats(this);
397418
}

Chess-Challenge/src/Framework/Application/Core/Settings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public static class Settings
2020
// Other settings
2121
public const int MaxTokenCount = 1024;
2222
public const LogType MessagesToLog = LogType.All;
23+
public const int MaxMemoryUsage = 256 * 1024 * 1024;
24+
public const bool MonitorMemoryUsage = true;
2325

2426
public enum LogType
2527
{
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Runtime.InteropServices;
7+
8+
namespace ChessChallenge.Application
9+
{
10+
public static class ObjectSizeHelper
11+
{
12+
static readonly long ObjectSize = IntPtr.Size == 8 ? 24 : 12;
13+
static readonly long PointerSize = IntPtr.Size;
14+
15+
public static long GetSize(object? obj) => GetSize(obj, new HashSet<object>());
16+
17+
static long GetSize(object? obj, HashSet<object> seenObjects)
18+
{
19+
if (obj is null)
20+
{
21+
return 0;
22+
}
23+
24+
var type = obj.GetType();
25+
26+
// ignore references to the API board and the API Timer
27+
if (typeof(API.Board) == type || typeof(API.Timer) == type)
28+
{
29+
return 0;
30+
}
31+
32+
if (type.IsEnum)
33+
{
34+
if (!sizeOfTypeCache.TryGetValue(type, out var sizeOfType))
35+
{
36+
var underlyingType = Enum.GetUnderlyingType(type);
37+
sizeOfTypeCache[type] = sizeOfType = Marshal.SizeOf(underlyingType);
38+
}
39+
40+
return sizeOfType;
41+
}
42+
43+
if (type.IsValueType)
44+
{
45+
// Marshal.SizeOf() does not work for structs with reference types
46+
// Marshal.SizeOf() also does not work with generics, so we need to use Unsafe.SizeOf()
47+
if (!fieldInfoCache.TryGetValue(type, out var fieldInfos))
48+
{
49+
fieldInfoCache[type] = fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
50+
}
51+
if (!typeContainsClassCache.TryGetValue(type, out var typeContainsClass))
52+
{
53+
typeContainsClassCache[type] = typeContainsClass = fieldInfos.Any(x => TypeIsClass(x.FieldType));
54+
}
55+
return typeContainsClass
56+
? GetFieldsMemorySize(obj, fieldInfos, seenObjects)
57+
: GetStructSize(type);
58+
}
59+
60+
if (type == typeof(string))
61+
{
62+
var str = (string)obj;
63+
return str.Length * 2 + 6 + ObjectSize;
64+
}
65+
66+
if (obj is IList collection)
67+
{
68+
var totalSize = ObjectSize;
69+
70+
// structs are counted easy
71+
var typeZero = collection.Count > 0 ? collection[0]?.GetType() : null;
72+
if (typeZero is not null && sizeOfTypeCache.TryGetValue(typeZero, out var sizeOfType))
73+
{
74+
totalSize += collection.Count * sizeOfType;
75+
return totalSize;
76+
}
77+
78+
for (var index = 0; index < collection.Count; index++)
79+
{
80+
var item = collection[index];
81+
totalSize += GetObjectSize(item, item?.GetType() ?? typeof(object), seenObjects);
82+
}
83+
84+
return totalSize;
85+
}
86+
87+
if (TypeIsClass(type))
88+
{
89+
if (!fieldInfoCache.TryGetValue(type, out var fieldInfos))
90+
{
91+
fieldInfoCache[type] = fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
92+
}
93+
94+
return ObjectSize + GetFieldsMemorySize(obj, fieldInfos, seenObjects);
95+
}
96+
97+
throw new ArgumentException($"Unknown type {type.Name}", nameof(obj));
98+
}
99+
100+
static readonly Dictionary<Type, FieldInfo[]> fieldInfoCache = new();
101+
static readonly Dictionary<Type, bool> typeContainsClassCache = new();
102+
static readonly Dictionary<Type, bool> typeIsClassCache = new();
103+
104+
static readonly Dictionary<Type, int> sizeOfTypeCache = new();
105+
private static long GetStructSize(Type type)
106+
{
107+
if (!sizeOfTypeCache.TryGetValue(type, out var sizeOfType))
108+
{
109+
var genericSizeOf = typeof(System.Runtime.CompilerServices.Unsafe)
110+
.GetMethod(nameof(System.Runtime.CompilerServices.Unsafe.SizeOf))
111+
.MakeGenericMethod(type);
112+
113+
sizeOfTypeCache[type] = sizeOfType = (int)genericSizeOf.Invoke(null, null)!;
114+
}
115+
116+
return sizeOfType;
117+
}
118+
119+
private static long GetFieldsMemorySize(object obj, FieldInfo[] fieldInfos, HashSet<object> seenObjects)
120+
{
121+
var totalSize = 0L;
122+
123+
for (var index = 0; index < fieldInfos.Length; index++)
124+
{
125+
var fieldInfo = fieldInfos[index];
126+
var fieldValue = fieldInfo.GetValue(obj);
127+
totalSize += GetObjectSize(fieldValue, fieldInfo.FieldType, seenObjects);
128+
}
129+
130+
return totalSize;
131+
}
132+
133+
private static bool TypeIsClass(Type type)
134+
{
135+
if (!typeIsClassCache.TryGetValue(type, out var typeIsClass))
136+
{
137+
typeIsClassCache[type] = typeIsClass = type.IsClass;
138+
}
139+
140+
return typeIsClass;
141+
}
142+
143+
private static long GetObjectSize(object? obj, Type type, HashSet<object> seenObjects)
144+
{
145+
var typeIsClass = TypeIsClass(type);
146+
147+
if (obj is not null && typeIsClass && seenObjects.Contains(obj))
148+
{
149+
return 0;
150+
}
151+
152+
var size = GetSize(obj, seenObjects) + (type.IsClass ? PointerSize : 0);
153+
154+
if (obj is not null && typeIsClass)
155+
{
156+
seenObjects.Add(obj);
157+
}
158+
159+
return size;
160+
}
161+
}
162+
}
Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Raylib_cs;
1+
using System;
2+
using Raylib_cs;
23

34
namespace ChessChallenge.Application
45
{
@@ -10,19 +11,45 @@ public static class BotBrainCapacityUI
1011
static readonly Color red = new(219, 9, 9, 255);
1112
static readonly Color background = new Color(40, 40, 40, 255);
1213

13-
public static void Draw(int totalTokenCount, int debugTokenCount, int tokenLimit)
14+
public static void DrawTokenUsage(int totalTokenCount, int debugTokenCount, int tokenLimit)
1415
{
1516
int activeTokenCount = totalTokenCount - debugTokenCount;
1617

17-
int screenWidth = Raylib.GetScreenWidth();
18+
int startPos = Raylib.GetScreenWidth() / 2;
19+
double t = (double)activeTokenCount / tokenLimit;
20+
string text = $"Bot Brain Capacity: {activeTokenCount}/{tokenLimit}";
21+
if (activeTokenCount > tokenLimit)
22+
{
23+
text += " [LIMIT EXCEEDED]";
24+
}
25+
else if (debugTokenCount != 0)
26+
{
27+
text += $" ({totalTokenCount} with Debugs included)";
28+
}
29+
Draw(text, t, startPos);
30+
}
31+
32+
public static void DrawMemoryUsage(long memorySize, long memoryLimit)
33+
{
34+
int startPos = 0;
35+
double t = (double)memorySize / memoryLimit;
36+
string text = $"Bot Memory Capacity: {FriendlySize(memorySize)} / {FriendlySize(memoryLimit)}";
37+
if (memorySize > memoryLimit)
38+
{
39+
text += " [LIMIT EXCEEDED]";
40+
}
41+
Draw(text, t, startPos);
42+
}
43+
44+
static void Draw(string text, double t, int startPos)
45+
{
46+
int barWidth = Raylib.GetScreenWidth() / 2;
1847
int screenHeight = Raylib.GetScreenHeight();
19-
int height = UIHelper.ScaleInt(48);
48+
int barHeight = UIHelper.ScaleInt(48);
2049
int fontSize = UIHelper.ScaleInt(35);
2150
// Bg
22-
Raylib.DrawRectangle(0, screenHeight - height, screenWidth, height, background);
51+
Raylib.DrawRectangle(startPos, screenHeight - barHeight, startPos + barWidth, barHeight, background);
2352
// Bar
24-
double t = (double)activeTokenCount / tokenLimit;
25-
2653
Color col;
2754
if (t <= 0.7)
2855
col = green;
@@ -32,19 +59,28 @@ public static void Draw(int totalTokenCount, int debugTokenCount, int tokenLimit
3259
col = orange;
3360
else
3461
col = red;
35-
Raylib.DrawRectangle(0, screenHeight - height, (int)(screenWidth * t), height, col);
62+
63+
Raylib.DrawRectangle(startPos, screenHeight - barHeight, startPos + (int)(barWidth * Math.Min(t, 1)), barHeight, col);
3664

37-
var textPos = new System.Numerics.Vector2(screenWidth / 2, screenHeight - height / 2);
38-
string text = $"Bot Brain Capacity: {activeTokenCount}/{tokenLimit}";
39-
if (activeTokenCount > tokenLimit)
65+
var textPos = new System.Numerics.Vector2(startPos + barWidth / 2, screenHeight - barHeight / 2);
66+
UIHelper.DrawText(text, textPos, fontSize, 1, Color.WHITE, UIHelper.AlignH.Centre);
67+
}
68+
69+
static readonly string[] sizes = { "B", "KB", "MB" };
70+
static string FriendlySize(long size)
71+
{
72+
if (size == 0)
4073
{
41-
text += " [LIMIT EXCEEDED]";
74+
return "--";
4275
}
43-
else if (debugTokenCount != 0)
76+
double friendlySize = size;
77+
int order = 0;
78+
while (friendlySize >= 1024 && order < sizes.Length - 1)
4479
{
45-
text += $" ({totalTokenCount} with Debugs included)";
80+
order++;
81+
friendlySize /= 1024;
4682
}
47-
UIHelper.DrawText(text, textPos, fontSize, 1, Color.WHITE, UIHelper.AlignH.Centre);
83+
return $"{friendlySize:0.##} {sizes[order]}";
4884
}
4985
}
5086
}

0 commit comments

Comments
 (0)