Add alignment options to StorageTester

This commit is contained in:
Alex Barney 2024-04-20 13:34:22 -07:00
parent 27cc721b31
commit c3cc7a69fb
3 changed files with 97 additions and 66 deletions

View file

@ -1,7 +1,9 @@
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Util;
namespace LibHac.Tests.Fs; namespace LibHac.Tests.Fs;
@ -12,6 +14,8 @@ public class StorageTester
private byte[][] _buffers; private byte[][] _buffers;
private int _size; private int _size;
private int[] _accessParamProbs;
private int[] _frequentAccessOffsets; private int[] _frequentAccessOffsets;
private int _lastAccessEnd; private int _lastAccessEnd;
private int _totalAccessCount; private int _totalAccessCount;
@ -20,8 +24,7 @@ public class StorageTester
public class Configuration public class Configuration
{ {
public Entry[] Entries { get; set; } public Entry[] Entries { get; set; }
public int[] SizeClassProbs { get; set; } public AccessParam[] AccessParams { get; set; }
public int[] SizeClassMaxSizes { get; set; }
public int[] TaskProbs { get; set; } public int[] TaskProbs { get; set; }
public int[] AccessTypeProbs { get; set; } public int[] AccessTypeProbs { get; set; }
public ulong RngSeed { get; set; } public ulong RngSeed { get; set; }
@ -34,28 +37,34 @@ public class StorageTester
if (entries.Length < 2) if (entries.Length < 2)
{ {
throw new ArgumentException("At least 2 storage entries must be provided", nameof(config.Entries)); throw new ArgumentException("At least 2 storage entries must be provided", nameof(config));
} }
if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1) if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1)
{ {
throw new ArgumentException("All storages must have the same size.", nameof(config.Entries)); throw new ArgumentException("All storages must have the same size.", nameof(config));
} }
if (entries[0].BackingArray.Length == 0) if (entries[0].BackingArray.Length == 0)
{ {
throw new ArgumentException("The storage size must be greater than 0.", nameof(config.Entries)); throw new ArgumentException("The storage size must be greater than 0.", nameof(config));
}
if (config.AccessParams.SelectMany<AccessParam, int>(x => [x.OffsetAlignment, x.SizeAlignment]).Any(x => x != 0 && !BitUtil.IsPowerOfTwo(x)))
{
throw new ArgumentException("All alignments must be 0 or powers of 2.", nameof(config));
} }
_config = config; _config = config;
_random = new Random(config.RngSeed); _random = new Random(config.RngSeed);
_accessParamProbs = config.AccessParams.Select(x => x.Prob).ToArray();
_backingArrays = entries.Select(x => x.BackingArray).ToArray(); _backingArrays = entries.Select(x => x.BackingArray).ToArray();
_buffers = new byte[entries.Length][]; _buffers = new byte[entries.Length][];
for (int i = 0; i < entries.Length; i++) for (int i = 0; i < entries.Length; i++)
{ {
_buffers[i] = new byte[config.SizeClassMaxSizes[^1]]; _buffers[i] = new byte[config.AccessParams.Max(x => x.MaxSize)];
} }
_size = entries[0].BackingArray.Length; _size = entries[0].BackingArray.Length;
@ -64,48 +73,10 @@ public class StorageTester
_frequentAccessOffsets = new int[config.FrequentAccessBlockCount]; _frequentAccessOffsets = new int[config.FrequentAccessBlockCount];
for (int i = 0; i < _frequentAccessOffsets.Length; i++) for (int i = 0; i < _frequentAccessOffsets.Length; i++)
{ {
_frequentAccessOffsets[i] = ChooseOffset(AccessType.Random); _frequentAccessOffsets[i] = _random.Next(0, _size);
} }
} }
//public StorageTester(ulong rngSeed, int frequentAccessBlockCount, params Entry[] entries)
//{
// if (entries.Length < 2)
// {
// throw new ArgumentException("At least 2 storage entries must be provided", nameof(entries));
// }
// if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1)
// {
// throw new ArgumentException("All storages must have the same size.", nameof(entries));
// }
// if (entries[0].BackingArray.Length == 0)
// {
// throw new ArgumentException("The storage size must be greater than 0.", nameof(entries));
// }
// _random = new Random(rngSeed);
// _entries = entries;
// _backingArrays = entries.Select(x => x.BackingArray).ToArray();
// _buffers = new byte[entries.Length][];
// for (int i = 0; i < entries.Length; i++)
// {
// _buffers[i] = new byte[SizeClassMaxSizes[^1]];
// }
// _size = _entries[0].BackingArray.Length;
// _lastAccessEnd = 0;
// _frequentAccessOffsets = new int[frequentAccessBlockCount];
// for (int i = 0; i < _frequentAccessOffsets.Length; i++)
// {
// _frequentAccessOffsets[i] = ChooseOffset(AccessType.Random);
// }
//}
public void Run(long accessCount) public void Run(long accessCount)
{ {
long endCount = _totalAccessCount + accessCount; long endCount = _totalAccessCount + accessCount;
@ -132,10 +103,11 @@ public class StorageTester
private void RunRead() private void RunRead()
{ {
int sizeClass = ChooseSizeClass(); AccessParam accessParams = ChooseAccessRangeParams();
AccessType accessType = ChooseAccessType(); AccessType accessType = ChooseAccessType();
int offset = ChooseOffset(accessType);
int size = ChooseSize(offset, sizeClass); int offset = ChooseOffset(accessType, accessParams);
int size = ChooseSize(offset, accessParams);
for (int i = 0; i < _config.Entries.Length; i++) for (int i = 0; i < _config.Entries.Length; i++)
{ {
@ -147,14 +119,17 @@ public class StorageTester
{ {
throw new InvalidDataException($"Read: Offset {offset}; Size {size}"); throw new InvalidDataException($"Read: Offset {offset}; Size {size}");
} }
_lastAccessEnd = offset + size;
} }
private void RunWrite() private void RunWrite()
{ {
int sizeClass = ChooseSizeClass(); AccessParam accessParams = ChooseAccessRangeParams();
AccessType accessType = ChooseAccessType(); AccessType accessType = ChooseAccessType();
int offset = ChooseOffset(accessType);
int size = ChooseSize(offset, sizeClass); int offset = ChooseOffset(accessType, accessParams);
int size = ChooseSize(offset, accessParams);
Span<byte> buffer = _buffers[0].AsSpan(0, size); Span<byte> buffer = _buffers[0].AsSpan(0, size);
_random.NextBytes(buffer); _random.NextBytes(buffer);
@ -164,6 +139,8 @@ public class StorageTester
Entry entry = _config.Entries[i]; Entry entry = _config.Entries[i];
entry.Storage.Write(offset, buffer).ThrowIfFailure(); entry.Storage.Write(offset, buffer).ThrowIfFailure();
} }
_lastAccessEnd = offset + size;
} }
private void RunFlush() private void RunFlush()
@ -180,24 +157,36 @@ public class StorageTester
} }
private Task ChooseTask() => (Task)ChooseProb(_config.TaskProbs); private Task ChooseTask() => (Task)ChooseProb(_config.TaskProbs);
private int ChooseSizeClass() => ChooseProb(_config.SizeClassProbs); private AccessParam ChooseAccessRangeParams() => _config.AccessParams[ChooseProb(_accessParamProbs)];
private AccessType ChooseAccessType() => (AccessType)ChooseProb(_config.AccessTypeProbs); private AccessType ChooseAccessType() => (AccessType)ChooseProb(_config.AccessTypeProbs);
private int ChooseOffset(AccessType type) => type switch private int ChooseOffset(AccessType type, AccessParam accessParams)
{ {
AccessType.Random => _random.Next(0, _size), int offset = type switch
AccessType.Sequential => _lastAccessEnd == _size ? 0 : _lastAccessEnd, {
AccessType.FrequentBlock => _frequentAccessOffsets[_random.Next(0, _frequentAccessOffsets.Length)], AccessType.Random => _random.Next(0, _size),
_ => 0 AccessType.Sequential => _lastAccessEnd == _size ? 0 : _lastAccessEnd,
}; AccessType.FrequentBlock => _frequentAccessOffsets[_random.Next(0, _frequentAccessOffsets.Length)],
_ => 0
};
return Align(offset, accessParams.OffsetAlignment);
}
private int ChooseSize(int offset, int sizeClass) private int ChooseSize(int offset, AccessParam sizeRange)
{ {
int availableSize = Math.Max(0, _size - offset); int availableSize = Math.Max(0, _size - offset);
int randSize = _random.Next(0, _config.SizeClassMaxSizes[sizeClass]); int randSize = Align(_random.Next(sizeRange.MinSize, sizeRange.MaxSize), sizeRange.SizeAlignment);
return Math.Min(availableSize, randSize); return Math.Min(availableSize, randSize);
} }
private int Align(int value, int alignment)
{
if (alignment == 0)
return value;
return Alignment.AlignDown(value, alignment);
}
private int ChooseProb(int[] weights) private int ChooseProb(int[] weights)
{ {
int total = 0; int total = 0;
@ -248,6 +237,38 @@ public class StorageTester
} }
} }
public readonly struct AccessParam
{
public readonly int Prob;
public readonly int MinSize;
public readonly int MaxSize;
public readonly int OffsetAlignment;
public readonly int SizeAlignment;
public AccessParam(int prob, int minSize, int maxSize, int offsetAlignment, int sizeAlignment)
{
Prob = prob;
MinSize = minSize;
MaxSize = maxSize;
OffsetAlignment = offsetAlignment;
SizeAlignment = sizeAlignment;
}
public AccessParam(int prob, int minSize, int maxSize)
{
Prob = prob;
MinSize = minSize;
MaxSize = maxSize;
}
public AccessParam(int prob, int maxSize)
{
Prob = prob;
MinSize = 0;
MaxSize = maxSize;
}
}
private enum Task private enum Task
{ {
Read = 0, Read = 0,

View file

@ -209,11 +209,16 @@ public class BufferedStorageTests
var memoryStorageEntry = new StorageTester.Entry(memoryStorage, memoryStorageArray); var memoryStorageEntry = new StorageTester.Entry(memoryStorage, memoryStorageArray);
var bufferedStorageEntry = new StorageTester.Entry(bufferedStorage, bufferedStorageArray); var bufferedStorageEntry = new StorageTester.Entry(bufferedStorage, bufferedStorageArray);
var testerConfig = new StorageTester.Configuration() var accessParams = new StorageTester.AccessParam[config.SizeClassProbs.Length];
for (int i = 0; i < accessParams.Length; i++)
{
accessParams[i] = new StorageTester.AccessParam(config.SizeClassProbs[i], config.SizeClassMaxSizes[i]);
}
var testerConfig = new StorageTester.Configuration
{ {
Entries = [memoryStorageEntry, bufferedStorageEntry], Entries = [memoryStorageEntry, bufferedStorageEntry],
SizeClassProbs = config.SizeClassProbs, AccessParams = accessParams,
SizeClassMaxSizes = config.SizeClassMaxSizes,
TaskProbs = config.TaskProbs, TaskProbs = config.TaskProbs,
AccessTypeProbs = config.AccessTypeProbs, AccessTypeProbs = config.AccessTypeProbs,
RngSeed = config.RngSeed, RngSeed = config.RngSeed,

View file

@ -340,11 +340,16 @@ public class IndirectStorageTests : IClassFixture<IndirectStorageBuffers>
var memoryStorageEntry = new StorageTester.Entry(memoryStorage, expectedStorageArray); var memoryStorageEntry = new StorageTester.Entry(memoryStorage, expectedStorageArray);
var indirectStorageEntry = new StorageTester.Entry(indirectStorage, expectedStorageArray); var indirectStorageEntry = new StorageTester.Entry(indirectStorage, expectedStorageArray);
var testerConfig = new StorageTester.Configuration() var accessParams = new StorageTester.AccessParam[accessConfig.SizeClassProbs.Length];
for (int i = 0; i < accessParams.Length; i++)
{
accessParams[i] = new StorageTester.AccessParam(accessConfig.SizeClassProbs[i], accessConfig.SizeClassMaxSizes[i]);
}
var testerConfig = new StorageTester.Configuration
{ {
Entries = [memoryStorageEntry, indirectStorageEntry], Entries = [memoryStorageEntry, indirectStorageEntry],
SizeClassProbs = accessConfig.SizeClassProbs, AccessParams = accessParams,
SizeClassMaxSizes = accessConfig.SizeClassMaxSizes,
TaskProbs = accessConfig.TaskProbs, TaskProbs = accessConfig.TaskProbs,
AccessTypeProbs = accessConfig.AccessTypeProbs, AccessTypeProbs = accessConfig.AccessTypeProbs,
RngSeed = accessConfig.RngSeed, RngSeed = accessConfig.RngSeed,