chore: Move the drift_offline_first to legacy in favor of Serverpod implementation (#32)
This commit is contained in:
parent
027aec5ed0
commit
cbc472523b
6 changed files with 13575 additions and 284 deletions
99
legacy/drift_offline_sync/benchmark/utils/benchmark.dart
Normal file
99
legacy/drift_offline_sync/benchmark/utils/benchmark.dart
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
||||
import 'package:drift_offline_sync/src/database/triggers.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'database.dart';
|
||||
|
||||
export '../../lib/src/database/triggers.dart' show Operation;
|
||||
|
||||
class CrdtBenchmark extends AsyncBenchmarkBase {
|
||||
CrdtBenchmark(
|
||||
super.name, {
|
||||
required this.crdtEnabled,
|
||||
required this.operation,
|
||||
required this.rowCount,
|
||||
});
|
||||
|
||||
final bool crdtEnabled;
|
||||
final Operation operation;
|
||||
final int rowCount;
|
||||
|
||||
late final CrdtBenchmarkDatabase _db;
|
||||
late final File _dbFile;
|
||||
|
||||
int _lastDatabaseSize = 0;
|
||||
int _lastRowsCount = 0;
|
||||
|
||||
@override
|
||||
Future<void> setup() async {
|
||||
final dbPath = p.join(Directory.systemTemp.path, 'benchmark_$name.db');
|
||||
_dbFile = File(dbPath);
|
||||
_db = CrdtBenchmarkDatabase.createDatabase(
|
||||
_dbFile,
|
||||
crdtEnabled: crdtEnabled,
|
||||
);
|
||||
|
||||
// Force the migration to be applied during setup.
|
||||
await _db.batchInsertTestRows(1);
|
||||
await _db.deleteTestRows(1);
|
||||
await _db.managers.tableWithEveryColumnType.delete();
|
||||
|
||||
if (operation != Operation.insert) {
|
||||
await _db.batchInsertTestRows(rowCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// The default warmup is to invoke the [run] method once, but this pollutes the
|
||||
/// results by persisting data before the run.
|
||||
@override
|
||||
Future<void> warmup() async {}
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
_lastDatabaseSize = 0;
|
||||
_lastRowsCount = 0;
|
||||
switch (operation) {
|
||||
case Operation.insert:
|
||||
case Operation.update:
|
||||
await _db.upsertTestRows(rowCount);
|
||||
case Operation.delete:
|
||||
await _db.deleteTestRows(rowCount);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<(double, int)> report() async {
|
||||
final averageMicroseconds = await measure();
|
||||
return (averageMicroseconds, measureStorage());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> teardown() async {
|
||||
_lastDatabaseSize = _dbFile.lengthSync();
|
||||
_lastRowsCount = await _db.testRowsCount();
|
||||
await _db.close();
|
||||
if (_dbFile.existsSync()) {
|
||||
_dbFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
int measureStorage() {
|
||||
if (operation == Operation.delete) {
|
||||
return _lastDatabaseSize;
|
||||
}
|
||||
|
||||
if (_lastDatabaseSize == 0) {
|
||||
throw Exception('Last database size is not set.');
|
||||
}
|
||||
if (_lastRowsCount < rowCount) {
|
||||
throw Exception(
|
||||
'Rows count is $_lastRowsCount but expected at least $rowCount.',
|
||||
);
|
||||
}
|
||||
// If the benchmark ran for more than one loop, it can contain a greater number
|
||||
// of rows than the expected. We need to normalize it to the expected total.
|
||||
return _lastDatabaseSize ~/ (_lastRowsCount / rowCount);
|
||||
}
|
||||
}
|
||||
48
legacy/drift_offline_sync/benchmark/utils/conversion.dart
Normal file
48
legacy/drift_offline_sync/benchmark/utils/conversion.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:intl/intl.dart';
|
||||
|
||||
final formatter0 = NumberFormat.decimalPatternDigits(decimalDigits: 0);
|
||||
final formatter2 = NumberFormat.decimalPatternDigits(decimalDigits: 2);
|
||||
final formatter3 = NumberFormat.decimalPatternDigits(decimalDigits: 3);
|
||||
|
||||
extension FormattedStorageSize on num {
|
||||
String toFormattedDuration() {
|
||||
var time = this;
|
||||
var unit = 1;
|
||||
|
||||
while (time.abs() > 1000 && unit < Duration.microsecondsPerSecond) {
|
||||
time /= 1000;
|
||||
unit *= 1000;
|
||||
}
|
||||
while (unit > Duration.microsecondsPerMinute && time.abs() > 60) {
|
||||
time /= 60;
|
||||
unit *= 60;
|
||||
}
|
||||
return '${formatter2.format(time)} ${switch (unit) {
|
||||
1 => 'μs',
|
||||
Duration.millisecondsPerSecond => 'ms',
|
||||
Duration.microsecondsPerSecond => 's',
|
||||
Duration.microsecondsPerMinute => 'min',
|
||||
Duration.microsecondsPerHour => 'h',
|
||||
_ => throw Exception('Unexpected time unit: $unit'),
|
||||
}}';
|
||||
}
|
||||
|
||||
String toFormattedStorageSize() {
|
||||
var storageIncrease = toDouble();
|
||||
var storageUnit = 1;
|
||||
|
||||
while (storageIncrease > 1024) {
|
||||
storageIncrease /= 1024;
|
||||
storageUnit *= 1024;
|
||||
}
|
||||
|
||||
final storageUnitString = switch (storageUnit) {
|
||||
1 => 'B',
|
||||
1024 => 'KB',
|
||||
const (1024 * 1024) => 'MB',
|
||||
_ => throw Exception('Unexpected storage unit: $storageUnit'),
|
||||
};
|
||||
|
||||
return '${formatter2.format(storageIncrease)} $storageUnitString';
|
||||
}
|
||||
}
|
||||
95
legacy/drift_offline_sync/benchmark/utils/database.dart
Normal file
95
legacy/drift_offline_sync/benchmark/utils/database.dart
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:drift_offline_sync/drift_offline_sync.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../test/utils/tables.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(tables: [TableWithEveryColumnType])
|
||||
class CrdtBenchmarkDatabase extends _$CrdtBenchmarkDatabase {
|
||||
CrdtBenchmarkDatabase(super.e, {required this.crdtEnabled}) {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
}
|
||||
|
||||
static CrdtBenchmarkDatabase createDatabase(
|
||||
File file, {
|
||||
required bool crdtEnabled,
|
||||
}) {
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
}
|
||||
|
||||
return CrdtBenchmarkDatabase(
|
||||
NativeDatabase.opened(sqlite3.open(file.path)),
|
||||
crdtEnabled: crdtEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
final bool crdtEnabled;
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
@override
|
||||
Migrator createMigrator() => !crdtEnabled
|
||||
? super.createMigrator()
|
||||
: OfflineSyncMigrator(
|
||||
this,
|
||||
userId: const Uuid().v7(),
|
||||
nodeId: const Uuid().v7(),
|
||||
synchronizedTables: [tableWithEveryColumnType],
|
||||
);
|
||||
|
||||
Future<void> batchInsertTestRows(int count) async {
|
||||
final baseTimestamp = DateTime.now();
|
||||
await managers.tableWithEveryColumnType.bulkCreate(
|
||||
(_) => List.generate(count, (i) => _createTestRow(i, baseTimestamp)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> upsertTestRows(int count) async {
|
||||
final baseTimestamp = DateTime.now();
|
||||
for (var i = 0; i < count; i++) {
|
||||
await managers.tableWithEveryColumnType.create(
|
||||
(_) => _createTestRow(i, baseTimestamp),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteTestRows(int count) async {
|
||||
for (var i = 0; i < count; i++) {
|
||||
await managers.tableWithEveryColumnType
|
||||
.filter((t) => t.id.equals(RowId(i)))
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> testRowsCount() async {
|
||||
return managers.tableWithEveryColumnType.count();
|
||||
}
|
||||
|
||||
TableWithEveryColumnTypeCompanion _createTestRow(
|
||||
int i,
|
||||
DateTime baseTimestamp,
|
||||
) {
|
||||
return TableWithEveryColumnTypeCompanion(
|
||||
id: Value(RowId(i)),
|
||||
aBool: Value(i.isEven),
|
||||
aDateTime: Value(baseTimestamp.add(Duration(seconds: i))),
|
||||
aText: Value('Text $i'),
|
||||
anInt: Value(i),
|
||||
anInt64: Value(BigInt.from(i)),
|
||||
aReal: Value(i.toDouble()),
|
||||
aBlob: Value(Uint8List.fromList([i % 256])),
|
||||
anIntEnum: const Value(null),
|
||||
aTextWithConverter: const Value(null),
|
||||
aUuid: const Value(null),
|
||||
);
|
||||
}
|
||||
}
|
||||
1075
legacy/drift_offline_sync/benchmark/utils/database.g.dart
generated
Normal file
1075
legacy/drift_offline_sync/benchmark/utils/database.g.dart
generated
Normal file
File diff suppressed because it is too large
Load diff
64
legacy/drift_offline_sync/benchmark/utils/results.dart
Normal file
64
legacy/drift_offline_sync/benchmark/utils/results.dart
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import 'benchmark.dart';
|
||||
import 'conversion.dart';
|
||||
|
||||
void printPerformanceImpact(
|
||||
BenchmarkResults benchmarkResults, {
|
||||
required int rowCount,
|
||||
bool runningInCI = false,
|
||||
}) {
|
||||
print(
|
||||
'${runningInCI ? '```' : ''}'
|
||||
'\n📊 ${benchmarkResults.operation.name.toUpperCase()} performance impact:',
|
||||
);
|
||||
final (baselineTime, _) = benchmarkResults.baseline;
|
||||
final (crdtTime, _) = benchmarkResults.crdt;
|
||||
|
||||
final runDelayUs = crdtTime - baselineTime;
|
||||
final slowdown = runDelayUs / baselineTime * 100;
|
||||
|
||||
final baselineDelay = baselineTime.toFormattedDuration();
|
||||
final crdtDelay = crdtTime.toFormattedDuration();
|
||||
final runDelay = runDelayUs.toFormattedDuration();
|
||||
final runDelayPerOperation = (runDelayUs / rowCount).toFormattedDuration();
|
||||
|
||||
print(' Time: $baselineDelay --> $crdtDelay ($runDelay)');
|
||||
print(' CRDT overhead: ${formatter2.format(slowdown)}% slower');
|
||||
print(' Delay per ${benchmarkResults.operation.name}: $runDelayPerOperation');
|
||||
if (runningInCI) print('```');
|
||||
}
|
||||
|
||||
void printStorageImpact(
|
||||
BenchmarkResults benchmarkResults, {
|
||||
required int rowCount,
|
||||
bool runningInCI = false,
|
||||
}) {
|
||||
print('${runningInCI ? '```' : ''}\n💽 Storage impact:');
|
||||
final (_, baselineSize) = benchmarkResults.baseline;
|
||||
final (_, crdtSize) = benchmarkResults.crdt;
|
||||
|
||||
final storageIncrease = crdtSize - baselineSize;
|
||||
final storageIncPercent = storageIncrease / baselineSize * 100;
|
||||
final extraStoragePerRow = storageIncrease / rowCount;
|
||||
final storageIncreaseVolume = storageIncPercent > 100
|
||||
? '(x${formatter2.format(storageIncPercent / 100)})'
|
||||
: '(+${(crdtSize - baselineSize).toFormattedStorageSize()})';
|
||||
print(
|
||||
' Storage size: ${baselineSize.toFormattedStorageSize()} '
|
||||
'--> ${crdtSize.toFormattedStorageSize()} $storageIncreaseVolume',
|
||||
);
|
||||
print(' CRDT overhead: ${formatter2.format(storageIncPercent)}% increase');
|
||||
print(' Extra storage per row: ${extraStoragePerRow.toFormattedStorageSize()}');
|
||||
if (runningInCI) print('```');
|
||||
}
|
||||
|
||||
class BenchmarkResults {
|
||||
const BenchmarkResults({
|
||||
required this.operation,
|
||||
required this.baseline,
|
||||
required this.crdt,
|
||||
});
|
||||
|
||||
final Operation operation;
|
||||
final (double, int) baseline;
|
||||
final (double, int) crdt;
|
||||
}
|
||||
39
legacy/drift_offline_sync/benchmark/utils/runner.dart
Normal file
39
legacy/drift_offline_sync/benchmark/utils/runner.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:cli_tools/src/logger/helpers/progress.dart';
|
||||
|
||||
Progress? _trackedAnimationInProgress;
|
||||
|
||||
Future<T> runWithProgress<T>(
|
||||
String message,
|
||||
Future<T> Function() runner, {
|
||||
bool Function(T result)? validator,
|
||||
bool skipProgress = false,
|
||||
}) async {
|
||||
if (skipProgress) {
|
||||
return Isolate.run(runner);
|
||||
}
|
||||
|
||||
_stopAnimationInProgress();
|
||||
final progress = Progress(message, stdout);
|
||||
_trackedAnimationInProgress = progress;
|
||||
final result = await Isolate.run(runner);
|
||||
_trackedAnimationInProgress = null;
|
||||
if (validator?.call(result) ?? true) {
|
||||
progress.complete();
|
||||
} else {
|
||||
progress.fail();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void _stopAnimationInProgress() {
|
||||
if (_trackedAnimationInProgress != null) {
|
||||
_trackedAnimationInProgress?.stopAnimation();
|
||||
// Since animation modifies the current line we add a new line so that
|
||||
// the next print doesn't end up on the same line.
|
||||
stdout.write('\n');
|
||||
}
|
||||
_trackedAnimationInProgress = null;
|
||||
}
|
||||
Loading…
Reference in a new issue