feat: Calc benchmarks for hive operations on init

This commit is contained in:
Krille Fear 2021-10-06 10:33:22 +02:00
parent a7818bbd0f
commit 3603dae312
2 changed files with 165 additions and 116 deletions

View File

@ -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 {

View File

@ -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;
}