feat: Calc benchmarks for hive operations on init
This commit is contained in:
parent
a7818bbd0f
commit
3603dae312
|
|
@ -29,6 +29,7 @@ import 'dart:typed_data';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:matrix/src/utils/queued_to_device_event.dart';
|
import 'package:matrix/src/utils/queued_to_device_event.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||||
|
|
||||||
/// This is a basic database for the Matrix SDK using the hive store. You need
|
/// This is a basic database for the Matrix SDK using the hive store. You need
|
||||||
/// to make sure that you perform `Hive.init()` or `Hive.flutterInit()` before
|
/// to make sure that you perform `Hive.init()` or `Hive.flutterInit()` before
|
||||||
|
|
@ -319,28 +320,31 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, BasicEvent>> getAccountData() async {
|
Future<Map<String, BasicEvent>> getAccountData() =>
|
||||||
final accountData = <String, BasicEvent>{};
|
runBenchmarked<Map<String, BasicEvent>>('Get all account data from Hive',
|
||||||
for (final key in _accountDataBox.keys) {
|
() async {
|
||||||
final raw = await _accountDataBox.get(key);
|
final accountData = <String, BasicEvent>{};
|
||||||
accountData[key.toString().fromHiveKey] = BasicEvent(
|
for (final key in _accountDataBox.keys) {
|
||||||
type: key.toString().fromHiveKey,
|
final raw = await _accountDataBox.get(key);
|
||||||
content: convertToJson(raw),
|
accountData[key.toString().fromHiveKey] = BasicEvent(
|
||||||
);
|
type: key.toString().fromHiveKey,
|
||||||
}
|
content: convertToJson(raw),
|
||||||
return accountData;
|
);
|
||||||
}
|
}
|
||||||
|
return accountData;
|
||||||
|
}, _accountDataBox.keys.length);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>?> getClient(String name) async {
|
Future<Map<String, dynamic>?> getClient(String name) =>
|
||||||
final map = <String, dynamic>{};
|
runBenchmarked('Get Client from Hive', () async {
|
||||||
for (final key in _clientBox.keys) {
|
final map = <String, dynamic>{};
|
||||||
if (key == 'version') continue;
|
for (final key in _clientBox.keys) {
|
||||||
map[key] = await _clientBox.get(key);
|
if (key == 'version') continue;
|
||||||
}
|
map[key] = await _clientBox.get(key);
|
||||||
if (map.isEmpty) return null;
|
}
|
||||||
return map;
|
if (map.isEmpty) return null;
|
||||||
}
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Event?> getEventById(String eventId, Room room) async {
|
Future<Event?> getEventById(String eventId, Room room) async {
|
||||||
|
|
@ -482,77 +486,80 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Room>> getRoomList(Client client) async {
|
Future<List<Room>> getRoomList(Client client) =>
|
||||||
final rooms = <String, Room>{};
|
runBenchmarked<List<Room>>('Get room list from hive', () async {
|
||||||
final importantRoomStates = client.importantStateEvents;
|
final rooms = <String, Room>{};
|
||||||
for (final key in _roomsBox.keys) {
|
final importantRoomStates = client.importantStateEvents;
|
||||||
// Get the room
|
for (final key in _roomsBox.keys) {
|
||||||
final raw = await _roomsBox.get(key);
|
// Get the room
|
||||||
final room = Room.fromJson(convertToJson(raw), client);
|
final raw = await _roomsBox.get(key);
|
||||||
|
final room = Room.fromJson(convertToJson(raw), client);
|
||||||
|
|
||||||
// let's see if we need any m.room.member events
|
// let's see if we need any m.room.member events
|
||||||
// We always need the member event for ourself
|
// We always need the member event for ourself
|
||||||
final membersToPostload = <String>{client.userID};
|
final membersToPostload = <String>{client.userID};
|
||||||
// If the room is a direct chat, those IDs should be there too
|
// If the room is a direct chat, those IDs should be there too
|
||||||
if (room.isDirectChat) membersToPostload.add(room.directChatMatrixID);
|
if (room.isDirectChat) membersToPostload.add(room.directChatMatrixID);
|
||||||
// the lastEvent message preview might have an author we need to fetch, if it is a group chat
|
// the lastEvent message preview might have an author we need to fetch, if it is a group chat
|
||||||
if (room.getState(EventTypes.Message) != null && !room.isDirectChat) {
|
if (room.getState(EventTypes.Message) != null && !room.isDirectChat) {
|
||||||
membersToPostload.add(room.getState(EventTypes.Message).senderId);
|
membersToPostload.add(room.getState(EventTypes.Message).senderId);
|
||||||
}
|
}
|
||||||
// if the room has no name and no canonical alias, its name is calculated
|
// if the room has no name and no canonical alias, its name is calculated
|
||||||
// based on the heroes of the room
|
// based on the heroes of the room
|
||||||
if (room.getState(EventTypes.RoomName) == null &&
|
if (room.getState(EventTypes.RoomName) == null &&
|
||||||
room.getState(EventTypes.RoomCanonicalAlias) == null) {
|
room.getState(EventTypes.RoomCanonicalAlias) == null) {
|
||||||
// we don't have a name and no canonical alias, so we'll need to
|
// we don't have a name and no canonical alias, so we'll need to
|
||||||
// post-load the heroes
|
// post-load the heroes
|
||||||
membersToPostload.addAll(room.summary?.mHeroes ?? []);
|
membersToPostload.addAll(room.summary?.mHeroes ?? []);
|
||||||
}
|
}
|
||||||
// Load members
|
// Load members
|
||||||
for (final userId in membersToPostload) {
|
for (final userId in membersToPostload) {
|
||||||
final state =
|
final state =
|
||||||
await _roomMembersBox.get(MultiKey(room.id, userId).toString());
|
await _roomMembersBox.get(MultiKey(room.id, userId).toString());
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
Logs().w('Unable to post load member $userId');
|
Logs().w('Unable to post load member $userId');
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
room.setState(Event.fromJson(convertToJson(state), room));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the "important" room states. All other states will be loaded once
|
||||||
|
// `getUnimportantRoomStates()` is called.
|
||||||
|
for (final type in importantRoomStates) {
|
||||||
|
final states = await _roomStateBox
|
||||||
|
.get(MultiKey(room.id, type).toString()) as Map?;
|
||||||
|
if (states == null) continue;
|
||||||
|
final stateEvents = states.values
|
||||||
|
.map((raw) => Event.fromJson(convertToJson(raw), room))
|
||||||
|
.toList();
|
||||||
|
for (final state in stateEvents) {
|
||||||
|
room.setState(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the list and continue.
|
||||||
|
rooms[room.id] = room;
|
||||||
}
|
}
|
||||||
room.setState(Event.fromJson(convertToJson(state), room));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the "important" room states. All other states will be loaded once
|
// Get the room account data
|
||||||
// `getUnimportantRoomStates()` is called.
|
for (final key in _roomAccountDataBox.keys) {
|
||||||
for (final type in importantRoomStates) {
|
final roomId = MultiKey.fromString(key).parts.first;
|
||||||
final states =
|
if (rooms.containsKey(roomId)) {
|
||||||
await _roomStateBox.get(MultiKey(room.id, type).toString()) as Map?;
|
final raw = await _roomAccountDataBox.get(key);
|
||||||
if (states == null) continue;
|
final basicRoomEvent = BasicRoomEvent.fromJson(
|
||||||
final stateEvents = states.values
|
convertToJson(raw),
|
||||||
.map((raw) => Event.fromJson(convertToJson(raw), room))
|
);
|
||||||
.toList();
|
rooms[roomId]!.roomAccountData[basicRoomEvent.type] =
|
||||||
for (final state in stateEvents) {
|
basicRoomEvent;
|
||||||
room.setState(state);
|
} else {
|
||||||
|
Logs().w(
|
||||||
|
'Found account data for unknown room $roomId. Delete now...');
|
||||||
|
await _roomAccountDataBox.delete(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add to the list and continue.
|
return rooms.values.toList();
|
||||||
rooms[room.id] = room;
|
}, _roomsBox.keys.length);
|
||||||
}
|
|
||||||
|
|
||||||
// Get the room account data
|
|
||||||
for (final key in _roomAccountDataBox.keys) {
|
|
||||||
final roomId = MultiKey.fromString(key).parts.first;
|
|
||||||
if (rooms.containsKey(roomId)) {
|
|
||||||
final raw = await _roomAccountDataBox.get(key);
|
|
||||||
final basicRoomEvent = BasicRoomEvent.fromJson(
|
|
||||||
convertToJson(raw),
|
|
||||||
);
|
|
||||||
rooms[roomId]!.roomAccountData[basicRoomEvent.type] = basicRoomEvent;
|
|
||||||
} else {
|
|
||||||
Logs().w('Found account data for unknown room $roomId. Delete now...');
|
|
||||||
await _roomAccountDataBox.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rooms.values.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SSSSCache?> getSSSSCache(String type) async {
|
Future<SSSSCache?> getSSSSCache(String type) async {
|
||||||
|
|
@ -595,36 +602,38 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, DeviceKeysList>> getUserDeviceKeys(Client client) async {
|
Future<Map<String, DeviceKeysList>> getUserDeviceKeys(Client client) =>
|
||||||
final deviceKeysOutdated = _userDeviceKeysOutdatedBox.keys;
|
runBenchmarked<Map<String, DeviceKeysList>>(
|
||||||
if (deviceKeysOutdated.isEmpty) {
|
'Get all user device keys from Hive', () async {
|
||||||
return {};
|
final deviceKeysOutdated = _userDeviceKeysOutdatedBox.keys;
|
||||||
}
|
if (deviceKeysOutdated.isEmpty) {
|
||||||
final res = <String, DeviceKeysList>{};
|
return {};
|
||||||
for (final userId in deviceKeysOutdated) {
|
}
|
||||||
final deviceKeysBoxKeys = _userDeviceKeysBox.keys.where((tuple) {
|
final res = <String, DeviceKeysList>{};
|
||||||
final tupleKey = MultiKey.fromString(tuple);
|
for (final userId in deviceKeysOutdated) {
|
||||||
return tupleKey.parts.first == userId;
|
final deviceKeysBoxKeys = _userDeviceKeysBox.keys.where((tuple) {
|
||||||
});
|
final tupleKey = MultiKey.fromString(tuple);
|
||||||
final crossSigningKeysBoxKeys =
|
return tupleKey.parts.first == userId;
|
||||||
_userCrossSigningKeysBox.keys.where((tuple) {
|
});
|
||||||
final tupleKey = MultiKey.fromString(tuple);
|
final crossSigningKeysBoxKeys =
|
||||||
return tupleKey.parts.first == userId;
|
_userCrossSigningKeysBox.keys.where((tuple) {
|
||||||
});
|
final tupleKey = MultiKey.fromString(tuple);
|
||||||
res[userId] = DeviceKeysList.fromDbJson(
|
return tupleKey.parts.first == userId;
|
||||||
{
|
});
|
||||||
'client_id': client.id,
|
res[userId] = DeviceKeysList.fromDbJson(
|
||||||
'user_id': userId,
|
{
|
||||||
'outdated': await _userDeviceKeysOutdatedBox.get(userId),
|
'client_id': client.id,
|
||||||
},
|
'user_id': userId,
|
||||||
await Future.wait(deviceKeysBoxKeys.map(
|
'outdated': await _userDeviceKeysOutdatedBox.get(userId),
|
||||||
(key) async => convertToJson(await _userDeviceKeysBox.get(key)))),
|
},
|
||||||
await Future.wait(crossSigningKeysBoxKeys.map((key) async =>
|
await Future.wait(deviceKeysBoxKeys.map((key) async =>
|
||||||
convertToJson(await _userCrossSigningKeysBox.get(key)))),
|
convertToJson(await _userDeviceKeysBox.get(key)))),
|
||||||
client);
|
await Future.wait(crossSigningKeysBoxKeys.map((key) async =>
|
||||||
}
|
convertToJson(await _userCrossSigningKeysBox.get(key)))),
|
||||||
return res;
|
client);
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
|
}, _userDeviceKeysBox.keys.length);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<User>> getUsers(Room room) async {
|
Future<List<User>> getUsers(Room room) async {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Famedly Matrix SDK
|
||||||
|
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
/// Calculates some benchmarks for this function. Give it a [name] and a [func]
|
||||||
|
/// to call and it will calculate the needed milliseconds. Give it an optional
|
||||||
|
/// [itemCount] to let it also calculate the needed milliseconds per item.
|
||||||
|
Future<T> runBenchmarked<T>(
|
||||||
|
String name,
|
||||||
|
Future<T> Function() func, [
|
||||||
|
int? itemCount,
|
||||||
|
]) async {
|
||||||
|
final start = DateTime.now();
|
||||||
|
final result = await func();
|
||||||
|
final milliseconds =
|
||||||
|
DateTime.now().millisecondsSinceEpoch - start.millisecondsSinceEpoch;
|
||||||
|
var message = 'Benchmark: $name -> $milliseconds ms';
|
||||||
|
if (itemCount != null) {
|
||||||
|
message +=
|
||||||
|
' ($itemCount items, ${itemCount > 0 ? milliseconds / itemCount : milliseconds} ms/item)';
|
||||||
|
}
|
||||||
|
Logs().v(message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue