Relay stats

This commit is contained in:
Kieran 2023-01-11 13:05:32 +00:00
parent 357937b403
commit 6f3a0e83ce
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
4 changed files with 98 additions and 25 deletions

View File

@ -1,10 +1,24 @@
.relay { .relay {
margin-bottom: 10px; margin-top: 10px;
background-color: var(--gray-secondary); background-color: var(--gray-secondary);
border-radius: 5px; border-radius: 5px;
text-align: start; text-align: start;
display: grid;
grid-template-columns: min-content auto;
overflow: hidden;
} }
.relay > div { .relay > div {
padding: 5px; padding: 5px;
} }
.relay > div:first-child {
}
.relay-extra {
padding: 5px;
margin: 0 5px;
background-color: var(--gray-tertiary);
border-radius: 0 0 5px 5px;
white-space: nowrap;
}

View File

@ -1,9 +1,9 @@
import "./Relay.css" import "./Relay.css"
import { faPlug, faTrash, faSquareCheck, faSquareXmark } from "@fortawesome/free-solid-svg-icons"; import { faPlug, faTrash, faSquareCheck, faSquareXmark, faWifi, faUpload, faDownload, faPlugCircleXmark, faEllipsisVertical } from "@fortawesome/free-solid-svg-icons";
import useRelayState from "../feed/RelayState"; import useRelayState from "../feed/RelayState";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useMemo } from "react"; import { useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { removeRelay, setRelays } from "../state/Login"; import { removeRelay, setRelays } from "../state/Login";
@ -13,6 +13,7 @@ export default function Relay(props) {
const relaySettings = useSelector(s => s.login.relays[props.addr]); const relaySettings = useSelector(s => s.login.relays[props.addr]);
const state = useRelayState(props.addr); const state = useRelayState(props.addr);
const name = useMemo(() => new URL(props.addr).host, [props.addr]); const name = useMemo(() => new URL(props.addr).host, [props.addr]);
const [showExtra, setShowExtra] = useState(false);
function configure(o) { function configure(o) {
dispatch(setRelays({ dispatch(setRelays({
@ -20,31 +21,59 @@ export default function Relay(props) {
})); }));
} }
let latency = parseInt(state?.avgLatency ?? 0);
return ( return (
<> <>
<div className="flex relay w-max"> <div className={`relay w-max`}>
<div> <div className={`flex ${state?.connected ? "bg-success" : "bg-error"}`}>
<FontAwesomeIcon icon={faPlug} color={state?.connected ? "var(--success)" : "var(--error)"} /> <FontAwesomeIcon icon={faPlug} />
</div> </div>
<div className="f-grow f-col"> <div className="f-grow f-col">
<b>{name}</b> <div className="flex mb10">
<div> <b className="f-2">{name}</b>
Write <div className="f-1">
<span className="pill" onClick={() => configure({ write: !relaySettings.write, read: relaySettings.read })}> Write
<FontAwesomeIcon icon={relaySettings.write ? faSquareCheck : faSquareXmark} /> <span className="pill" onClick={() => configure({ write: !relaySettings.write, read: relaySettings.read })}>
</span> <FontAwesomeIcon icon={relaySettings.write ? faSquareCheck : faSquareXmark} />
Read </span>
<span className="pill" onClick={() => configure({ write: relaySettings.write, read: !relaySettings.read })}> </div>
<FontAwesomeIcon icon={relaySettings.read ? faSquareCheck : faSquareXmark} /> <div className="f-1">
</span> Read
<span className="pill" onClick={() => configure({ write: relaySettings.write, read: !relaySettings.read })}>
<FontAwesomeIcon icon={relaySettings.read ? faSquareCheck : faSquareXmark} />
</span>
</div>
</div>
<div className="flex">
<div className="f-grow">
<FontAwesomeIcon icon={faWifi} /> {latency > 2000 ? `${(latency / 1000).toFixed(0)} secs` : `${latency.toLocaleString()} ms`}
&nbsp;
<FontAwesomeIcon icon={faPlugCircleXmark} /> {state?.disconnects}
</div>
<div>
<span className="pill" onClick={() => setShowExtra(s => !s)}>
<FontAwesomeIcon icon={faEllipsisVertical} />
</span>
</div>
</div> </div>
</div> </div>
<div> </div>
<span className="pill"> {showExtra ? <div className="flex relay-extra w-max">
<FontAwesomeIcon icon={faTrash} onClick={() => dispatch(removeRelay(props.addr))} /> <div className="f-1">
<FontAwesomeIcon icon={faUpload} /> {state?.events.send}
</div>
<div className="f-1">
<FontAwesomeIcon icon={faDownload} /> {state?.events.received}
</div>
<div className="f-1">
Delete
<span className="pill" onClick={() => dispatch(removeRelay(props.addr))}>
<FontAwesomeIcon icon={faTrash} />
</span> </span>
</div> </div>
</div> </div> : null}
</> </>
) )
} }

View File

@ -133,6 +133,10 @@ textarea:placeholder {
flex: 1; flex: 1;
} }
.f-2 {
flex: 2;
}
.f-grow { .f-grow {
flex-grow: 1; flex-grow: 1;
} }
@ -271,6 +275,14 @@ body.scroll-lock {
color: var(--error); color: var(--error);
} }
.bg-error {
background-color: var(--error);
}
.bg-success {
background-color: var(--success);
}
.root-tabs { .root-tabs {
padding: 0; padding: 0;
align-items: center; align-items: center;

View File

@ -12,6 +12,7 @@ export class ConnectionStats {
this.SubsTimeout = 0; this.SubsTimeout = 0;
this.EventsReceived = 0; this.EventsReceived = 0;
this.EventsSent = 0; this.EventsSent = 0;
this.Disconnects = 0;
} }
} }
@ -28,7 +29,13 @@ export default class Connection {
this.StateHooks = {}; this.StateHooks = {};
this.HasStateChange = true; this.HasStateChange = true;
this.CurrentState = { this.CurrentState = {
connected: false connected: false,
disconnects: 0,
avgLatency: 0,
events: {
received: 0,
send: 0
}
}; };
this.LastState = Object.freeze({ ...this.CurrentState }); this.LastState = Object.freeze({ ...this.CurrentState });
this.IsClosed = false; this.IsClosed = false;
@ -47,7 +54,7 @@ export default class Connection {
Close() { Close() {
this.IsClosed = true; this.IsClosed = true;
if(this.ReconnectTimer !== null) { if (this.ReconnectTimer !== null) {
clearTimeout(this.ReconnectTimer); clearTimeout(this.ReconnectTimer);
this.ReconnectTimer = null; this.ReconnectTimer = null;
} }
@ -74,6 +81,7 @@ export default class Connection {
this.ReconnectTimer = setTimeout(() => { this.ReconnectTimer = setTimeout(() => {
this.Connect(); this.Connect();
}, this.ConnectTimeout); }, this.ConnectTimeout);
this.Stats.Disconnects++;
} else { } else {
console.log(`[${this.Address}] Closed!`); console.log(`[${this.Address}] Closed!`);
this.ReconnectTimer = null; this.ReconnectTimer = null;
@ -88,6 +96,8 @@ export default class Connection {
switch (tag) { switch (tag) {
case "EVENT": { case "EVENT": {
this._OnEvent(msg[1], msg[2]); this._OnEvent(msg[1], msg[2]);
this.Stats.EventsReceived++;
this._UpdateState();
break; break;
} }
case "EOSE": { case "EOSE": {
@ -96,7 +106,7 @@ export default class Connection {
} }
case "OK": { case "OK": {
// feedback to broadcast call // feedback to broadcast call
console.debug("OK: ", msg[1]); console.debug("OK: ", msg);
break; break;
} }
case "NOTICE": { case "NOTICE": {
@ -126,6 +136,8 @@ export default class Connection {
} }
let req = ["EVENT", e.ToObject()]; let req = ["EVENT", e.ToObject()];
this._SendJson(req); this._SendJson(req);
this.Stats.EventsSent++;
this._UpdateState();
} }
/** /**
@ -199,6 +211,11 @@ export default class Connection {
_UpdateState() { _UpdateState() {
this.CurrentState.connected = this.Socket?.readyState === WebSocket.OPEN; this.CurrentState.connected = this.Socket?.readyState === WebSocket.OPEN;
this.CurrentState.events.received = this.Stats.EventsReceived;
this.CurrentState.events.send = this.Stats.EventsSent;
this.CurrentState.avgLatency = this.Stats.Latency.length > 0 ? (this.Stats.Latency.reduce((acc, v) => acc + v, 0) / this.Stats.Latency.length) : 0;
this.CurrentState.disconnects = this.Stats.Disconnects;
this.Stats.Latency = this.Stats.Latency.slice(this.Stats.Latency.length - 20); // trim
this.HasStateChange = true; this.HasStateChange = true;
this._NotifyState(); this._NotifyState();
} }
@ -239,9 +256,10 @@ export default class Connection {
console.warn(`[${this.Address}][${subId}] Slow response time ${(responseTime / 1000).toFixed(1)} seconds`); console.warn(`[${this.Address}][${subId}] Slow response time ${(responseTime / 1000).toFixed(1)} seconds`);
} }
sub.OnEnd(this); sub.OnEnd(this);
this.Stats.Latency.push(responseTime);
this._UpdateState();
} else { } else {
// console.warn(`No subscription for end! ${subId}`); console.warn(`No subscription for end! ${subId}`);
// ignored for now, track as "dropped event" with connection stats
} }
} }