| 1 | import { useState } from 'react' |
| 2 | import { delay } from '../utils/delay.ts' |
| 3 | import { Button } from './button.tsx' |
| 4 | import { ConsolePanel } from './console.tsx' |
| 5 | import { ErrorPanel } from './error.tsx' |
| 6 | import { Panel } from './panel.tsx' |
| 7 | import { TextInput } from './text-input.tsx' |
| 8 | import type { MetricsRPC } from '@ipshipyard/libp2p-inspector-metrics' |
| 9 | import type { JSX } from 'react' |
| 10 | |
| 11 | interface PingPanelProps { |
| 12 | component: string |
| 13 | metrics: MetricsRPC |
| 14 | } |
| 15 | |
| 16 | export function Ping ({ component, metrics }: PingPanelProps): JSX.Element { |
| 17 | const [peerIdOrMultiaddr, setPeerIdOrMultiaddr] = useState('') |
| 18 | const [result, setResult] = useState<JSX.Element | string>('') |
| 19 | |
| 20 | function handlePing (evt: { preventDefault(): void }): void { |
| 21 | evt.preventDefault() |
| 22 | |
| 23 | let remote = peerIdOrMultiaddr |
| 24 | |
| 25 | setResult(<ConsolePanel>{formatHeader(remote)}</ConsolePanel>) |
| 26 | |
| 27 | Promise.resolve() |
| 28 | .then(async () => { |
| 29 | const conn = await metrics.openConnection(peerIdOrMultiaddr, { |
| 30 | signal: AbortSignal.timeout(10_000) |
| 31 | }) |
| 32 | remote = conn.remotePeer.toString() |
| 33 | |
| 34 | const results: Array<string | number> = [] |
| 35 | |
| 36 | for (let i = 0; i < 5; i++) { |
| 37 | try { |
| 38 | const rtt = await metrics.ping(component, peerIdOrMultiaddr, { |
| 39 | signal: AbortSignal.timeout(10_000) |
| 40 | }) |
| 41 | |
| 42 | results.push(rtt) |
| 43 | |
| 44 | setResult( |
| 45 | <ConsolePanel> |
| 46 | {formatHeader(remote)}{'\n'} |
| 47 | {formatResults(results)} |
| 48 | </ConsolePanel> |
| 49 | ) |
| 50 | |
| 51 | await delay(1_000) |
| 52 | } catch (err: any) { |
| 53 | results.push(err.message) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | setResult(( |
| 58 | <ConsolePanel> |
| 59 | {formatHeader(remote)}{'\n'} |
| 60 | {formatResults(results)}{'\n'} |
| 61 | --- {peerIdOrMultiaddr} ping statistics ---{'\n'} |
| 62 | {calculateStats(results)} |
| 63 | </ConsolePanel> |
| 64 | )) |
| 65 | }) |
| 66 | .catch(err => { |
| 67 | setResult(<ErrorPanel error={err} />) |
| 68 | }) |
| 69 | } |
| 70 | |
| 71 | return ( |
| 72 | <Panel> |
| 73 | <p>Ping a peer</p> |
| 74 | <form onSubmit={handlePing}> |
| 75 | <TextInput type='text' value={peerIdOrMultiaddr} placeholder='Peer ID or Multiaddr' onChange={(e) => { setPeerIdOrMultiaddr(e.target.value) }} /> |
| 76 | <Button onClick={handlePing} primary>Ping</Button> |
| 77 | </form> |
| 78 | {result} |
| 79 | </Panel> |
| 80 | ) |
| 81 | } |
| 82 | |
| 83 | function formatHeader (remote: string): string { |
| 84 | return `PING ${remote}: 32 data bytes` |
| 85 | } |
| 86 | |
| 87 | function formatResults (results: Array<string | number>): string { |
| 88 | return results.map((res, index) => { |
| 89 | if (typeof res === 'string') { |
| 90 | return `seq=${index} ${res}` |
| 91 | } |
| 92 | |
| 93 | return `seq=${index} time=${res} ms` |
| 94 | }) |
| 95 | .join('\n') |
| 96 | } |
| 97 | |
| 98 | function calculateStats (results: Array<string | number>): string { |
| 99 | const success = results |
| 100 | .filter(res => typeof res !== 'string') |
| 101 | |
| 102 | let min = Infinity |
| 103 | let max = 0 |
| 104 | let sum = 0 |
| 105 | |
| 106 | for (const res of success) { |
| 107 | if (res < min) { |
| 108 | min = res |
| 109 | } |
| 110 | |
| 111 | if (res > max) { |
| 112 | max = res |
| 113 | } |
| 114 | |
| 115 | sum += res |
| 116 | } |
| 117 | |
| 118 | const mean = sum / success.length |
| 119 | const squaredDeviations = success.map(val => Math.pow(val - mean, 2)) |
| 120 | const variance = squaredDeviations.reduce((acc, curr) => acc + curr, 0) / (success.length - 1) |
| 121 | const stdDeviation = Math.sqrt(variance) |
| 122 | |
| 123 | return [ |
| 124 | `${results.length} packets transmitted, ${success.length} packets received, ${100 - Math.round((success.length / results.length) * 100)}% packet loss`, |
| 125 | `round-trip min/avg/max/stddev = ${min}/${Math.round(mean)}/${max}/${Math.round(stdDeviation)} ms` |
| 126 | ].join('\n') |
| 127 | } |