1
+ /**
2
+ *
3
+ * This file is part of speed-cloudflare-cli (https://github.com/KNawm/speed-cloudflare-cli),
4
+ * which is released under the MIT License.
5
+ *
6
+ * This file has been modified to be used inside MySpeed.
7
+ *
8
+ * Copyright (c) 2020 Tomás Arias
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in all
18
+ * copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ * SOFTWARE.
27
+ */
28
+ const { performance } = require ( "perf_hooks" ) ;
29
+ const https = require ( "https" ) ;
30
+
31
+ function average ( values ) {
32
+ let total = 0 ;
33
+
34
+ for ( let i = 0 ; i < values . length ; i += 1 ) {
35
+ total += values [ i ] ;
36
+ }
37
+
38
+ return total / values . length ;
39
+ }
40
+
41
+ function median ( values ) {
42
+ const half = Math . floor ( values . length / 2 ) ;
43
+
44
+ values . sort ( ( a , b ) => a - b ) ;
45
+
46
+ if ( values . length % 2 ) return values [ half ] ;
47
+
48
+ return ( values [ half - 1 ] + values [ half ] ) / 2 ;
49
+ }
50
+
51
+ function quartile ( values , percentile ) {
52
+ values . sort ( ( a , b ) => a - b ) ;
53
+ const pos = ( values . length - 1 ) * percentile ;
54
+ const base = Math . floor ( pos ) ;
55
+ const rest = pos - base ;
56
+
57
+ if ( values [ base + 1 ] !== undefined ) {
58
+ return values [ base ] + rest * ( values [ base + 1 ] - values [ base ] ) ;
59
+ }
60
+
61
+ return values [ base ] ;
62
+ }
63
+
64
+ function request ( options , data = "" ) {
65
+ let started ;
66
+ let dnsLookup ;
67
+ let tcpHandshake ;
68
+ let sslHandshake ;
69
+ let ttfb ;
70
+ let ended ;
71
+
72
+ options . agent = new https . Agent ( options ) ;
73
+
74
+ return new Promise ( ( resolve , reject ) => {
75
+ started = performance . now ( ) ;
76
+ const req = https . request ( options , ( res ) => {
77
+ res . once ( "readable" , ( ) => {
78
+ ttfb = performance . now ( ) ;
79
+ } ) ;
80
+ res . on ( "data" , ( ) => { } ) ;
81
+ res . on ( "end" , ( ) => {
82
+ ended = performance . now ( ) ;
83
+ resolve ( [ started , dnsLookup , tcpHandshake , sslHandshake , ttfb , ended , parseFloat ( res . headers [ "server-timing" ] . slice ( 22 ) ) ] ) ;
84
+ } ) ;
85
+ } ) ;
86
+
87
+ req . on ( "socket" , ( socket ) => {
88
+ socket . on ( "lookup" , ( ) => {
89
+ dnsLookup = performance . now ( ) ;
90
+ } ) ;
91
+ socket . on ( "connect" , ( ) => {
92
+ tcpHandshake = performance . now ( ) ;
93
+ } ) ;
94
+ socket . on ( "secureConnect" , ( ) => {
95
+ sslHandshake = performance . now ( ) ;
96
+ } ) ;
97
+ } ) ;
98
+
99
+ req . on ( "error" , ( error ) => {
100
+ reject ( error ) ;
101
+ } ) ;
102
+
103
+ req . write ( data ) ;
104
+ req . end ( ) ;
105
+ } ) ;
106
+ }
107
+
108
+ function download ( bytes ) {
109
+ const options = {
110
+ hostname : "speed.cloudflare.com" ,
111
+ path : `/__down?bytes=${ bytes } ` ,
112
+ method : "GET" ,
113
+ } ;
114
+
115
+ return request ( options ) ;
116
+ }
117
+
118
+ function upload ( bytes ) {
119
+ const data = "0" . repeat ( bytes ) ;
120
+ const options = {
121
+ hostname : "speed.cloudflare.com" ,
122
+ path : "/__up" ,
123
+ method : "POST" ,
124
+ headers : {
125
+ "Content-Length" : Buffer . byteLength ( data ) ,
126
+ } ,
127
+ } ;
128
+
129
+ return request ( options , data ) ;
130
+ }
131
+
132
+ function measureSpeed ( bytes , duration ) {
133
+ return ( bytes * 8 ) / ( duration / 1000 ) / 1e6 ;
134
+ }
135
+
136
+ async function measureLatency ( ) {
137
+ const measurements = [ ] ;
138
+
139
+ for ( let i = 0 ; i < 20 ; i += 1 ) {
140
+ await download ( 1000 ) . then (
141
+ ( response ) => {
142
+ // TTFB - Server processing time
143
+ measurements . push ( response [ 4 ] - response [ 0 ] - response [ 6 ] ) ;
144
+ } ,
145
+ ( error ) => {
146
+ console . log ( `Error: ${ error } ` ) ;
147
+ } ,
148
+ ) ;
149
+ }
150
+
151
+ return [ Math . min ( ...measurements ) , Math . max ( ...measurements ) , average ( measurements ) , median ( measurements ) ] ;
152
+ }
153
+
154
+ async function measureDownload ( bytes , iterations ) {
155
+ const measurements = [ ] ;
156
+
157
+ for ( let i = 0 ; i < iterations ; i += 1 ) {
158
+ await download ( bytes ) . then (
159
+ ( response ) => {
160
+ const transferTime = response [ 5 ] - response [ 4 ] ;
161
+ measurements . push ( measureSpeed ( bytes , transferTime ) ) ;
162
+ } ,
163
+ ( error ) => {
164
+ console . log ( `Error: ${ error } ` ) ;
165
+ } ,
166
+ ) ;
167
+ }
168
+
169
+ return measurements ;
170
+ }
171
+
172
+ async function measureUpload ( bytes , iterations ) {
173
+ const measurements = [ ] ;
174
+
175
+ for ( let i = 0 ; i < iterations ; i += 1 ) {
176
+ await upload ( bytes ) . then (
177
+ ( response ) => {
178
+ const transferTime = response [ 6 ] ;
179
+ measurements . push ( measureSpeed ( bytes , transferTime ) ) ;
180
+ } ,
181
+ ( error ) => {
182
+ console . log ( `Error: ${ error } ` ) ;
183
+ } ,
184
+ ) ;
185
+ }
186
+
187
+ return measurements ;
188
+ }
189
+
190
+ module . exports = async function speedTest ( ) {
191
+ let result = { } ;
192
+ try {
193
+ result [ "ping" ] = Math . round ( ( await measureLatency ( ) ) [ 3 ] ) ;
194
+
195
+ const testDown1 = await measureDownload ( 101000 , 10 ) ;
196
+ const testDown2 = await measureDownload ( 25001000 , 2 ) ;
197
+ const testDown3 = await measureDownload ( 100001000 , 1 ) ;
198
+ result [ "download" ] = quartile ( [ ...testDown1 , ...testDown2 , ...testDown3 ] , 0.9 ) . toFixed ( 2 ) ;
199
+
200
+ const testUp1 = await measureUpload ( 11000 , 10 ) ;
201
+ const testUp2 = await measureUpload ( 101000 , 5 ) ;
202
+ const testUp3 = await measureUpload ( 1001000 , 4 ) ;
203
+ result [ "upload" ] = quartile ( [ ...testUp1 , ...testUp2 , ...testUp3 ] , 0.9 ) . toFixed ( 2 ) ;
204
+ } catch ( error ) {
205
+ console . error ( "Error while using cloudflare speedtest" ) ;
206
+ result = { error : error . message } ;
207
+ }
208
+
209
+ return result ;
210
+ }
0 commit comments