Add KeepAlive handling, add statistics, add table in network view for statistics, reduce connection timeout

This commit is contained in:
Manfred Karrer 2016-02-15 18:28:29 +01:00
parent 54a0fe9cc0
commit 540f49f1e1
31 changed files with 748 additions and 711 deletions

View file

@ -196,11 +196,11 @@ public class MainViewModel implements ViewModel {
onAllServicesInitialized();
});
startupTimeout = FxTimer.runLater(Duration.ofMillis(60000), () -> {
startupTimeout = FxTimer.runLater(Duration.ofMinutes(3), () -> {
log.warn("startupTimeout called");
MainView.blur();
new Popup().warning("The application could not startup after 60 seconds.\n" +
"There might be some network connection problems.\n\n" +
new Popup().warning("The application could not startup after 3 minutes.\n" +
"There might be some network connection problems or a unstable Tor path.\n\n" +
"Please restart and try again.")
.closeButtonText("Shut down")
.onClose(BitsquareApp.shutDownHandler::run)

View file

@ -19,6 +19,7 @@
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.settings.network.NetworkSettingsView"
@ -65,12 +66,50 @@
</TextField>
<Label fx:id="p2PPeersLabel" text="Connected peers:" GridPane.rowIndex="4"/>
<TextArea fx:id="p2PPeersTextArea" GridPane.rowIndex="4" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
GridPane.vgrow="ALWAYS" editable="false" focusTraversable="false"/>
<TableView fx:id="p2PPeerTable" GridPane.rowIndex="4" GridPane.columnIndex="1" VBox.vgrow="ALWAYS"
GridPane.hgrow="ALWAYS">
<columns>
<TableColumn text="Onion address" fx:id="onionAddressColumn" minWidth="220">
<cellValueFactory>
<PropertyValueFactory property="onionAddress"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Type" fx:id="connectionTypeColumn" minWidth="70" maxWidth="80">
<cellValueFactory>
<PropertyValueFactory property="connectionType"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Established" fx:id="creationDateColumn" minWidth="180" maxWidth="180">
<cellValueFactory>
<PropertyValueFactory property="creationDate"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Last activity" fx:id="lastActivityColumn" minWidth="100" maxWidth="120">
<cellValueFactory>
<PropertyValueFactory property="lastActivity"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Sent" fx:id="sentBytesColumn" minWidth="100" maxWidth="120">
<cellValueFactory>
<PropertyValueFactory property="sentBytes"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Received" fx:id="receivedBytesColumn" minWidth="100" maxWidth="120">
<cellValueFactory>
<PropertyValueFactory property="receivedBytes"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Identity" fx:id="peerTypeColumn" minWidth="100" maxWidth="100">
<cellValueFactory>
<PropertyValueFactory property="peerType"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="200.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
<ColumnConstraints hgrow="NEVER" halignment="RIGHT"/>
<ColumnConstraints hgrow="ALWAYS"/>
</columnConstraints>
</GridPane>

View file

@ -29,8 +29,6 @@ import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.p2p.network.LocalhostNetworkNode;
import io.bitsquare.p2p.network.OutboundConnection;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import io.bitsquare.user.Preferences;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
@ -46,7 +44,6 @@ import org.reactfx.util.FxTimer;
import javax.inject.Inject;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@FxmlView
@ -63,20 +60,26 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
@FXML
ComboBox<BitcoinNetwork> netWorkComboBox;
@FXML
TextArea bitcoinPeersTextArea, p2PPeersTextArea;
TextArea bitcoinPeersTextArea;
@FXML
Label bitcoinPeersLabel, p2PPeersLabel;
@FXML
CheckBox useTorCheckBox;
@FXML
TableView<NetworkStatisticListItem> p2PPeerTable;
@FXML
TableColumn<NetworkStatisticListItem, String> onionAddressColumn, connectionTypeColumn, creationDateColumn,
lastActivityColumn, sentBytesColumn, receivedBytesColumn, peerTypeColumn;
/* TableColumn<NetworkStatisticListItem, NetworkStatisticListItem> onionAddressColumn, connectionTypeColumn, creationDateColumn,
lastActivityColumn, sentBytesColumn, receivedBytesColumn, peerTypeColumn;
*/
private P2PServiceListener p2PServiceListener;
private ChangeListener<Number> numP2PPeersChangeListener;
private ChangeListener<List<Peer>> bitcoinPeersChangeListener;
private final Set<NodeAddress> seedNodeAddresses;
@Inject
public NetworkSettingsView(WalletService walletService, P2PService p2PService, Preferences preferences,
SeedNodesRepository seedNodesRepository, BSFormatter formatter) {
BSFormatter formatter) {
this.walletService = walletService;
this.p2PService = p2PService;
this.preferences = preferences;
@ -84,7 +87,6 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
BitcoinNetwork bitcoinNetwork = preferences.getBitcoinNetwork();
boolean useLocalhost = p2PService.getNetworkNode() instanceof LocalhostNetworkNode;
this.seedNodeAddresses = seedNodesRepository.getSeedNodeAddresses(useLocalhost, bitcoinNetwork.ordinal());
}
public void initialize() {
@ -138,6 +140,19 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
public void onSetupFailed(Throwable throwable) {
}
};
p2PPeerTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
p2PPeerTable.setPlaceholder(new Label("No connections are available"));
p2PPeerTable.getSortOrder().add(creationDateColumn);
creationDateColumn.setSortType(TableColumn.SortType.ASCENDING);
//TODO sorting needs other NetworkStatisticListItem as columns type
/* creationDateColumn.setComparator((o1, o2) ->
o1.statistic.getCreationDate().compareTo(o2.statistic.getCreationDate()));
sentBytesColumn.setComparator((o1, o2) ->
((Integer) o1.statistic.getSentBytes()).compareTo(((Integer) o2.statistic.getSentBytes())));
receivedBytesColumn.setComparator((o1, o2) ->
((Integer) o1.statistic.getReceivedBytes()).compareTo(((Integer) o2.statistic.getReceivedBytes())));*/
}
@Override
@ -171,9 +186,9 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
walletService.connectedPeersProperty().addListener(bitcoinPeersChangeListener);
updateBitcoinPeersTextArea();
numP2PPeersChangeListener = (observable, oldValue, newValue) -> updateP2PPeersTextArea();
numP2PPeersChangeListener = (observable, oldValue, newValue) -> updateP2PStatistics();
p2PService.getNumConnectedPeers().addListener(numP2PPeersChangeListener);
updateP2PPeersTextArea();
updateP2PStatistics();
}
@Override
@ -188,24 +203,18 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
if (numP2PPeersChangeListener != null)
p2PService.getNumConnectedPeers().removeListener(numP2PPeersChangeListener);
p2PPeerTable.getItems().forEach(NetworkStatisticListItem::cleanup);
}
private void updateP2PPeersTextArea() {
p2PPeersTextArea.clear();
p2PPeersTextArea.setText(p2PService.getNetworkNode().getConfirmedConnections()
.stream()
.map(connection -> {
if (connection.getPeersNodeAddressOptional().isPresent()) {
NodeAddress nodeAddress = connection.getPeersNodeAddressOptional().get();
return nodeAddress.getFullAddress() + " (" +
(connection instanceof OutboundConnection ? "outbound" : "inbound") +
(seedNodeAddresses.contains(nodeAddress) ? " / seed node)" : ")");
} else {
// Should never be the case
return "";
}
})
.collect(Collectors.joining("\n")));
private void updateP2PStatistics() {
p2PPeerTable.getItems().forEach(NetworkStatisticListItem::cleanup);
List<NetworkStatisticListItem> list = p2PService.getNetworkNode().getConfirmedConnections().stream()
.map(connection -> new NetworkStatisticListItem(connection, formatter))
.collect(Collectors.toList());
p2PPeerTable.setItems(FXCollections.observableArrayList(list));
p2PPeerTable.sort();
}
private void updateBitcoinPeersTextArea() {

View file

@ -0,0 +1,123 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.settings.network;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.OutboundConnection;
import io.bitsquare.p2p.network.Statistic;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.reactfx.util.FxTimer;
import org.reactfx.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
public class NetworkStatisticListItem {
private static final Logger log = LoggerFactory.getLogger(NetworkStatisticListItem.class);
final Statistic statistic;
private final Connection connection;
private final Subscription sentBytesSubscription, receivedBytesSubscription;
private final Timer timer;
private BSFormatter formatter;
private StringProperty lastActivity = new SimpleStringProperty();
private StringProperty sentBytes = new SimpleStringProperty();
private StringProperty receivedBytes = new SimpleStringProperty();
public NetworkStatisticListItem(Connection connection, BSFormatter formatter) {
this.connection = connection;
this.formatter = formatter;
this.statistic = connection.getStatistic();
sentBytesSubscription = EasyBind.subscribe(statistic.sentBytesProperty,
e -> sentBytes.set(formatter.formatBytes((int) e)));
receivedBytesSubscription = EasyBind.subscribe(statistic.receivedBytesProperty,
e -> receivedBytes.set(formatter.formatBytes((int) e)));
timer = FxTimer.runPeriodically(Duration.ofMillis(1000),
() -> UserThread.execute(() -> onLastActivityChanged(statistic.lastActivityTimestampProperty.get())));
onLastActivityChanged(statistic.lastActivityTimestampProperty.get());
}
private void onLastActivityChanged(long timeStamp) {
lastActivity.set(DurationFormatUtils.formatDuration(System.currentTimeMillis() - timeStamp, "mm:ss.SSS"));
}
public void cleanup() {
sentBytesSubscription.unsubscribe();
receivedBytesSubscription.unsubscribe();
timer.stop();
}
public String getOnionAddress() {
if (connection.getPeersNodeAddressOptional().isPresent())
return connection.getPeersNodeAddressOptional().get().getFullAddress();
else
return "";
}
public String getConnectionType() {
return connection instanceof OutboundConnection ? "outbound" : "inbound";
}
public String getCreationDate() {
return formatter.formatDateTime(statistic.getCreationDate());
}
public String getPeerType() {
if (connection.getPeerType() == Connection.PeerType.SEED_NODE)
return "Seed node";
else if (connection.getPeerType() == Connection.PeerType.DIRECT_MSG_PEER)
return "Peer (direct)";
else
return "Peer";
}
public String getLastActivity() {
return lastActivity.get();
}
public StringProperty lastActivityProperty() {
return lastActivity;
}
public String getSentBytes() {
return sentBytes.get();
}
public StringProperty sentBytesProperty() {
return sentBytes;
}
public String getReceivedBytes() {
return receivedBytes.get();
}
public StringProperty receivedBytesProperty() {
return receivedBytes;
}
}

View file

@ -23,6 +23,7 @@ import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import org.bitcoinj.utils.MonetaryFormat;
@ -374,8 +375,8 @@ public class BSFormatter {
}
public String getDaysHoursMinutes(Date startDate, Date endDate) {
long different = endDate.getTime() - startDate.getTime();
long secondsInMilli = 1000;
return DurationFormatUtils.formatDurationWords(endDate.getTime() - startDate.getTime(), true, true);
/* long secondsInMilli = 1000;
long minutesInMilli = secondsInMilli * 60;
long hoursInMilli = minutesInMilli * 60;
long daysInMilli = hoursInMilli * 24;
@ -394,9 +395,64 @@ public class BSFormatter {
else if (elapsedMinutes > 0)
return elapsedMinutes + " " + minuteString;
else
return null;
return null;*/
}
/* public List<String> tokenizePeriod(long time, boolean showSeconds, boolean showMillis) {
log.error("formatPastTimestamp " + time + "");
long secondsInMilli = 1000;
long minutesInMilli = secondsInMilli * 60;
long hoursInMilli = minutesInMilli * 60;
long daysInMilli = hoursInMilli * 24;
long elapsedDays = time / daysInMilli;
time = time % daysInMilli;
long elapsedHours = time / hoursInMilli;
time = time % hoursInMilli;
long elapsedMinutes = time / minutesInMilli;
time = time % minutesInMilli;
long elapsedSeconds = time / secondsInMilli;
time = time % secondsInMilli;
long elapsedMillis = time;
String dayString = elapsedDays == 1 ? elapsedDays + " day" : elapsedDays + " days";
String hourString = elapsedHours == 1 ? elapsedHours + " hour" : elapsedHours + " hours";
String minuteString = elapsedMinutes == 1 ? elapsedMinutes + " minute" : elapsedMinutes + " minutes";
String secondsString = elapsedSeconds == 1 ? elapsedSeconds + " second" : elapsedSeconds + " seconds";
String millisString = elapsedMillis + " ms";
List<String> tokens = new ArrayList<>();
if (elapsedDays > 0)
tokens.add(dayString);
if (elapsedHours > 0)
tokens.add(hourString);
if (elapsedMinutes > 0)
tokens.add(minuteString);
if (showSeconds && elapsedSeconds > 0)
tokens.add(secondsString);
if (showMillis && elapsedMillis > 0)
tokens.add(millisString);
return tokens;
}
private String formatTimeTokens(List<String> tokens) {
if (tokens.size() > 1) {
String last = tokens.remove(tokens.size() - 1);
String result = tokens.stream().collect(Collectors.joining(", "));
return result + " and " + last;
} else if (tokens.size() == 1) {
return tokens.get(0);
} else {
return "";
}
}*/
public String booleanToYesNo(boolean value) {
return value ? "Yes" : "No";
@ -429,4 +485,17 @@ public class BSFormatter {
else
return isOfferer ? "Seller (offerer)" : "Buyer (taker)";
}
public String formatBytes(int bytes) {
double kb = 1024;
double mb = kb * kb;
DecimalFormat decimalFormat = new DecimalFormat("#.##");
if (bytes < kb)
return bytes + " bytes";
else if (bytes < mb)
return decimalFormat.format(bytes / kb) + " KB";
else
return decimalFormat.format(bytes / mb) + " MB";
}
}