diff --git a/lib/spinify.dart b/lib/spinify.dart index b3bab3c..266485f 100644 --- a/lib/spinify.dart +++ b/lib/spinify.dart @@ -15,6 +15,7 @@ export 'package:spinify/src/model/exception.dart'; export 'package:spinify/src/model/history.dart'; export 'package:spinify/src/model/jwt.dart'; export 'package:spinify/src/model/message.dart'; +export 'package:spinify/src/model/metrics.dart'; export 'package:spinify/src/model/presence.dart'; export 'package:spinify/src/model/presence_stats.dart'; export 'package:spinify/src/model/publication.dart'; diff --git a/lib/src/client/spinify.dart b/lib/src/client/spinify.dart index 91307d5..4600143 100644 --- a/lib/src/client/spinify.dart +++ b/lib/src/client/spinify.dart @@ -15,6 +15,7 @@ import 'package:spinify/src/model/event.dart'; import 'package:spinify/src/model/exception.dart'; import 'package:spinify/src/model/history.dart'; import 'package:spinify/src/model/message.dart'; +import 'package:spinify/src/model/metrics.dart'; import 'package:spinify/src/model/presence.dart'; import 'package:spinify/src/model/presence_stats.dart'; import 'package:spinify/src/model/publication.dart'; @@ -65,7 +66,8 @@ final class Spinify extends SpinifyBase SpinifyPresenceMixin, SpinifyHistoryMixin, SpinifyRPCMixin, - SpinifyQueueMixin { + SpinifyQueueMixin, + SpinifyMetricsMixin { /// {@macro spinify} Spinify([SpinifyConfig? config]) : super(config ?? SpinifyConfig.byDefault()); @@ -713,6 +715,42 @@ base mixin SpinifyRPCMixin on SpinifyBase, SpinifyErrorsMixin { } } +/// Responsible for metrics. +/// {@nodoc} +@internal +base mixin SpinifyMetricsMixin on SpinifyBase { + int _connectsTotal = 0, _connectsSuccessful = 0; + + @override + Future connect(String url) async { + _connectsTotal++; + return super.connect(url); + } + + @override + void _onConnected(SpinifyState$Connected state) { + super._onConnected(state); + _connectsSuccessful++; + } + + /// Get metrics of Spinify client. + @override + SpinifyMetrics get metrics { + final timestamp = DateTime.now().toUtc(); + final wsMetrics = _transport.metrics; + return SpinifyMetrics( + timestamp: timestamp, + lastUrl: wsMetrics.lastUrl, + reconnects: (successful: _connectsSuccessful, total: _connectsTotal), + state: state, + receivedCount: wsMetrics.receivedCount, + receivedSize: wsMetrics.receivedSize, + transferredCount: wsMetrics.transferredCount, + transferredSize: wsMetrics.transferredSize, + ); + } +} + /// Mixin responsible for queue. /// SHOULD BE LAST MIXIN. /// {@nodoc} diff --git a/lib/src/client/spinify_interface.dart b/lib/src/client/spinify_interface.dart index 56e40ba..8a6d70a 100644 --- a/lib/src/client/spinify_interface.dart +++ b/lib/src/client/spinify_interface.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:spinify/src/client/state.dart'; import 'package:spinify/src/client/states_stream.dart'; import 'package:spinify/src/model/history.dart'; +import 'package:spinify/src/model/metrics.dart'; import 'package:spinify/src/model/presence.dart'; import 'package:spinify/src/model/presence_stats.dart'; import 'package:spinify/src/model/pushes_stream.dart'; @@ -22,7 +23,8 @@ abstract interface class ISpinify ISpinifyClientSubscriptionsManager, ISpinifyPresenceOwner, ISpinifyHistoryOwner, - ISpinifyRemoteProcedureCall { + ISpinifyRemoteProcedureCall, + ISpinifyMetricsOwner { /// Connect to the server. /// [url] is a URL of endpoint. Future connect(String url); @@ -128,3 +130,9 @@ abstract interface class ISpinifyRemoteProcedureCall { /// Send arbitrary RPC and wait for response. Future> rpc(String method, List data); } + +/// Spinify metrics interface. +abstract interface class ISpinifyMetricsOwner { + /// Get metrics of Spinify client. + SpinifyMetrics get metrics; +} diff --git a/lib/src/model/metrics.dart b/lib/src/model/metrics.dart new file mode 100644 index 0000000..9195f6f --- /dev/null +++ b/lib/src/model/metrics.dart @@ -0,0 +1,67 @@ +import 'package:meta/meta.dart'; +import 'package:spinify/src/client/state.dart'; + +/// {@template metrics} +/// Metrics of Spinify client. +/// {@endtemplate} +/// {@category Client} +/// {@category Entity} +@immutable +final class SpinifyMetrics implements Comparable { + /// {@macro metrics} + const SpinifyMetrics({ + required this.timestamp, + required this.state, + required this.transferredSize, + required this.receivedSize, + required this.reconnects, + required this.transferredCount, + required this.receivedCount, + required this.lastUrl, + }); + + /// Timestamp of the metrics. + final DateTime timestamp; + + /// The current state of the client. + final SpinifyState state; + + /// The total number of bytes sent. + final BigInt transferredSize; + + /// The total number of bytes received. + final BigInt receivedSize; + + /// The total number of times the connection has been re-established. + final ({int successful, int total}) reconnects; + + /// The total number of messages sent. + final BigInt transferredCount; + + /// The total number of messages received. + final BigInt receivedCount; + + /// The last URL used to connect. + final String? lastUrl; + + @override + int compareTo(SpinifyMetrics other) => timestamp.compareTo(other.timestamp); + + /// Convert metrics to JSON. + Map toJson() => { + 'timestamp': timestamp, + 'state': state.toJson(), + 'reconnects': { + 'successful': reconnects.successful, + 'total': reconnects.total, + }, + 'transferredSize': transferredSize, + 'receivedSize': receivedSize, + 'transferredCount': transferredCount, + 'receivedCount': receivedCount, + 'lastUrl': lastUrl, + }; + + @override + String toString() => 'SpinifyMetrics{}'; +} diff --git a/lib/src/transport/transport_interface.dart b/lib/src/transport/transport_interface.dart index ba544b7..aed4316 100644 --- a/lib/src/transport/transport_interface.dart +++ b/lib/src/transport/transport_interface.dart @@ -12,6 +12,7 @@ import 'package:spinify/src/subscription/server_subscription_manager.dart'; import 'package:spinify/src/subscription/subcibed_on_channel.dart'; import 'package:spinify/src/subscription/subscription_config.dart'; import 'package:spinify/src/util/notifier.dart'; +import 'package:ws/ws.dart'; /// Class responsible for sending and receiving data from the server. /// {@nodoc} @@ -29,6 +30,10 @@ abstract interface class ISpinifyTransport { /// {@nodoc} abstract final SpinifyListenable events; + /// Get web socket metrics. + /// {@nodoc} + WebSocketMetrics get metrics; + /// Connect to the server. /// [url] is a URL of endpoint. /// [subs] is a list of server-side subscriptions to subscribe on connect. diff --git a/lib/src/transport/ws_protobuf_transport.dart b/lib/src/transport/ws_protobuf_transport.dart index 1775768..0d2860a 100644 --- a/lib/src/transport/ws_protobuf_transport.dart +++ b/lib/src/transport/ws_protobuf_transport.dart @@ -75,6 +75,9 @@ abstract base class SpinifyWSPBTransportBase implements ISpinifyTransport { final SpinifyChangeNotifier events = SpinifyChangeNotifier(); + @override + WebSocketMetrics get metrics => _webSocket.metrics; + /// Init transport, override this method to add custom logic. /// {@nodoc} @protected