Skip to content

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.

Run2026-11-04T15:30:53.375Z
Serverjanus 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
BrowserChrome (headless, installed channel) with --autoplay-policy=no-user-gesture-required --disable-features=WebRtcHideLocalIpsWithMdns
Mediasynthetic: 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
TransportHTTP long poll for the plugin checks; the core checks also run over WebSockets
Result23 passed, 5 failed, 0 skipped of 28

Every check ​

GroupCheckResultTimeTitle
Corecore.info.httppass6 msServer info over HTTP
Corecore.info.wspass9 msServer info over WebSockets
Corecore.pluginspass35 msPlugins registered
Corecore.session.httppass18 msSession and handle lifecycle (HTTP)
Corecore.session.wspass17 msSession and handle lifecycle (WebSockets)
Corecore.errorspass19 msError replies
Corecore.longpollpass14 msHTTP long poll
Admin APIadmin.authpass10 msAdmin secret is enforced
Admin APIadmin.sessions.httppass35 msSessions, handles and handle_info (HTTP)
Admin APIadmin.sessions.wspass39 msSessions, handles and handle_info (WS)
Admin APIadmin.settingspass81 msRuntime settings
Admin APIadmin.queriesFAIL43 msQueries: transports, loggers, handlers, loops, resolve, STUN
EchoTestechotest.mediapass1667 msWebRTC echo with audio and video
EchoTestechotest.datachannelpass1756 msData channel echo
EchoTestechotest.recordpass2312 msPlugin recording (.mjr)
VideoRoomvideoroom.roomspass121 msRoom management
VideoRoomvideoroom.publish-subscribeFAIL16064 msPublish and subscribe
VideoRoomvideoroom.datachannelFAIL21574 msData channel in a room
VideoCallvideocall.callpass1911 msRegister, call, accept, hang up
Streamingstreaming.mountpointspass75 msMountpoint management
Streamingstreaming.watchpass1643 msWatch a mountpoint
RecordPlayrecordplay.record-playFAIL15342 msRecord, list, play back
TextRoomtextroom.chatpass5871 msChat over data channels
AudioBridgeaudiobridge.mixFAIL10974 msJoin the mixer
SIPsip.registerpass1184 msGuest registration and outgoing call
NoSIPnosip.generate-processpass927 msGenerate a plain-RTP offer, process an answer
Lualua.echotestpass1345 msScripted EchoTest (Lua)
Duktape (JavaScript)duktape.echotestpass1357 msScripted 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

text
✗ 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_logline

videoroom.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

text
✓ 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

text

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)

text
✓ 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)

text
✓ 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":0

One 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:

text
✓ 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:

CheckHTTPWebSocketsDetail
videocall.callpassfailtimeout 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-process proves 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 register places 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; ✓ fields
  • core.info.ws (pass, 9 ms): ✓ open ws with janus-protocol; ✓ info
  • core.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.nosip
  • core.session.http (pass, 18 ms): ✓ open transport; ✓ create session; ✓ keepalive; ✓ attach echotest; ✓ detach handle; ✓ destroy session; ✓ request on destroyed session is refused
  • core.session.ws (pass, 17 ms): ✓ open transport; ✓ create session; ✓ keepalive; ✓ attach echotest; ✓ detach handle; ✓ destroy session; ✓ request on destroyed session is refused
  • core.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; ✓ no janus member 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 accepted
  • admin.sessions.http (pass, 35 ms): ✓ list_sessions; ✓ list_handles; ✓ handle_info; ✓ handle_info plugin_only; ✓ destroy_session via admin
  • admin.sessions.ws (pass, 39 ms): ✓ list_sessions; ✓ list_handles; ✓ handle_info; ✓ handle_info plugin_only; ✓ destroy_session via admin
  • admin.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; ✓ hangup
  • echotest.datachannel (pass, 1756 ms): ✓ data channel opens; ✓ text echoes back
  • echotest.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 exist
  • videoroom.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 event
  • videoroom.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; ✓ destroy
  • streaming.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

GPL-3.0-or-later, like the Janus gateway it ports.