2021-01-17 08:30:51 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using LibHac.Fs;
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
namespace LibHac.Tests.Fs;
|
|
|
|
|
|
|
|
|
|
public class StorageTester
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private Random _random;
|
|
|
|
|
private byte[][] _backingArrays;
|
|
|
|
|
private byte[][] _buffers;
|
|
|
|
|
private int _size;
|
|
|
|
|
|
|
|
|
|
private int[] _frequentAccessOffsets;
|
|
|
|
|
private int _lastAccessEnd;
|
|
|
|
|
private int _totalAccessCount;
|
|
|
|
|
private Configuration _config;
|
|
|
|
|
|
|
|
|
|
public class Configuration
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
public Entry[] Entries { get; set; }
|
|
|
|
|
public int[] SizeClassProbs { get; set; }
|
|
|
|
|
public int[] SizeClassMaxSizes { get; set; }
|
|
|
|
|
public int[] TaskProbs { get; set; }
|
|
|
|
|
public int[] AccessTypeProbs { get; set; }
|
|
|
|
|
public ulong RngSeed { get; set; }
|
|
|
|
|
public int FrequentAccessBlockCount { get; set; }
|
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
public StorageTester(Configuration config)
|
|
|
|
|
{
|
|
|
|
|
Entry[] entries = config.Entries;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
if (entries.Length < 2)
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
throw new ArgumentException("At least 2 storage entries must be provided", nameof(config.Entries));
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
if (entries.Select(x => x.BackingArray.Length).Distinct().Count() != 1)
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
throw new ArgumentException("All storages must have the same size.", nameof(config.Entries));
|
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
if (entries[0].BackingArray.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException("The storage size must be greater than 0.", nameof(config.Entries));
|
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
_config = config;
|
|
|
|
|
_random = new Random(config.RngSeed);
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
_backingArrays = entries.Select(x => x.BackingArray).ToArray();
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
_buffers = new byte[entries.Length][];
|
|
|
|
|
for (int i = 0; i < entries.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
_buffers[i] = new byte[config.SizeClassMaxSizes[^1]];
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
_size = entries[0].BackingArray.Length;
|
|
|
|
|
_lastAccessEnd = 0;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
_frequentAccessOffsets = new int[config.FrequentAccessBlockCount];
|
|
|
|
|
for (int i = 0; i < _frequentAccessOffsets.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
_frequentAccessOffsets[i] = ChooseOffset(AccessType.Random);
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
2021-11-14 20:08:57 +01:00
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
//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)
|
|
|
|
|
{
|
|
|
|
|
long endCount = _totalAccessCount + accessCount;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
while (_totalAccessCount < endCount)
|
|
|
|
|
{
|
|
|
|
|
Task task = ChooseTask();
|
|
|
|
|
switch (task)
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
case Task.Read:
|
|
|
|
|
RunRead();
|
|
|
|
|
break;
|
|
|
|
|
case Task.Write:
|
|
|
|
|
RunWrite();
|
|
|
|
|
break;
|
|
|
|
|
case Task.Flush:
|
|
|
|
|
RunFlush();
|
|
|
|
|
break;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
_totalAccessCount++;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
2021-11-14 20:08:57 +01:00
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private void RunRead()
|
|
|
|
|
{
|
|
|
|
|
int sizeClass = ChooseSizeClass();
|
|
|
|
|
AccessType accessType = ChooseAccessType();
|
|
|
|
|
int offset = ChooseOffset(accessType);
|
|
|
|
|
int size = ChooseSize(offset, sizeClass);
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
for (int i = 0; i < _config.Entries.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
Entry entry = _config.Entries[i];
|
|
|
|
|
entry.Storage.Read(offset, _buffers[i].AsSpan(0, size)).ThrowIfFailure();
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
if (!CompareBuffers(_buffers, size))
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
throw new InvalidDataException($"Read: Offset {offset}; Size {size}");
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
2021-11-14 20:08:57 +01:00
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private void RunWrite()
|
|
|
|
|
{
|
|
|
|
|
int sizeClass = ChooseSizeClass();
|
|
|
|
|
AccessType accessType = ChooseAccessType();
|
|
|
|
|
int offset = ChooseOffset(accessType);
|
|
|
|
|
int size = ChooseSize(offset, sizeClass);
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
Span<byte> buffer = _buffers[0].AsSpan(0, size);
|
|
|
|
|
_random.NextBytes(buffer);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _config.Entries.Length; i++)
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
Entry entry = _config.Entries[i];
|
|
|
|
|
entry.Storage.Write(offset, buffer).ThrowIfFailure();
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private void RunFlush()
|
|
|
|
|
{
|
|
|
|
|
foreach (Entry entry in _config.Entries)
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
entry.Storage.Flush().ThrowIfFailure();
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
if (!CompareBuffers(_backingArrays, _size))
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
throw new InvalidDataException("Flush");
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private Task ChooseTask() => (Task)ChooseProb(_config.TaskProbs);
|
|
|
|
|
private int ChooseSizeClass() => ChooseProb(_config.SizeClassProbs);
|
|
|
|
|
private AccessType ChooseAccessType() => (AccessType)ChooseProb(_config.AccessTypeProbs);
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private int ChooseOffset(AccessType type) => type switch
|
|
|
|
|
{
|
|
|
|
|
AccessType.Random => _random.Next(0, _size),
|
|
|
|
|
AccessType.Sequential => _lastAccessEnd == _size ? 0 : _lastAccessEnd,
|
|
|
|
|
AccessType.FrequentBlock => _frequentAccessOffsets[_random.Next(0, _frequentAccessOffsets.Length)],
|
|
|
|
|
_ => 0
|
|
|
|
|
};
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private int ChooseSize(int offset, int sizeClass)
|
|
|
|
|
{
|
|
|
|
|
int availableSize = Math.Max(0, _size - offset);
|
|
|
|
|
int randSize = _random.Next(0, _config.SizeClassMaxSizes[sizeClass]);
|
|
|
|
|
return Math.Min(availableSize, randSize);
|
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
private int ChooseProb(int[] weights)
|
|
|
|
|
{
|
|
|
|
|
int total = 0;
|
|
|
|
|
foreach (int weight in weights)
|
|
|
|
|
{
|
|
|
|
|
total += weight;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
int rand = _random.Next(0, total);
|
|
|
|
|
int currentThreshold = 0;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
for (int i = 0; i < weights.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
currentThreshold += weights[i];
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
if (rand < currentThreshold)
|
|
|
|
|
return i;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool CompareBuffers(byte[][] buffers, int size)
|
|
|
|
|
{
|
|
|
|
|
Span<byte> baseBuffer = buffers[0].AsSpan(0, size);
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
for (int i = 1; i < buffers.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
Span<byte> testBuffer = buffers[i].AsSpan(0, size);
|
|
|
|
|
if (!baseBuffer.SequenceEqual(testBuffer))
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
return false;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2021-01-17 08:30:51 +01:00
|
|
|
|
|
2021-11-14 20:08:57 +01:00
|
|
|
|
public readonly struct Entry
|
|
|
|
|
{
|
|
|
|
|
public readonly IStorage Storage;
|
|
|
|
|
public readonly byte[] BackingArray;
|
|
|
|
|
|
|
|
|
|
public Entry(IStorage storage, byte[] backingArray)
|
2021-01-17 08:30:51 +01:00
|
|
|
|
{
|
2021-11-14 20:08:57 +01:00
|
|
|
|
Storage = storage;
|
|
|
|
|
BackingArray = backingArray;
|
2021-01-17 08:30:51 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-14 20:08:57 +01:00
|
|
|
|
|
|
|
|
|
private enum Task
|
|
|
|
|
{
|
|
|
|
|
Read = 0,
|
|
|
|
|
Write = 1,
|
|
|
|
|
Flush = 2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private enum AccessType
|
|
|
|
|
{
|
|
|
|
|
Random = 0,
|
|
|
|
|
Sequential = 1,
|
|
|
|
|
FrequentBlock = 2
|
|
|
|
|
}
|
2022-11-12 02:21:07 +01:00
|
|
|
|
}
|