ui to add servers

This commit is contained in:
Lex Berezhny 2019-07-15 01:11:44 -04:00
parent 6d7aa93157
commit 45bdb9023a
9 changed files with 787 additions and 263 deletions

View file

@ -15,15 +15,18 @@ class Client {
open() {
channel = IOWebSocketChannel.connect(this.url);
int tick = 1;
channel.stream.listen((message) {
Map data = json.decode(message);
Map commands = data['commands'];
_metricsController.add(
MetricDataPoint(
tick,
CommandMetrics.from_map(commands['search'] ?? {}),
CommandMetrics.from_map(commands['resolve'] ?? {})
)
);
tick++;
});
}
@ -63,11 +66,12 @@ class CommandMetrics {
class MetricDataPoint {
final DateTime time = DateTime.now();
final int tick;
final CommandMetrics search;
final CommandMetrics resolve;
MetricDataPoint(this.search, this.resolve);
MetricDataPoint(this.tick, this.search, this.resolve);
MetricDataPoint.empty():
tick = 0,
search=CommandMetrics.from_map({}),
resolve=CommandMetrics.from_map({});
}

View file

@ -1,49 +1,82 @@
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride;
import 'package:provider/provider.dart';
import 'package:ver/src/servers.dart';
import 'package:ver/src/models/server.dart';
import 'package:ver/utils.dart';
import 'package:ver/time_series_chart.dart';
class VerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Ver',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.lightBlue,
fontFamily: 'Roboto',
),
home: VerHomePage(title: 'Wallet Server'),
);
}
class UnderConstructionPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Under Construction')),
body: SizedBox.expand(
child: Center(child: Text('Under Construction')),
),
);
}
}
class VerHomePage extends StatefulWidget {
VerHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_VerHomePageState createState() => _VerHomePageState();
class MainPage extends StatefulWidget {
@override
_MainPageState createState() => _MainPageState();
}
class _VerHomePageState extends State<VerHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: new Padding(
padding: const EdgeInsets.all(8.0),
child: SimpleTimeSeriesChart()
),
);
}
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 3;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: [
UnderConstructionPage(),
UnderConstructionPage(),
UnderConstructionPage(),
ServersSectionNavigation(),
UnderConstructionPage(),
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.trending_up), title: Text('Trending')),
BottomNavigationBarItem(icon: Icon(Icons.subscriptions), title: Text('Subscriptions')),
BottomNavigationBarItem(icon: Icon(Icons.router), title: Text('Servers')),
BottomNavigationBarItem(icon: Icon(Icons.folder), title: Text('Library')),
],
currentIndex: _currentIndex,
onTap: (int index) {
setState(() {
_currentIndex = index;
});
},
selectedItemColor: Colors.amber[800],
),
);
}
}
void main() {
debugDefaultTargetPlatformOverride = getTargetPlatformForDesktop();
runApp(new VerApp());
debugDefaultTargetPlatformOverride = getTargetPlatformForDesktop();
runApp(
MaterialApp(
title: 'Ver',
theme: ThemeData(
brightness: Brightness.dark,
//primarySwatch: Colors.lightBlue,
fontFamily: 'Roboto',
),
home: MultiProvider(
providers: [
ChangeNotifierProvider<ServerManager>(builder: (context) => ServerManager())
],
child: MainPage()
)
)
);
}

View file

@ -0,0 +1,190 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:lbry/lbry.dart';
class ServerManager extends ChangeNotifier {
final List<Server> _servers = [];
UnmodifiableListView<Server> get items => UnmodifiableListView(_servers);
add(Server server) {
_servers.add(server);
notifyListeners();
}
remove(Server server) {
server.dispose();
_servers.remove(server);
notifyListeners();
}
@override
void dispose() {
super.dispose();
for (var server in _servers) {
server.dispose();
}
}
}
class Server extends ChangeNotifier {
String _label = "";
String get label => _label;
String get labelOrHost => _label.length > 0 ? _label : host;
String _host = "";
String get host => _host;
int _port = 8181;
int get port => _port;
bool _ssl = false;
bool get ssl => _ssl;
String get url => _connection.url;
_setURL(String host, int port, bool ssl) {
_host = host;
_port = port;
_connection.url = "ws${ssl?'s':''}://$host:$port";
}
DateTime _added = new DateTime.now();
String get added => _added.toIso8601String();
bool _isDefault = false;
bool get isDefault => _isDefault;
bool _isEnabled = false;
bool get isEnabled => _isEnabled;
bool _isTrackingServerLoad = false;
bool get isTrackingServerLoad => _isTrackingServerLoad;
_setIsTrackingServerLoad(bool toggle_tracking) {
if (_isTrackingServerLoad && !toggle_tracking) {
_connection.unsubscribe_from_server_load_data();
} else if (!_isTrackingServerLoad && toggle_tracking) {
_connection.subscribe_to_server_load_data();
}
_isTrackingServerLoad = toggle_tracking;
}
ClientLoadManager clientLoadManager;
final ServerConnection _connection = ServerConnection();
bool get isConnected => _connection.isConnected;
Stream<ServerLoadDataPoint> get serverLoadStream => _connection.load_data;
final List<ServerLoadDataPoint> serverLoadData = [ServerLoadDataPoint.empty()];
Server() {
clientLoadManager = ClientLoadManager(this);
serverLoadStream.listen(serverLoadData.add);
}
update({String host, int port, bool ssl, String label,
bool isDefault, bool isEnabled, bool isTrackingServerLoad}) {
if (host != null && port != null && ssl != null) {
_setURL(host, port, ssl);
}
if (isTrackingServerLoad != null) {
_setIsTrackingServerLoad(isTrackingServerLoad);
}
_label = label ?? _label;
_isDefault = isDefault ?? _isDefault;
_isEnabled = isEnabled ?? _isEnabled;
notifyListeners();
}
connect() async {
await _connection.open();
if (_isTrackingServerLoad) {
_connection.subscribe_to_server_load_data();
}
notifyListeners();
}
disconnect() {
clientLoadManager.stop();
_connection.close();
notifyListeners();
}
@override
void dispose() {
disconnect();
super.dispose();
}
}
class ClientLoadManager extends ChangeNotifier {
final Server _server;
ClientLoadGenerator clientLoadGenerator;
final StreamController<ClientLoadDataPoint> _loadDataController = StreamController.broadcast();
Stream<ClientLoadDataPoint> get clientLoadStream => _loadDataController.stream;
final List<ClientLoadDataPoint> clientLoadData = [ClientLoadDataPoint.empty()];
int _load = 1;
int get load => _load;
int _offset = 0;
int get offset => _offset;
bool _noTotals = false;
bool get noTotals => _noTotals;
ClientLoadManager(this._server) {
clientLoadStream.listen(clientLoadData.add);
}
update({int load, int offset, bool noTotals}) {
_load = load ?? _load;
_offset = offset ?? _offset;
_noTotals = noTotals ?? _noTotals;
notifyListeners();
}
start() {
clientLoadData.clear();
clientLoadData.add(ClientLoadDataPoint.empty());
clientLoadGenerator = ClientLoadGenerator(
_server.host, _server.port,
query: {
'id': 1,
'method': 'blockchain.claimtrie.search',
'params': {
'no_totals': _noTotals,
'offset': _offset,
'limit': 20,
'fee_amount': '<1',
//'all_tags': ['funny'],
'any_tags': [
'crypto',
'outdoors',
'cars',
'automotive'
]
}
}, tickCallback: (t, stats) {
_loadDataController.add(stats);
//increase = max(1, min(100, increase+2)-stats.backlog);
//increase += 1;
//t.query['params']['offset'] = (increase/2).ceil()*t.query['params']['limit'];
t.load = _load;//rand.nextInt(10)+5;
return true;
})..start();
}
stop() {
if (clientLoadGenerator != null) {
clientLoadGenerator.stop();
}
}
@override
void dispose() {
super.dispose();
stop();
}
}

View file

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:ver/src/widgets/server.dart';
import 'package:provider/provider.dart';
import 'models/server.dart';
class ServerListPage extends StatelessWidget {
@override
Widget build(BuildContext context) =>
Scaffold(
appBar: AppBar(title: Text('Servers')),
body: ServerList(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => Navigator.of(context).pushNamed('/edit', arguments: true),
)
);
}
class ServerFormPage extends StatelessWidget {
final bool creating;
ServerFormPage(this.creating);
@override
Widget build(BuildContext context) =>
Scaffold(
appBar: AppBar(title: Text(creating ? 'Add Server' : 'Modify Server')),
body: ServerForm(creating),
);
}
class ServerViewPage extends StatelessWidget {
final Server server;
ServerViewPage(this.server);
@override
Widget build(BuildContext context) =>
Scaffold(
appBar: AppBar(title: Text(server.labelOrHost)),
body: ChangeNotifierProvider<Server>.value(
value: server,
child: ServerView()
)
);
}
class ServersSectionNavigation extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
settings: settings,
builder: (BuildContext context) {
switch (settings.name) {
case '/':
return ServerListPage();
case '/edit':
return ServerFormPage(settings.arguments);
case '/view':
return ServerViewPage(settings.arguments);
}
return ServerListPage();
},
);
},
);
}
}

View file

@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../models/server.dart';
import 'time_series_chart.dart';
class ServerList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<ServerManager>(
builder: (_, servers, __) =>
ListView.builder(
itemCount: servers.items.length,
itemBuilder: (context, index) =>
ChangeNotifierProvider<Server>.value(
value: servers.items[index],
child: ServerListItem()
)
)
);
}
}
class ServerListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Consumer<Server>(
builder: (_, server, __) =>
ListTile(
leading: FlutterLogo(size: 72.0),
title: Text(server.labelOrHost),
subtitle: Text("${server.url}\nadded ${server.added}"),
trailing: Icon(Icons.more_vert),
isThreeLine: true,
onTap: ()=> Navigator.of(context).pushNamed('/view', arguments: server),
)
)
);
}
}
class ServerView extends StatelessWidget {
@override
Widget build(BuildContext context) => ServerCharts();
}
class ServerForm extends StatefulWidget {
final bool creating;
ServerForm(this.creating);
@override
_ServerFormState createState() => _ServerFormState();
}
class _ServerFormState extends State<ServerForm> {
final _formKey = GlobalKey<FormState>();
final _formData = {
'label': '',
'host': 'localhost',
'port': 8181,
'ssl': false,
'isDefault': true,
'isEnabled': false,
'isTrackingServerLoad': false
};
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(title: TextFormField(
initialValue: _formData['label'],
decoration: const InputDecoration(
labelText: 'Label',
hintText: 'Optional text to display in server list.'
),
onSaved: (value) => _formData['label'] = value,
)),
ListTile(title: TextFormField(
initialValue: _formData['host'],
keyboardType: TextInputType.url,
inputFormatters: [
WhitelistingTextInputFormatter(RegExp(r'[\w\-\.]+'))
],
decoration: const InputDecoration(
labelText: 'Host Name',
hintText: 'Enter the host name of the server.'
),
validator: (value) => value.isEmpty ? 'A host name is required.' : null,
onSaved: (value) => _formData['host'] = value,
)),
ListTile(title: TextFormField(
initialValue: _formData['port'].toString(),
keyboardType: TextInputType.number,
inputFormatters: [
WhitelistingTextInputFormatter.digitsOnly
],
decoration: const InputDecoration(
labelText: 'Port',
hintText: 'Enter the port of the server.'
),
validator: (value) => value.isEmpty ? 'A port is required.' : null,
onSaved: (value) => setState(() => _formData['port'] = int.parse(value)),
)),
SwitchListTile(
title: Text('Requires SSL.'),
value: _formData['ssl'],
onChanged: (value) => setState(() => _formData['ssl'] = value),
),
SwitchListTile(
title: Text('Use this as your primary and default server.'),
value: _formData['isDefault'],
onChanged: (value) => setState(() => _formData['isDefault'] = value),
),
SwitchListTile(
title: Text('Always stay connected.'),
value: _formData['isEnabled'],
onChanged: (value) => setState(() => _formData['isEnabled'] = value),
),
SwitchListTile(
title: Text('Track server load.'),
value: _formData['isTrackingServerLoad'],
onChanged: (value) =>
setState(() => _formData['isTrackingServerLoad'] = value),
),
ListTile(title: RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
var manager = Provider.of<ServerManager>(
context, listen: false
);
var server = Server();
server.update(
label: _formData['label'],
host: _formData['host'],
port: _formData['port'],
ssl: _formData['ssl'],
isDefault: _formData['isDefault'],
isEnabled: _formData['isEnabled'],
isTrackingServerLoad: _formData['isTrackingServerLoad'],
);
manager.add(server);
if (server.isEnabled) {
server.connect();
}
Navigator.of(context).pop();
}
},
child: Text(widget.creating ? 'Add Server' : 'Update Server'),
),
),
],
),
);
}
}

View file

@ -0,0 +1,270 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:charts_flutter/src/base_chart_state.dart' as state;
import 'package:charts_common/common.dart' as common;
import 'package:provider/provider.dart';
import 'package:lbry/lbry.dart';
import '../models/server.dart';
class ServerCharts extends StatelessWidget {
@override
Widget build(BuildContext context) {
var server = Provider.of<Server>(context, listen: false);
return ListView(children: <Widget>[
SizedBox(height: 220.0, child: ServerLoadChart(server)),
SizedBox(height: 220.0, child: ServerPerformanceChart(server)),
SizedBox(height: 220.0, child: ClientLoadChart(server.clientLoadManager)),
SizedBox(height: 220.0, child: ClientPerformanceChart(server.clientLoadManager)),
]);
}
}
class ServerLoadChart extends StatefulWidget {
final Server server;
ServerLoadChart(this.server);
@override
State<StatefulWidget> createState() => ServerLoadChartState();
}
class ServerLoadChartState extends State<ServerLoadChart> {
List<charts.Series<ServerLoadDataPoint, int>> seriesData;
@override
void initState() {
super.initState();
seriesData = [
charts.Series<ServerLoadDataPoint, int>(
id: 'Searches Started',
colorFn: (_, __) =>
charts.MaterialPalette.deepOrange.shadeDefault.lighter,
domainFn: (ServerLoadDataPoint load, _) => load.tick,
measureFn: (ServerLoadDataPoint load, _) => load.search.started,
data: widget.server.serverLoadData,
),
charts.Series<ServerLoadDataPoint, int>(
id: 'Searches Finished',
colorFn: (_, __) =>
charts.MaterialPalette.deepOrange.shadeDefault.darker,
domainFn: (ServerLoadDataPoint load, _) => load.tick,
measureFn: (ServerLoadDataPoint load, _) => load.search.finished,
data: widget.server.serverLoadData,
),
charts.Series<ServerLoadDataPoint, int>(
id: 'Resolves Started',
colorFn: (_, __) =>
charts.MaterialPalette.teal.shadeDefault.lighter,
domainFn: (ServerLoadDataPoint load, _) => load.tick,
measureFn: (ServerLoadDataPoint load, _) => load.resolve.started,
data: widget.server.serverLoadData,
),
charts.Series<ServerLoadDataPoint, int>(
id: 'Resolves Finished',
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.darker,
domainFn: (ServerLoadDataPoint load, _) => load.tick,
measureFn: (ServerLoadDataPoint load, _) => load.resolve.finished,
data: widget.server.serverLoadData,
)
];
}
@override
Widget build(BuildContext context) {
return StreamBuilder<ServerLoadDataPoint>(
stream: widget.server.serverLoadStream,
builder: (BuildContext context, _) => BetterLineChart(seriesData)
);
}
}
class ServerPerformanceChart extends StatefulWidget {
final Server server;
ServerPerformanceChart(this.server);
@override
State<StatefulWidget> createState() => ServerPerformanceChartState();
}
class ServerPerformanceChartState extends State<ServerPerformanceChart> {
List<charts.Series<ServerLoadDataPoint, int>> seriesData;
@override
void initState() {
super.initState();
seriesData = [
charts.Series<ServerLoadDataPoint, int>(
id: 'Avg. Waiting',
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault.darker,
domainFn: (ServerLoadDataPoint load, _) => load.tick,
measureFn: (ServerLoadDataPoint load, _) => load.search.avg_wait_time,
data: widget.server.serverLoadData,
),
charts.Series<ServerLoadDataPoint, int>(
id: 'Avg. Executing',
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.lighter,
domainFn: (ServerLoadDataPoint load, _) => load.tick,
measureFn: (ServerLoadDataPoint load, _) => load.search.avg_execution_time,
data: widget.server.serverLoadData,
),
charts.Series<ServerLoadDataPoint, int>(
id: 'Avg. SQLite',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault.darker,
domainFn: (ServerLoadDataPoint load, _) => load.tick,
measureFn: (ServerLoadDataPoint load, _) => load.search.avg_query_time_per_search,
data: widget.server.serverLoadData,
)
];
}
@override
Widget build(BuildContext context) {
return StreamBuilder<ServerLoadDataPoint>(
stream: widget.server.serverLoadStream,
builder: (BuildContext context, _) => BetterLineChart(seriesData)
);
}
}
class ClientLoadChart extends StatefulWidget {
final ClientLoadManager client;
ClientLoadChart(this.client);
@override
State<StatefulWidget> createState() => ClientLoadChartState();
}
class ClientLoadChartState extends State<ClientLoadChart> {
List<charts.Series<ClientLoadDataPoint, int>> seriesData;
@override
void initState() {
super.initState();
seriesData = [
charts.Series<ClientLoadDataPoint, int>(
id: 'Load',
colorFn: (_, __) => charts.MaterialPalette.black.darker,
domainFn: (ClientLoadDataPoint load, _) => load.tick,
measureFn: (ClientLoadDataPoint load, _) => load.load,
data: widget.client.clientLoadData,
),
charts.Series<ClientLoadDataPoint, int>(
id: 'Success',
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
domainFn: (ClientLoadDataPoint load, _) => load.tick,
measureFn: (ClientLoadDataPoint load, _) => load.success,
data: widget.client.clientLoadData,
),
charts.Series<ClientLoadDataPoint, int>(
id: 'Backlog',
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
domainFn: (ClientLoadDataPoint load, _) => load.tick,
measureFn: (ClientLoadDataPoint load, _) => load.backlog,
data: widget.client.clientLoadData,
),
charts.Series<ClientLoadDataPoint, int>(
id: 'Catch-up',
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
domainFn: (ClientLoadDataPoint load, _) => load.tick,
measureFn: (ClientLoadDataPoint load, _) => load.catchup,
data: widget.client.clientLoadData,
)
];
}
@override
Widget build(BuildContext context) {
return StreamBuilder<ClientLoadDataPoint>(
stream: widget.client.clientLoadStream,
builder: (BuildContext context, _) => BetterLineChart(seriesData)
);
}
}
class ClientPerformanceChart extends StatefulWidget {
final ClientLoadManager client;
ClientPerformanceChart(this.client);
@override
State<StatefulWidget> createState() => ClientPerformanceChartState();
}
class ClientPerformanceChartState extends State<ClientPerformanceChart> {
List<charts.Series<ClientLoadDataPoint, int>> seriesData;
@override
void initState() {
super.initState();
seriesData = [
charts.Series<ClientLoadDataPoint, int>(
id: 'Avg. Success Time',
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
domainFn: (ClientLoadDataPoint load, _) => load.tick,
measureFn: (ClientLoadDataPoint load, _) => load.avg_success,
data: widget.client.clientLoadData,
),
charts.Series<ClientLoadDataPoint, int>(
id: 'Avg. Catch-up Time',
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
domainFn: (ClientLoadDataPoint load, _) => load.tick,
measureFn: (ClientLoadDataPoint load, _) => load.avg_catchup,
data: widget.client.clientLoadData,
),
];
}
@override
Widget build(BuildContext context) {
return StreamBuilder<ClientLoadDataPoint>(
stream: widget.client.clientLoadStream,
builder: (BuildContext context, _) => BetterLineChart(seriesData)
);
}
}
class BetterLineChart extends charts.LineChart {
final int itemCount;
final Object lastItem;
BetterLineChart(List<charts.Series<dynamic, int>> seriesList)
:
itemCount = seriesList[0].data.length,
lastItem = seriesList[0].data.last,
super(
seriesList,
behaviors: [charts.SeriesLegend()],
domainAxis: charts.NumericAxisSpec(
viewport: new charts.NumericExtents(
max(0, seriesList[0].data.last.tick - 60), seriesList[0].data.last.tick
)
)
);
@override
void updateCommonChart(common.BaseChart baseChart, charts.BaseChart oldWidget,
state.BaseChartState chartState) {
super.updateCommonChart(baseChart, oldWidget, chartState);
final prev = oldWidget as BetterLineChart;
if (itemCount != prev?.itemCount || lastItem != prev?.lastItem) {
chartState.markChartDirty();
}
}
}

View file

@ -1,226 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:charts_flutter/src/base_chart_state.dart' as state;
import 'package:charts_common/common.dart' as common;
import 'package:lbry/lbry.dart';
class SimpleTimeSeriesChart extends StatefulWidget {
SimpleTimeSeriesChart({Key key}) : super(key: key);
@override
_SimpleTimeSeriesChartState createState() => _SimpleTimeSeriesChartState();
}
class _SimpleTimeSeriesChartState extends State<SimpleTimeSeriesChart> {
final List<MetricDataPoint> metricData = [];
final List<LoadDataPoint> loadData = [];
final List<charts.Series<LoadDataPoint, DateTime>> loadSeries = [];
final List<charts.Series<LoadDataPoint, DateTime>> timeSeries = [];
final List<charts.Series<MetricDataPoint, DateTime>> metricSeries = [];
final List<charts.Series<MetricDataPoint, DateTime>> metricTimeSeries = [];
final Random rand = Random();
LoadGenerator loadGenerator;
Client client;
@override
void initState() {
super.initState();
metricSeries.add(
charts.Series<MetricDataPoint, DateTime>(
id: 'Searches Started',
colorFn: (_, __) => charts.MaterialPalette.deepOrange.shadeDefault.lighter,
domainFn: (MetricDataPoint load, _) => load.time,
measureFn: (MetricDataPoint load, _) => load.search.started,
data: metricData,
)
);
metricSeries.add(
charts.Series<MetricDataPoint, DateTime>(
id: 'Searches Finished',
colorFn: (_, __) => charts.MaterialPalette.deepOrange.shadeDefault.darker,
domainFn: (MetricDataPoint load, _) => load.time,
measureFn: (MetricDataPoint load, _) => load.search.finished,
data: metricData,
)
);
metricSeries.add(
charts.Series<MetricDataPoint, DateTime>(
id: 'Resolves Started',
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.lighter,
domainFn: (MetricDataPoint load, _) => load.time,
measureFn: (MetricDataPoint load, _) => load.resolve.started,
data: metricData,
)
);
metricSeries.add(
charts.Series<MetricDataPoint, DateTime>(
id: 'Resolves Finished',
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.darker,
domainFn: (MetricDataPoint load, _) => load.time,
measureFn: (MetricDataPoint load, _) => load.resolve.finished,
data: metricData,
)
);
metricTimeSeries.add(
charts.Series<MetricDataPoint, DateTime>(
id: 'Avg. Waiting',
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault.darker,
domainFn: (MetricDataPoint load, _) => load.time,
measureFn: (MetricDataPoint load, _) => load.search.avg_wait_time,
data: metricData,
)
);
metricTimeSeries.add(
charts.Series<MetricDataPoint, DateTime>(
id: 'Avg. Executing',
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.lighter,
domainFn: (MetricDataPoint load, _) => load.time,
measureFn: (MetricDataPoint load, _) => load.search.avg_execution_time,
data: metricData,
)
);
metricTimeSeries.add(
charts.Series<MetricDataPoint, DateTime>(
id: 'Avg. SQLite',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault.darker,
domainFn: (MetricDataPoint load, _) => load.time,
measureFn: (MetricDataPoint load, _) => load.search.avg_query_time_per_search,
data: metricData,
)
);
loadSeries.add(
charts.Series<LoadDataPoint, DateTime>(
id: 'Load',
colorFn: (_, __) => charts.MaterialPalette.black.darker,
domainFn: (LoadDataPoint load, _) => load.time,
measureFn: (LoadDataPoint load, _) => load.load,
data: loadData,
)
);
loadSeries.add(
charts.Series<LoadDataPoint, DateTime>(
id: 'Success',
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
domainFn: (LoadDataPoint load, _) => load.time,
measureFn: (LoadDataPoint load, _) => load.success,
data: loadData,
)
);
loadSeries.add(
charts.Series<LoadDataPoint, DateTime>(
id: 'Backlog',
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
domainFn: (LoadDataPoint load, _) => load.time,
measureFn: (LoadDataPoint load, _) => load.backlog,
data: loadData,
)
);
loadSeries.add(
charts.Series<LoadDataPoint, DateTime>(
id: 'Catch-up',
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
domainFn: (LoadDataPoint load, _) => load.time,
measureFn: (LoadDataPoint load, _) => load.catchup,
data: loadData,
)
);
timeSeries.add(
charts.Series<LoadDataPoint, DateTime>(
id: 'Avg. Success Time',
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
domainFn: (LoadDataPoint load, _) => load.time,
measureFn: (LoadDataPoint load, _) => load.avg_success,
data: loadData,
)
);
timeSeries.add(
charts.Series<LoadDataPoint, DateTime>(
id: 'Avg. Catch-up Time',
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
domainFn: (LoadDataPoint load, _) => load.time,
measureFn: (LoadDataPoint load, _) => load.avg_catchup,
data: loadData,
)
);
var increase = 1;
loadData.add(LoadDataPoint());
loadGenerator = LoadGenerator('localhost', 50001, {
'id': 1,
'method': 'blockchain.claimtrie.search',
'params': {
'no_totals': true,
'offset': 0,
'limit': 20,
'fee_amount': '<1',
//'all_tags': ['funny'],
'any_tags': [
'crypto',
'outdoors',
'cars',
'automotive'
]
}
}, (t, stats) {
setState(() {
//if (loadData.length > 60) loadData.removeAt(0);
loadData.add(stats);
});
increase = max(1, min(100, increase+2)-stats.backlog);
//increase += 1;
//t.query['params']['offset'] = (increase/2).ceil()*t.query['params']['limit'];
t.load = increase;//rand.nextInt(10)+5;
return true;
})..start();
metricData.add(MetricDataPoint.empty());
client = Client('ws://localhost:8181/')..open()..metrics.listen((m) {
setState(() {
metricData.add(m);
});
});
}
@override
void dispose() {
if (client != null) client.close();
if (loadGenerator != null) loadGenerator.stop();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
SizedBox(height: 220.0, child: BetterTimeSeriesChart(loadSeries)),
SizedBox(height: 220.0, child: BetterTimeSeriesChart(timeSeries)),
SizedBox(height: 220.0, child: BetterTimeSeriesChart(metricSeries)),
SizedBox(height: 220.0, child: BetterTimeSeriesChart(metricTimeSeries)),
]);
}
}
class BetterTimeSeriesChart extends charts.TimeSeriesChart {
final int itemCount;
final Object lastItem;
BetterTimeSeriesChart(
List<charts.Series<dynamic, DateTime>> seriesList):
itemCount = seriesList[0].data.length,
lastItem = seriesList[0].data.last,
super(seriesList, behaviors: [charts.SeriesLegend()]);
@override
void updateCommonChart(common.BaseChart baseChart, charts.BaseChart oldWidget,
state.BaseChartState chartState) {
super.updateCommonChart(baseChart, oldWidget, chartState);
final prev = oldWidget as BetterTimeSeriesChart;
if (itemCount != prev?.itemCount || lastItem != prev?.lastItem) {
chartState.markChartDirty();
}
}
}

View file

@ -151,6 +151,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.15"
provider:
dependency: "direct main"
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0+1"
quiver:
dependency: transitive
description:

View file

@ -12,6 +12,7 @@ dependencies:
sdk: flutter
cupertino_icons: ^0.1.2
charts_flutter: ^0.6.0
provider: ^3.0.0
lbry:
path: ../lbry