Bench results
The Janus Bench checks, run headless against the Rust binary. These are the results of one recorded run; run npm run e2e in webapp/ to get your own, and treat anything here as a snapshot.
| Run | 2026-11-04T15:30:53.375Z |
| Server | janus 1.4.4 as built from the working tree on 2026-11-04 with every plugin feature (echotest, videocall, videoroom, streaming, recordplay, textroom, audiobridge, sip, nosip, lua, duktape); 11 plugins listed by info: audiobridge, echojs, echolua, echotest, nosip, recordplay, sip, streaming, textroom, videocall, videoroom |
| Browser | Chrome (headless, installed channel) with --autoplay-policy=no-user-gesture-required --disable-features=WebRtcHideLocalIpsWithMdns |
| Media | synthetic: a 440 Hz tone and an animated canvas; a UDP feeder sends Opus RTP to the Streaming mountpoint; a small UDP SIP peer answers the SIP call |
| Transport | HTTP long poll for the plugin checks; the core checks also run over WebSockets |
| Result | 23 passed, 5 failed, 0 skipped of 28 |
Every check
| Group | Check | Result | Time | Title |
|---|---|---|---|---|
| Core | core.info.http | pass | 6 ms | Server info over HTTP |
| Core | core.info.ws | pass | 9 ms | Server info over WebSockets |
| Core | core.plugins | pass | 35 ms | Plugins registered |
| Core | core.session.http | pass | 18 ms | Session and handle lifecycle (HTTP) |
| Core | core.session.ws | pass | 17 ms | Session and handle lifecycle (WebSockets) |
| Core | core.errors | pass | 19 ms | Error replies |
| Core | core.longpoll | pass | 14 ms | HTTP long poll |
| Admin API | admin.auth | pass | 10 ms | Admin secret is enforced |
| Admin API | admin.sessions.http | pass | 35 ms | Sessions, handles and handle_info (HTTP) |
| Admin API | admin.sessions.ws | pass | 39 ms | Sessions, handles and handle_info (WS) |
| Admin API | admin.settings | pass | 81 ms | Runtime settings |
| Admin API | admin.queries | FAIL | 43 ms | Queries: transports, loggers, handlers, loops, resolve, STUN |
| EchoTest | echotest.media | pass | 1667 ms | WebRTC echo with audio and video |
| EchoTest | echotest.datachannel | pass | 1756 ms | Data channel echo |
| EchoTest | echotest.record | pass | 2312 ms | Plugin recording (.mjr) |
| VideoRoom | videoroom.rooms | pass | 121 ms | Room management |
| VideoRoom | videoroom.publish-subscribe | FAIL | 16064 ms | Publish and subscribe |
| VideoRoom | videoroom.datachannel | FAIL | 21574 ms | Data channel in a room |
| VideoCall | videocall.call | pass | 1911 ms | Register, call, accept, hang up |
| Streaming | streaming.mountpoints | pass | 75 ms | Mountpoint management |
| Streaming | streaming.watch | pass | 1643 ms | Watch a mountpoint |
| RecordPlay | recordplay.record-play | FAIL | 15342 ms | Record, list, play back |
| TextRoom | textroom.chat | pass | 5871 ms | Chat over data channels |
| AudioBridge | audiobridge.mix | FAIL | 10974 ms | Join the mixer |
| SIP | sip.register | pass | 1184 ms | Guest registration and outgoing call |
| NoSIP | nosip.generate-process | pass | 927 ms | Generate a plain-RTP offer, process an answer |
| Lua | lua.echotest | pass | 1345 ms | Scripted EchoTest (Lua) |
| Duktape (JavaScript) | duktape.echotest | pass | 1357 ms | Scripted EchoTest (JavaScript) |
The failures, explained
admin.queries
Where it stopped: "query_transport janus.transport.http", "query_logger janus.logger.jsonlog"
query_transport and query_logger answer error 490 Not supported in this build. loops_info, resolve_address, message_plugin, custom_logline and query_eventhandler answer as the C server does.
Look in: crates/janus-core/src/admin.rs
✗ query_transport janus.transport.http — {"janus":"error","transaction":"bench-mw29xfnc-2c","error":{"code":490,"reason":"Not supported in this build: query_transport"}}
✗ query_logger janus.logger.jsonlog — {"janus":"error","transaction":"bench-mw29xfni-2d","error":{"code":490,"reason":"Not supported in this build: query_logger"}}
✓ query_eventhandler (none loaded: error 462 or 460 is the C answer)
✓ loops_info
✓ resolve_address localhost
✓ message_plugin to videoroom (list)
✓ custom_loglinevideoroom.publish-subscribe
Where it stopped: "subscriber: wait for the offer event"
A subscriber's join is answered with an attached event that carries no jsep, and no offer ever follows. The publisher side is healthy: the server receives its audio and video and counts one subscriber. This is the documented gap "The subscriber side of SDP" at the top of plugins/videoroom/src/lib.rs. Without it, nobody can receive a VideoRoom stream.
Look in: plugins/videoroom/src/lib.rs, subscriber.rs, subscriber_req.rs
✓ publisher: attach
✓ publisher: join room 1234 — id 572436
✓ publisher: publish with offer
✓ publisher: connected
✓ publisher: webrtcup event
✓ publisher: listparticipants shows it publishing
✓ subscriber: attach
✓ subscriber: join feed
✗ subscriber: wait for the offer event — timeout after 15000 ms waiting for the subscriber offer on handle 4113929698473511 (janus.plugin.videoroom). Publisher side: handle_info: ice ready, dtls connected, audio: in 754p out 0p; video: in 461p out 0p, plugin {"type":"publisher","room":1234,"id":572436,"private_id":943844276,"bitrate":512000,"streams":[{"type":"audio","mindex":0,"mid":"0","codec":"videoroom.datachannel
Where it stopped: (see below)
Same cause as videoroom.publish-subscribe: the subscriber never gets an offer, so the data channel cannot reach it.
Look in: plugins/videoroom/src/lib.rs
recordplay.record-play
Where it stopped: "recorded audio arrives"
Recording works (record negotiates and connects, stop is accepted, and list shows the new recording) and play negotiates and connects, but the server sends no RTP: handle_info shows 0 packets in both directions. The playout thread is a listed gap.
Look in: plugins/recordplay/src/lib.rs ("Missing: .mjr file I/O, playout thread" in parity/status.json)
✓ record with an offer
✓ connected
✓ stop
✓ list contains the recording (polls for 5 s)
✓ play it back
✓ playback connects
✗ recorded audio arrives — no audio within 10000 ms ({"audioBytesIn":0,"videoBytesIn":0,"audioPacketsIn":0,"videoPacketsIn":0,"audioBytesOut":0,"videoBytesOut":0,"framesDecoded":0}). handle_info: ice ready, dtls connected, audio: in 0p out 0p; video: in 0p out 0p, plugin {"type":"player","recording_id":4621808467726611,"recording_name":"bench-32e7w2","hangingup":0,"destroyed":0}audiobridge.mix
Where it stopped: "mixed audio arrives"
Joining works and the server receives our audio (handle_info: 496 packets in), but it sends 0 packets back: no mixed audio reaches the peer connection.
Look in: plugins/audiobridge/src/lib.rs (and parity/status.json notes for the AudioBridge component)
✓ list rooms — 1 room(s)
✓ join room 1234 with an offer
✓ connected
✗ mixed audio arrives — no mixed audio within 10000 ms ({"audioBytesIn":0,"videoBytesIn":0,"audioPacketsIn":0,"videoPacketsIn":0,"audioBytesOut":23558,"videoBytesOut":0,"framesDecoded":0}). handle_info: ice ready, dtls connected, audio: in 496p out 0p, plugin {"state":"inroom","room":1234,"id":76962395314107,"display":"bench","muted":false,"active":true,"talking":false,"fec":true,"expected-loss":0One more failure the runner hides on purpose
Chrome does not send its LAN address in ICE candidates when the page has no camera permission. It sends a random .local name (mDNS) instead. The Rust ICE agent does not pair .local remote candidates, and it does not learn the peer's address from the connectivity checks either, so the server never starts DTLS. The browser then sits with iceConnectionState: connected and connectionState: connecting for ever.
The runner starts Chrome with --disable-features=WebRtcHideLocalIpsWithMdns so the rest of the matrix can run. With npm run e2e -- --only echotest.media --mdns the same check fails:
✓ send offer, apply answer
✗ ICE and DTLS connect — peer connection not connected after 20000 ms (state connecting, ice connected)It matters for real users: with the camera permission granted Chrome exposes real addresses and everything on this page works, but a page without permission, and Safari or Firefox by default, will not connect. crates/janus-ice/src/agent.rs lists "mDNS candidates, which are never paired" among what the agent does not do.
The same run over WebSockets
The plugin checks use the transport chosen on the Threshold page. A second run with npm run e2e -- --transport ws sends every plugin message over a janus-protocol WebSocket instead of HTTP and long polling: 22 passed, 6 failed, 0 skipped of 28.
Checks whose result differs from the HTTP run:
| Check | HTTP | WebSockets | Detail |
|---|---|---|---|
videocall.call | pass | fail | timeout after 15000 ms waiting for incomingcall on handle 4427459746680656 (janus.plugin.videocall) |
videocall.call. An event for another session is written on the wrong socket. When alice calls bob over WebSockets, bob's incomingcall event (its session_id and sender are bob's) arrives on alice's connection, between her ack and her calling event, and bob's own socket receives nothing. Bob never sees the call. Over HTTP the long poll of each session is separate, so the same call works. Reproduced with two raw WebSocket clients and no browser: see the notes at the foot of this page. This affects every plugin that pushes an event to a session other than the requester's (VideoCall, VideoRoom notifications, and so on). The routing code is janus/src/ws_listener.rs.
What a pass does not cover
- NoSIP
generate-processproves the signalling and that the browser connects. The docs workers also found that plain RTP sent to the server by the peer never reaches the browser (browser to peer works), so the NoSIP media path is half done. See the NoSIP page. - SIP
registerplaces a call to a tiny UDP peer and connects ICE and DTLS; it does not check RTP in either direction. - Simulcast, SVC, RTP forwarding, recording playback, ICE restart, TURN, ICE-TCP, TLS transports, MQTT, RabbitMQ, nanomsg have no Bench check. The plugin and transport pages say which of them were exercised by hand.
What each check ran against
Core
core.info.http(pass, 6 ms): ✓ GET /info; ✓ fieldscore.info.ws(pass, 9 ms): ✓ open ws with janus-protocol; ✓ infocore.plugins(pass, 35 ms): ✓ janus.plugin.echotest; ✓ janus.plugin.videocall; ✓ janus.plugin.videoroom; ✓ janus.plugin.streaming; ✓ janus.plugin.recordplay; ✓ janus.plugin.textroom; ✓ janus.plugin.audiobridge; ✓ janus.plugin.sip; ✓ janus.plugin.nosipcore.session.http(pass, 18 ms): ✓ open transport; ✓ create session; ✓ keepalive; ✓ attach echotest; ✓ detach handle; ✓ destroy session; ✓ request on destroyed session is refusedcore.session.ws(pass, 17 ms): ✓ open transport; ✓ create session; ✓ keepalive; ✓ attach echotest; ✓ detach handle; ✓ destroy session; ✓ request on destroyed session is refusedcore.errors(pass, 19 ms): ✓ unknown request; ✓ attach to a plugin that does not exist; ✓ attach with no plugin; ✓ message to an unknown handle; ✓ unknown session; ✓ nojanusmember at all (456, no transaction in the reply); ✓ body that is not JSON (454)core.longpoll(pass, 14 ms): ✓ message and wait for its event; ✓ a GET for an unknown session answers 404 with no body (as the C server)
Admin API
admin.auth(pass, 10 ms): ✓ wrong secret is refused (403); ✓ no secret is refused (403); ✓ right secret is acceptedadmin.sessions.http(pass, 35 ms): ✓ list_sessions; ✓ list_handles; ✓ handle_info; ✓ handle_info plugin_only; ✓ destroy_session via adminadmin.sessions.ws(pass, 39 ms): ✓ list_sessions; ✓ list_handles; ✓ handle_info; ✓ handle_info plugin_only; ✓ destroy_session via adminadmin.settings(pass, 81 ms): ✓ set_log_level 5; ✓ restore log level; ✓ set_session_timeout 90; ✓ restore session timeout; ✓ set_min_nack_queue 300; ✓ restore nack queue; ✓ set_no_media_timer 2; ✓ restore no_media_timer; ✓ set_slowlink_threshold 6; ✓ restore slowlink; ✓ accept_new_sessions false refuses new sessions (472)admin.queries(fail, 43 ms): ✗ query_transport janus.transport.http; ✗ query_logger janus.logger.jsonlog; ✓ query_eventhandler (none loaded: error 462 or 460 is the C answer); ✓ loops_info; ✓ resolve_address localhost; ✓ message_plugin to videoroom (list); ✓ custom_logline
EchoTest
echotest.media(pass, 1667 ms): ✓ attach; ✓ synthetic tone and video; ✓ send offer, apply answer; ✓ ICE and DTLS connect; ✓ webrtcup event; ✓ echoed audio arrives; ✓ echoed video arrives; ✓ media event for audio; ✓ configure: audio off, bitrate cap; ✓ hangupechotest.datachannel(pass, 1756 ms): ✓ data channel opens; ✓ text echoes backechotest.record(pass, 2312 ms): ✓ stop recording
VideoRoom
videoroom.rooms(pass, 121 ms): ✓ list; ✓ exists 1234; ✓ create; ✓ edit with the wrong secret fails; ✓ edit; ✓ allowed enable and add; ✓ listparticipants of an empty room; ✓ destroy; ✓ destroyed room does not existvideoroom.publish-subscribe(fail, 16064 ms): ✓ publisher: attach; ✓ publisher: join room 1234; ✓ publisher: publish with offer; ✓ publisher: connected; ✓ publisher: webrtcup event; ✓ publisher: listparticipants shows it publishing; ✓ subscriber: attach; ✓ subscriber: join feed; ✗ subscriber: wait for the offer eventvideoroom.datachannel(fail, 21574 ms):
VideoCall
videocall.call(pass, 1911 ms): ✓ register alice-uz0uq7; ✓ register bob-gpvkzy; ✓ registering the same name twice fails; ✓ list shows both users; ✓ alice calls bob; ✓ bob receives incomingcall with an offer; ✓ bob accepts with an answer; ✓ alice gets accepted with the answer; ✓ both peers connect; ✓ alice hears bob (audio); ✓ bob sees alice (video); ✓ alice hangs up
Streaming
streaming.mountpoints(pass, 75 ms): ✓ list; ✓ info 1; ✓ create an RTP mountpoint; ✓ destroystreaming.watch(pass, 1643 ms): ✓ watch mountpoint 1; ✓ answer and start; ✓ ICE and DTLS connect; ✓ status event: started; ✓ RTP from the feeder reaches the browser; ✓ stop
RecordPlay
recordplay.record-play(fail, 15342 ms): ✓ record with an offer; ✓ connected; ✓ stop; ✓ list contains the recording (polls for 5 s); ✓ play it back; ✓ playback connects; ✗ recorded audio arrives
TextRoom
textroom.chat(pass, 5871 ms): ✓ alice: setup and open the data channel; ✓ bob: setup and open the data channel; ✓ alice joins room 1234; ✓ bob joins and sees alice in the participants; ✓ alice is told that bob joined; ✓ bob sends a public message; ✓ alice receives it; ✓ bob whispers to alice; ✓ alice receives the whisper; ✓ list participants; ✓ both leave
AudioBridge
audiobridge.mix(fail, 10974 ms): ✓ list rooms; ✓ join room 1234 with an offer; ✓ connected; ✗ mixed audio arrives
SIP
sip.register(pass, 1184 ms): ✓ register as guest (type: guest); ✓ call the peer; ✓ peer answers (accepted with an answer); ✓ ICE and DTLS connect; ✓ hangup
NoSIP
nosip.generate-process(pass, 927 ms): ✓ generate; ✓ process a plain answer; ✓ browser connects; ✓ hangup
Lua
lua.echotest(pass, 1345 ms): ✓ attach; ✓ offer through the script; ✓ ICE and DTLS connect; ✓ echoed audio arrives; ✓ echoed video arrives
Duktape (JavaScript)
duktape.echotest(pass, 1357 ms): ✓ attach; ✓ offer through the script; ✓ ICE and DTLS connect; ✓ echoed audio arrives; ✓ echoed video arrives