main
tsx 127 lines 3.49 KB
Raw
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 }