Skip to content

Commit 1b4049d

Browse files
authored
Akavache Version 11 - New version (Breaking Change) (#1040)
* Step 1 Add new code * Update Akavache.sln * Ensure All functions have equivalents * Update * Refactor InMemoryBlobCache to reside in BSON library * 4 tests remain to be fixed * Updated tests * Update Serializers * Update SqliteBlobCache.cs * Improve Serializer handling * Update DateTime Converters * Updates for AoT * Update to resolve path * Update SettingsCacheTests.cs * Start to remove verified original code * Add further tests * Fix Concurrency in tests * Add structural folders for tests * Remove Splat where possible * Update BlobCacheTestsBase.cs * Update InMemoryBlobCache instances * Update SettingsBase.cs * Add Json Time handlers * Update Serializers * Update tests and remove original Akavache * All tests can pass, some timing issues exist that may mean some fail, tweaking of time tolerances required * Prepare for final version * Make single target for settings tests * Minor Formatting fixes and optimisations * AOT markup added to missing elements * Configure Parallellism update tests to be thread safe * Add comprehensive unit tests and improve test isolation Added new test files for AOT compatibility, bitmap image extensions, core registrations, and other Akavache features. Enhanced SettingsCacheTests to use unique test names for isolation and added tests for encrypted settings persistence, password validation, multiple disposals, and serializer property management. Updated Akavache.Tests.csproj to reference Akavache.Drawing. Minor fix in LoginExtensions to use value tuple instead of Tuple for login data. * Update to handle override for database path * Remove empty tests apps, update tests * Fix Argument Validation * Update Insert * Update Serializer Extension Tests * Skip some unreliable tests
1 parent 682d432 commit 1b4049d

File tree

396 files changed

+21037
-15274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

396 files changed

+21037
-15274
lines changed

src/Akavache.Benchmarks/.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# editorconfig.org
2+
# override for benchmarks.
3+
4+
# top-most EditorConfig file
5+
root = true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>net9.0</TargetFrameworks>
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\Akavache.Core\Akavache.Core.csproj" />
15+
<ProjectReference Include="..\Akavache.EncryptedSqlite3\Akavache.EncryptedSqlite3.csproj" />
16+
<ProjectReference Include="..\Akavache.Sqlite3\Akavache.Sqlite3.csproj" />
17+
<ProjectReference Include="..\Akavache.SystemTextJson\Akavache.SystemTextJson.csproj" />
18+
</ItemGroup>
19+
</Project>
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Reactive.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
using BenchmarkDotNet.Attributes;
11+
using BenchmarkDotNet.Configs;
12+
using BenchmarkDotNet.Jobs;
13+
14+
using Akavache.Core;
15+
using Akavache.SystemTextJson;
16+
using System.Reactive.Threading.Tasks;
17+
18+
namespace Akavache.Benchmarks
19+
{
20+
[SimpleJob(RuntimeMoniker.Net90)]
21+
[MemoryDiagnoser]
22+
[MarkdownExporterAttribute.GitHub]
23+
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
24+
public class CacheDatabaseReadBenchmarks
25+
{
26+
private readonly Random _randomNumberGenerator = new Random();
27+
private string _tempDirectory;
28+
private IDisposable _directoryCleanup;
29+
30+
[Params(10, 100, 1000)]
31+
public int BenchmarkSize { get; set; }
32+
33+
[GlobalSetup]
34+
public void GlobalSetup()
35+
{
36+
// Initialize the serializer first
37+
CoreRegistrations.Serializer = new SystemJsonSerializer();
38+
39+
// Create temporary directory
40+
_directoryCleanup = Utility.WithEmptyDirectory(out _tempDirectory);
41+
42+
// Generate database synchronously to avoid deadlocks
43+
BlobCache = GenerateAGiantDatabaseSync(_tempDirectory);
44+
Keys = BlobCache.GetAllKeys().ToList().FirstAsync().GetAwaiter().GetResult();
45+
Size = BenchmarkSize;
46+
}
47+
48+
[GlobalCleanup]
49+
public void GlobalCleanup()
50+
{
51+
BlobCache?.Dispose();
52+
_directoryCleanup?.Dispose();
53+
}
54+
55+
public IBlobCache BlobCache { get; set; }
56+
57+
public int Size { get; set; }
58+
59+
public IList<string> Keys { get; set; }
60+
61+
[Benchmark]
62+
[BenchmarkCategory("Read")]
63+
public async Task SequentialRead()
64+
{
65+
var toFetch = Enumerable.Range(0, Size)
66+
.Select(_ => Keys[_randomNumberGenerator.Next(0, Keys.Count - 1)])
67+
.ToArray();
68+
69+
foreach (var v in toFetch)
70+
{
71+
await BlobCache.Get(v);
72+
}
73+
}
74+
75+
[Benchmark]
76+
[BenchmarkCategory("Read")]
77+
public async Task RandomRead()
78+
{
79+
var tasks = new List<Task>();
80+
81+
for (int i = 0; i < Size; i++)
82+
{
83+
var randomKey = Keys[_randomNumberGenerator.Next(0, Keys.Count - 1)];
84+
tasks.Add(BlobCache.Get(randomKey).FirstAsync().ToTask());
85+
}
86+
87+
await Task.WhenAll(tasks);
88+
}
89+
90+
[Benchmark]
91+
[BenchmarkCategory("Read")]
92+
public async Task BulkRead()
93+
{
94+
var toFetch = Enumerable.Range(0, Size)
95+
.Select(_ => Keys[_randomNumberGenerator.Next(0, Keys.Count - 1)])
96+
.ToArray();
97+
98+
await BlobCache.Get(toFetch).ToList().FirstAsync();
99+
}
100+
101+
/// <summary>
102+
/// Generates a giant database synchronously for GlobalSetup.
103+
/// </summary>
104+
/// <param name="path">A path to use for generating it.</param>
105+
/// <returns>The blob cache.</returns>
106+
private IBlobCache GenerateAGiantDatabaseSync(string path)
107+
{
108+
try
109+
{
110+
path ??= GetIntegrationTestRootDirectory();
111+
112+
var giantDbSize = Math.Max(1000, BenchmarkSize * 10); // Ensure enough data for benchmarks
113+
var cache = new Sqlite3.SqliteBlobCache(Path.Combine(path, "benchmarks-read.db"));
114+
115+
var keys = cache.GetAllKeys().ToList().FirstAsync().GetAwaiter().GetResult();
116+
if (keys.Count >= giantDbSize)
117+
{
118+
return cache;
119+
}
120+
121+
cache.InvalidateAll().FirstAsync().GetAwaiter().GetResult();
122+
123+
// Generate smaller chunks to avoid memory issues
124+
var ret = new List<string>();
125+
var remaining = giantDbSize;
126+
127+
while (remaining > 0)
128+
{
129+
var chunkSize = Math.Min(500, remaining); // Process in reasonable chunks
130+
var toWrite = PerfHelper.GenerateRandomDatabaseContents(chunkSize);
131+
132+
cache.Insert(toWrite).FirstAsync().GetAwaiter().GetResult();
133+
134+
foreach (var k in toWrite.Keys)
135+
{
136+
ret.Add(k);
137+
}
138+
139+
remaining -= chunkSize;
140+
141+
if (remaining % 2000 == 0 || remaining == 0)
142+
{
143+
Console.WriteLine($"Generated {giantDbSize - remaining}/{giantDbSize} items");
144+
}
145+
}
146+
147+
return cache;
148+
}
149+
catch (Exception ex)
150+
{
151+
Console.WriteLine($"Error in GenerateAGiantDatabaseSync: {ex.Message}");
152+
throw;
153+
}
154+
}
155+
156+
/// <summary>
157+
/// Gets the root folder for the integration tests.
158+
/// </summary>
159+
/// <returns>The root folder.</returns>
160+
public static string GetIntegrationTestRootDirectory()
161+
{
162+
// XXX: This is an evil hack, but it's okay for a unit test
163+
// We can't use Assembly.Location because unit test runners love
164+
// to move stuff to temp directories
165+
var st = new StackFrame(true);
166+
var di = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(st.GetFileName())));
167+
168+
return di.FullName;
169+
}
170+
}
171+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<Project>
2+
</Project>

src/Akavache.Benchmarks/PerfHelper.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reactive.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
using Akavache.Core;
9+
10+
namespace Akavache.Benchmarks
11+
{
12+
internal static class PerfHelper
13+
{
14+
private static readonly Random _randomNumberGenerator = new();
15+
16+
/// <summary>
17+
/// Tests generating a database.
18+
/// </summary>
19+
/// <param name="targetCache">The target blob cache.</param>
20+
/// <param name="size">The number of items to generate.</param>
21+
/// <returns>A list of generated items.</returns>
22+
public static async Task<List<string>> GenerateDatabase(IBlobCache targetCache, int size)
23+
{
24+
var ret = new List<string>();
25+
26+
// Write out in groups of 4096
27+
while (size > 0)
28+
{
29+
var toWriteSize = Math.Min(4096, size);
30+
var toWrite = GenerateRandomDatabaseContents(toWriteSize);
31+
32+
await targetCache.Insert(toWrite);
33+
34+
foreach (var k in toWrite.Keys)
35+
{
36+
ret.Add(k);
37+
}
38+
39+
size -= toWrite.Count;
40+
Console.WriteLine(size);
41+
}
42+
43+
return ret;
44+
}
45+
46+
/// <summary>
47+
/// Generate the contents of the database.
48+
/// </summary>
49+
/// <param name="toWriteSize">The size of the database to write.</param>
50+
/// <returns>A dictionary of the contents.</returns>
51+
public static Dictionary<string, byte[]> GenerateRandomDatabaseContents(int toWriteSize)
52+
{
53+
return Enumerable.Range(0, toWriteSize)
54+
.Select(_ => GenerateRandomKey())
55+
.Distinct()
56+
.ToDictionary(k => k, _ => GenerateRandomBytes());
57+
}
58+
59+
/// <summary>
60+
/// Generate random bytes for a value.
61+
/// </summary>
62+
/// <returns>The generated random bytes.</returns>
63+
public static byte[] GenerateRandomBytes()
64+
{
65+
var ret = new byte[_randomNumberGenerator.Next(1, 256)];
66+
67+
_randomNumberGenerator.NextBytes(ret);
68+
return ret;
69+
}
70+
71+
/// <summary>
72+
/// Generates a random key for the database.
73+
/// </summary>
74+
/// <returns>The random key.</returns>
75+
public static string GenerateRandomKey()
76+
{
77+
var bytes = GenerateRandomBytes();
78+
79+
// NB: Mask off the MSB and set bit 5 so we always end up with
80+
// valid UTF-8 characters that aren't control characters
81+
for (int i = 0; i < bytes.Length; i++)
82+
{
83+
bytes[i] = (byte)((bytes[i] & 0x7F) | 0x20);
84+
}
85+
86+
return Encoding.UTF8.GetString(bytes, 0, Math.Min(256, bytes.Length));
87+
}
88+
89+
/// <summary>
90+
/// Gets a series of size values to use in generating performance tests.
91+
/// </summary>
92+
/// <returns>The range of sizes.</returns>
93+
public static int[] GetPerfRanges()
94+
{
95+
return [1, 10, 100, 1000, 10000, 100000,];
96+
}
97+
}
98+
}

src/Akavache.Benchmarks/Program.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2019-2022 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using BenchmarkDotNet.Running;
6+
7+
namespace Akavache.Benchmarks
8+
{
9+
/// <summary>
10+
/// Main entry point class.
11+
/// </summary>
12+
public static class Program
13+
{
14+
/// <summary>
15+
/// Main entry point.
16+
/// </summary>
17+
/// <param name="args">Arguments.</param>
18+
public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
19+
}
20+
}

0 commit comments

Comments
 (0)