Browse Source

Add lightweight load-test webpage, disabled by default (#8514)

Co-authored-by: Hristo Terezov <hristo@jitsi.org>
Co-authored-by: damencho <damencho@jitsi.org>
master
Jonathan Lennox 4 years ago
parent
commit
12680c35ca
No account linked to committer's email address

+ 4
- 1
Makefile View File

@@ -16,9 +16,12 @@ WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack-dev-server
16 16
 
17 17
 all: compile deploy clean
18 18
 
19
-compile:
19
+compile: compile-load-test
20 20
 	$(WEBPACK) -p
21 21
 
22
+compile-load-test:
23
+	${NPM} install --prefix load-test && ${NPM} run build --prefix load-test
24
+
22 25
 clean:
23 26
 	rm -fr $(BUILD_DIR)
24 27
 

+ 2
- 0
debian/jitsi-meet-web.install View File

@@ -15,3 +15,5 @@ resources/robots.txt	/usr/share/jitsi-meet/
15 15
 resources/*.sh			/usr/share/jitsi-meet/scripts/
16 16
 pwa-worker.js			/usr/share/jitsi-meet/
17 17
 manifest.json			/usr/share/jitsi-meet/
18
+load-test/*.html		/usr/share/jitsi-meet/load-test/
19
+load-test/libs  		/usr/share/jitsi-meet/load-test/

+ 9
- 0
doc/debian/jitsi-meet/jitsi-meet.example View File

@@ -100,6 +100,15 @@ server {
100 100
         tcp_nodelay on;
101 101
     }
102 102
 
103
+    # load test minimal client, uncomment when used
104
+    #location ~ ^/_load-test/([^/?&:'"]+)$ {
105
+    #    rewrite ^/_load-test/(.*)$ /load-test/index.html break;
106
+    #}
107
+    #location ~ ^/_load-test/libs/(.*)$ {
108
+    #    add_header 'Access-Control-Allow-Origin' '*';
109
+    #    alias /usr/share/jitsi-meet/load-test/libs/$1;
110
+    #}
111
+
103 112
     location ~ ^/([^/?&:'"]+)$ {
104 113
         try_files $uri @root_path;
105 114
     }

+ 13
- 0
load-test/index.html View File

@@ -0,0 +1,13 @@
1
+<!DOCTYPE html>
2
+<html>
3
+<head lang="en">
4
+    <meta charset="UTF-8">
5
+    <title></title>
6
+    <script><!--#include virtual="/config.js" --></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
7
+    <script src="../libs/lib-jitsi-meet.min.js?v=139"></script>
8
+    <script src="libs/load-test-participant.min.js" ></script>
9
+</head>
10
+<body>
11
+    <div>Number of participants: <span id="participants">1</span></div>
12
+</body>
13
+</html>

+ 228
- 0
load-test/load-test-participant.js View File

@@ -0,0 +1,228 @@
1
+/* global $, config, JitsiMeetJS */
2
+import 'jquery';
3
+import { parseURLParams } from '../react/features/base/util/parseURLParams';
4
+import { parseURIString } from '../react/features/base/util/uri';
5
+
6
+const params = parseURLParams(window.location, false, 'hash');
7
+const { isHuman = false } = params;
8
+const {
9
+    localAudio = params['config.startWithAudioMuted'] !== true,
10
+    localVideo = params['config.startWithVideoMuted'] !== true,
11
+    remoteVideo = isHuman,
12
+    remoteAudio = isHuman
13
+} = params;
14
+
15
+const { room: roomName } = parseURIString(window.location.toString());
16
+
17
+let connection = null;
18
+
19
+let isJoined = false;
20
+
21
+let room = null;
22
+
23
+let numParticipants = 1;
24
+
25
+let localTracks = [];
26
+const remoteTracks = {};
27
+
28
+window.APP = {
29
+    conference: {
30
+        getStats() {
31
+            return room.connectionQuality.getStats();
32
+        },
33
+        getConnectionState() {
34
+            return room && room.getConnectionState();
35
+        }
36
+    },
37
+
38
+    get room() {
39
+        return room;
40
+    },
41
+    get connection() {
42
+        return connection;
43
+    },
44
+    get numParticipants() {
45
+        return numParticipants;
46
+    },
47
+    get localTracks() {
48
+        return localTracks;
49
+    },
50
+    get remoteTracks() {
51
+        return remoteTracks;
52
+    },
53
+    get params() {
54
+        return {
55
+            roomName,
56
+            localAudio,
57
+            localVideo,
58
+            remoteVideo,
59
+            remoteAudio
60
+        };
61
+    }
62
+};
63
+
64
+/**
65
+ *
66
+ */
67
+function setNumberOfParticipants() {
68
+    $('#participants').text(numParticipants);
69
+}
70
+
71
+/**
72
+ * Handles local tracks.
73
+ * @param tracks Array with JitsiTrack objects
74
+ */
75
+function onLocalTracks(tracks = []) {
76
+    localTracks = tracks;
77
+    for (let i = 0; i < localTracks.length; i++) {
78
+        if (localTracks[i].getType() === 'video') {
79
+            $('body').append(`<video autoplay='1' id='localVideo${i}' />`);
80
+            localTracks[i].attach($(`#localVideo${i}`)[0]);
81
+        } else {
82
+            $('body').append(
83
+                `<audio autoplay='1' muted='true' id='localAudio${i}' />`);
84
+            localTracks[i].attach($(`#localAudio${i}`)[0]);
85
+        }
86
+        if (isJoined) {
87
+            room.addTrack(localTracks[i]);
88
+        }
89
+    }
90
+}
91
+
92
+/**
93
+ * Handles remote tracks
94
+ * @param track JitsiTrack object
95
+ */
96
+function onRemoteTrack(track) {
97
+    if (track.isLocal()
98
+            || (track.getType() === 'video' && !remoteVideo) || (track.getType() === 'audio' && !remoteAudio)) {
99
+        return;
100
+    }
101
+    const participant = track.getParticipantId();
102
+
103
+    if (!remoteTracks[participant]) {
104
+        remoteTracks[participant] = [];
105
+    }
106
+    const idx = remoteTracks[participant].push(track);
107
+    const id = participant + track.getType() + idx;
108
+
109
+    if (track.getType() === 'video') {
110
+        $('body').append(`<video autoplay='1' id='${id}' />`);
111
+    } else {
112
+        $('body').append(`<audio autoplay='1' id='${id}' />`);
113
+    }
114
+    track.attach($(`#${id}`)[0]);
115
+}
116
+
117
+/**
118
+ * That function is executed when the conference is joined
119
+ */
120
+function onConferenceJoined() {
121
+    isJoined = true;
122
+    for (let i = 0; i < localTracks.length; i++) {
123
+        room.addTrack(localTracks[i]);
124
+    }
125
+}
126
+
127
+/**
128
+ *
129
+ * @param id
130
+ */
131
+function onUserLeft(id) {
132
+    numParticipants--;
133
+    setNumberOfParticipants();
134
+    if (!remoteTracks[id]) {
135
+        return;
136
+    }
137
+    const tracks = remoteTracks[id];
138
+
139
+    for (let i = 0; i < tracks.length; i++) {
140
+        const container = $(`#${id}${tracks[i].getType()}${i + 1}`)[0];
141
+
142
+        if (container) {
143
+            tracks[i].detach(container);
144
+            container.parentElement.removeChild(container);
145
+        }
146
+    }
147
+}
148
+
149
+/**
150
+ * That function is called when connection is established successfully
151
+ */
152
+function onConnectionSuccess() {
153
+    room = connection.initJitsiConference(roomName, config);
154
+    room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
155
+    room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
156
+    room.on(JitsiMeetJS.events.conference.USER_JOINED, id => {
157
+        numParticipants++;
158
+        setNumberOfParticipants();
159
+        remoteTracks[id] = [];
160
+    });
161
+    room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
162
+    room.join();
163
+}
164
+
165
+/**
166
+ * This function is called when the connection fail.
167
+ */
168
+function onConnectionFailed() {
169
+    console.error('Connection Failed!');
170
+}
171
+
172
+/**
173
+ * This function is called when we disconnect.
174
+ */
175
+function disconnect() {
176
+    console.log('disconnect!');
177
+    connection.removeEventListener(
178
+        JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
179
+        onConnectionSuccess);
180
+    connection.removeEventListener(
181
+        JitsiMeetJS.events.connection.CONNECTION_FAILED,
182
+        onConnectionFailed);
183
+    connection.removeEventListener(
184
+        JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
185
+        disconnect);
186
+}
187
+
188
+/**
189
+ *
190
+ */
191
+function unload() {
192
+    for (let i = 0; i < localTracks.length; i++) {
193
+        localTracks[i].dispose();
194
+    }
195
+    room.leave();
196
+    connection.disconnect();
197
+}
198
+
199
+$(window).bind('beforeunload', unload);
200
+$(window).bind('unload', unload);
201
+
202
+JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR);
203
+
204
+JitsiMeetJS.init(config);
205
+
206
+connection = new JitsiMeetJS.JitsiConnection(null, null, config);
207
+connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED, onConnectionSuccess);
208
+connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onConnectionFailed);
209
+connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
210
+connection.connect();
211
+
212
+const devices = [];
213
+
214
+if (localVideo) {
215
+    devices.push('video');
216
+}
217
+if (localAudio) {
218
+    devices.push('audio');
219
+}
220
+if (devices.length > 0) {
221
+    JitsiMeetJS.createLocalTracks({ devices })
222
+    .then(onLocalTracks)
223
+    .catch(error => {
224
+        throw error;
225
+    });
226
+}
227
+
228
+

+ 6983
- 0
load-test/package-lock.json
File diff suppressed because it is too large
View File


+ 55
- 0
load-test/package.json View File

@@ -0,0 +1,55 @@
1
+{
2
+  "name": "jitsi-meet-load-test",
3
+  "version": "0.0.0",
4
+  "description": "A load test participant",
5
+  "repository": {
6
+    "type": "git",
7
+    "url": "git://github.com/jitsi/jitsi-meet"
8
+  },
9
+  "keywords": [
10
+    "jingle",
11
+    "webrtc",
12
+    "xmpp",
13
+    "browser"
14
+  ],
15
+  "author": "",
16
+  "readmeFilename": "../README.md",
17
+  "dependencies": {
18
+    "jquery": "3.4.0"
19
+  },
20
+  "devDependencies": {
21
+    "@babel/core": "7.5.5",
22
+    "@babel/plugin-proposal-class-properties": "7.1.0",
23
+    "@babel/plugin-proposal-export-default-from": "7.0.0",
24
+    "@babel/plugin-proposal-export-namespace-from": "7.0.0",
25
+    "@babel/plugin-proposal-nullish-coalescing-operator": "7.4.4",
26
+    "@babel/plugin-proposal-optional-chaining": "7.2.0",
27
+    "@babel/plugin-transform-flow-strip-types": "7.0.0",
28
+    "@babel/preset-env": "7.1.0",
29
+    "@babel/preset-flow": "7.0.0",
30
+    "@babel/runtime": "7.5.5",
31
+    "babel-eslint": "10.0.1",
32
+    "babel-loader": "8.0.4",
33
+    "eslint": "5.6.1",
34
+    "eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#1.0.1",
35
+    "eslint-plugin-flowtype": "2.50.3",
36
+    "eslint-plugin-import": "2.14.0",
37
+    "eslint-plugin-jsdoc": "3.8.0",
38
+    "expose-loader": "0.7.5",
39
+    "flow-bin": "0.104.0",
40
+    "imports-loader": "0.7.1",
41
+    "string-replace-loader": "2.1.1",
42
+    "style-loader": "0.19.0",
43
+    "webpack": "4.27.1",
44
+    "webpack-bundle-analyzer": "3.4.1",
45
+    "webpack-cli": "3.1.2"
46
+  },
47
+  "engines": {
48
+    "node": ">=8.0.0",
49
+    "npm": ">=6.0.0"
50
+  },
51
+  "license": "Apache-2.0",
52
+  "scripts": {
53
+    "build": "webpack -p"
54
+  }
55
+}

+ 131
- 0
load-test/webpack.config.js View File

@@ -0,0 +1,131 @@
1
+/* global __dirname */
2
+
3
+const process = require('process');
4
+const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
5
+const analyzeBundle = process.argv.indexOf('--analyze-bundle') !== -1;
6
+const minimize
7
+    = process.argv.indexOf('-p') !== -1
8
+        || process.argv.indexOf('--optimize-minimize') !== -1;
9
+
10
+/**
11
+ * Build a Performance configuration object for the given size.
12
+ * See: https://webpack.js.org/configuration/performance/
13
+ */
14
+function getPerformanceHints(size) {
15
+    return {
16
+        hints: minimize ? 'error' : false,
17
+        maxAssetSize: size,
18
+        maxEntrypointSize: size
19
+    };
20
+}
21
+
22
+// The base Webpack configuration to bundle the JavaScript artifacts of
23
+// jitsi-meet such as app.bundle.js and external_api.js.
24
+const config = {
25
+    devtool: 'source-map',
26
+    mode: minimize ? 'production' : 'development',
27
+    module: {
28
+        rules: [ {
29
+            // Transpile ES2015 (aka ES6) to ES5. Accept the JSX syntax by React
30
+            // as well.
31
+
32
+            exclude: [
33
+                new RegExp(`${__dirname}/node_modules/(?!js-utils)`)
34
+            ],
35
+            loader: 'babel-loader',
36
+            options: {
37
+                // XXX The require.resolve bellow solves failures to locate the
38
+                // presets when lib-jitsi-meet, for example, is npm linked in
39
+                // jitsi-meet.
40
+                plugins: [
41
+                    require.resolve('@babel/plugin-transform-flow-strip-types'),
42
+                    require.resolve('@babel/plugin-proposal-class-properties'),
43
+                    require.resolve('@babel/plugin-proposal-export-default-from'),
44
+                    require.resolve('@babel/plugin-proposal-export-namespace-from'),
45
+                    require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
46
+                    require.resolve('@babel/plugin-proposal-optional-chaining')
47
+                ],
48
+                presets: [
49
+                    [
50
+                        require.resolve('@babel/preset-env'),
51
+
52
+                        // Tell babel to avoid compiling imports into CommonJS
53
+                        // so that webpack may do tree shaking.
54
+                        {
55
+                            modules: false,
56
+
57
+                            // Specify our target browsers so no transpiling is
58
+                            // done unnecessarily. For browsers not specified
59
+                            // here, the ES2015+ profile will be used.
60
+                            targets: {
61
+                                chrome: 58,
62
+                                electron: 2,
63
+                                firefox: 54,
64
+                                safari: 11
65
+                            }
66
+
67
+                        }
68
+                    ],
69
+                    require.resolve('@babel/preset-flow'),
70
+                    require.resolve('@babel/preset-react')
71
+                ]
72
+            },
73
+            test: /\.jsx?$/
74
+        }, {
75
+            // Expose jquery as the globals $ and jQuery because it is expected
76
+            // to be available in such a form by multiple jitsi-meet
77
+            // dependencies including lib-jitsi-meet.
78
+
79
+            loader: 'expose-loader?$!expose-loader?jQuery',
80
+            test: /\/node_modules\/jquery\/.*\.js$/
81
+        } ]
82
+    },
83
+    node: {
84
+        // Allow the use of the real filename of the module being executed. By
85
+        // default Webpack does not leak path-related information and provides a
86
+        // value that is a mock (/index.js).
87
+        __filename: true
88
+    },
89
+    optimization: {
90
+        concatenateModules: minimize,
91
+        minimize
92
+    },
93
+    output: {
94
+        filename: `[name]${minimize ? '.min' : ''}.js`,
95
+        path: `${__dirname}/libs`,
96
+        publicPath: 'load-test/libs/',
97
+        sourceMapFilename: `[name].${minimize ? 'min' : 'js'}.map`
98
+    },
99
+    plugins: [
100
+        analyzeBundle
101
+            && new BundleAnalyzerPlugin({
102
+                analyzerMode: 'disabled',
103
+                generateStatsFile: true
104
+            })
105
+    ].filter(Boolean),
106
+    resolve: {
107
+        alias: {
108
+            jquery: `jquery/dist/jquery${minimize ? '.min' : ''}.js`
109
+        },
110
+        aliasFields: [
111
+            'browser'
112
+        ],
113
+        extensions: [
114
+            '.web.js',
115
+
116
+            // Webpack defaults:
117
+            '.js',
118
+            '.json'
119
+        ]
120
+    }
121
+};
122
+
123
+module.exports = [
124
+    Object.assign({}, config, {
125
+        entry: {
126
+            'load-test-participant': './load-test-participant.js'
127
+        },
128
+        performance: getPerformanceHints(3 * 1024 * 1024)
129
+    })
130
+];
131
+

Loading…
Cancel
Save