diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html')
-rw-r--r-- | dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html | 838 |
1 files changed, 838 insertions, 0 deletions
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html new file mode 100644 index 0000000000..739675eab3 --- /dev/null +++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html @@ -0,0 +1,838 @@ +<!DOCTYPE HTML> +<html> +<head> + <script type="application/javascript" src="pc.js"></script> +</head> +<body> +<pre id="test"> +<script type="application/javascript"> + createHTML({ + bug: "1337525", + title: "webRtc Stats composition and sanity" + }); +const statsExpectedByType = { + "inbound-rtp": { + expected: [ + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "packetsReceived", + "packetsLost", + "bytesReceived", + "jitter", + ], + optional: [ + "remoteId", + "nackCount", + ], + localVideoOnly: [ + "discardedPackets", + "framerateStdDev", + "framerateMean", + "bitrateMean", + "bitrateStdDev", + "firCount", + "pliCount", + "framesDecoded", + ], + unimplemented: [ + "mediaTrackId", + "transportId", + "codecId", + "packetsDiscarded", + "associateStatsId", + "sliCount", + "packetsRepaired", + "fractionLost", + "burstPacketsLost", + "burstLossCount", + "burstDiscardCount", + "gapDiscardRate", + "gapLossRate", + "qpSum", // Not yet implemented for inbound media, see bug 1519590 + ], + deprecated: [ + "mozRtt", + "isRemote", + ], + }, + "outbound-rtp": { + expected: [ + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "packetsSent", + "bytesSent", + "remoteId", + ], + optional: [ + "nackCount", + ], + localVideoOnly: [ + "droppedFrames", + "bitrateMean", + "bitrateStdDev", + "framerateMean", + "framerateStdDev", + "framesEncoded", + "firCount", + "pliCount", + "qpSum", + ], + unimplemented: [ + "mediaTrackId", + "transportId", + "codecId", + "sliCount", + "targetBitrate", + ], + deprecated: [ + "isRemote", + ], + }, + "remote-inbound-rtp": { + expected: [ + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "packetsLost", + "jitter", + "localId", + ], + optional: [ + "roundTripTime", + "nackCount", + "packetsReceived", + "bytesReceived", + ], + unimplemented: [ + "mediaTrackId", + "transportId", + "codecId", + "packetsDiscarded", + "associateStatsId", + "sliCount", + "packetsRepaired", + "fractionLost", + "burstPacketsLost", + "burstLossCount", + "burstDiscardCount", + "gapDiscardRate", + "gapLossRate", + ], + deprecated: [ + "mozRtt", + "isRemote", + ], + }, + "remote-outbound-rtp": { + expected: [ + "id", + "timestamp", + "type", + "ssrc", + "mediaType", + "kind", + "packetsSent", + "bytesSent", + "localId", + "remoteTimestamp", + ], + optional: [ + "nackCount", + ], + unimplemented: [ + "mediaTrackId", + "transportId", + "codecId", + "sliCount", + "targetBitrate", + ], + deprecated: [ + "isRemote", + ], + }, + "csrc": { skip: true }, + "codec": { skip: true }, + "peer-connection": { skip: true }, + "data-channel": { skip: true }, + "track": { skip: true }, + "transport": { skip: true }, + "candidate-pair": { + expected: [ + "id", + "timestamp", + "type", + "transportId", + "localCandidateId", + "remoteCandidateId", + "state", + "priority", + "nominated", + "writable", + "readable", + "bytesSent", + "bytesReceived", + "lastPacketSentTimestamp", + "lastPacketReceivedTimestamp", + ], + optional: [ + "selected", + ], + unimplemented: [ + "totalRoundTripTime", + "currentRoundTripTime", + "availableOutgoingBitrate", + "availableIncomingBitrate", + "requestsReceived", + "requestsSent", + "responsesReceived", + "responsesSent", + "retransmissionsReceived", + "retransmissionsSent", + "consentRequestsSent", + ], + deprecated: [], + }, + "local-candidate": { + expected: [ + "id", + "timestamp", + "type", + "address", + "protocol", + "port", + "candidateType", + "priority", + ], + optional: [ + "relayProtocol", + "proxied", + ], + unimplemented: [ + "networkType", + "url", + "transportId" + ], + deprecated: [ + "candidateId", + "portNumber", + "ipAddress", + "componentId", + "mozLocalTransport", + "transport", + ], + }, + "remote-candidate": { + expected: [ + "id", + "timestamp", + "type", + "address", + "protocol", + "port", + "candidateType", + "priority", + ], + optional: [ + "relayProtocol", + "proxied", + ], + unimplemented: [ + "networkType", + "url", + "transportId", + ], + deprecated: [ + "candidateId", + "portNumber", + "ipAddress", + "componentId", + "mozLocalTransport", + "transport", + ], + }, + "certificate": { skip: true }, +}; + +["in", "out"].forEach(pre => { + let s = statsExpectedByType[pre + "bound-rtp"]; + s.optional = [...s.optional, ...s.localVideoOnly]; +}); + +// +// Checks that the fields in a report conform to the expectations in +// statExpectedByType +// +var checkExpectedFields = report => report.forEach(stat => { + let expectations = statsExpectedByType[stat.type]; + ok(expectations, "Stats type " + stat.type + " was expected"); + // If the type is not expected or if it is flagged for skipping continue to + // the next + if (!expectations || expectations.skip) { + return; + } + // Check that all required fields exist + expectations.expected.forEach(field => { + ok(field in stat, "Expected stat field " + stat.type + "." + field + + " exists"); + }); + // Check that each field is either expected or optional + let allowed = [...expectations.expected, ...expectations.optional]; + Object.keys(stat).forEach(field => { + ok(allowed.includes(field), "Stat field " + stat.type + "." + field + + ` is allowed. ${JSON.stringify(stat)}`); + }); + + // + // Ensure that unimplemented fields are not implemented + // note: if a field is implemented it should be moved to expected or + // optional. + // + expectations.unimplemented.forEach(field => { + ok(!Object.keys(stat).includes(field), "Unimplemented field " + stat.type + + "." + field + " does not exist."); + }); + + // + // Ensure that all deprecated fields are not present + // + expectations.deprecated.forEach(field => { + ok(!Object.keys(stat).includes(field), "Deprecated field " + stat.type + + "." + field + " does not exist."); + }); +}); + +var pedanticChecks = report => { + // Check that report is only-maplike + [...report.keys()].forEach(key => is(report[key], undefined, + `Report is not dictionary like, it lacks a property for key ${key}`)); + report.forEach((statObj, mapKey) => { + info(`"${mapKey} = ${JSON.stringify(statObj, null, 2)}`); + }); + // eslint-disable-next-line complexity + report.forEach((statObj, mapKey) => { + let tested = {}; + // Record what fields get tested. + // To access a field foo without marking it as tested use stat.inner.foo + let stat = new Proxy(statObj, { + get(stat, key) { + if (key == "inner") return stat; + tested[key] = true; + return stat[key]; + } + }); + + let expectations = statsExpectedByType[stat.type]; + + if (expectations.skip) { + return; + } + + // All stats share the following attributes inherited from RTCStats + is(stat.id, mapKey, stat.type + ".id is the same as the report key."); + + // timestamp + ok(stat.timestamp >= 0, stat.type + ".timestamp is not less than 0"); + // If the timebase for the timestamp is not properly set the timestamp will + // appear relative to the year 1970; Bug 1495446 + const date = new Date(stat.timestamp); + ok(date.getFullYear() > 1970, + `${stat.type}.timestamp is relative to current time, date=${date}`); + // + // RTCStreamStats attributes with common behavior + // + // inbound-rtp, outbound-rtp, remote-inbound-rtp, remote-outbound-rtp + // inherit from RTCStreamStats + if (["inbound-rtp", "outbound-rtp", + "remote-inbound-rtp", "remote-outbound-rtp"].includes(stat.type)) { + const isRemote = stat.type.startsWith("remote-"); + // + // Common RTCStreamStats fields + // + + // SSRC + ok(stat.ssrc, stat.type + ".ssrc has a value"); + + // kind + ok(["audio", "video"].includes(stat.kind), + stat.type + ".kind is 'audio' or 'video'"); + + // mediaType, renamed to kind but remains for backward compability. + ok(["audio", "video"].includes(stat.mediaType), + stat.type + ".mediaType is 'audio' or 'video'"); + + ok(stat.kind == stat.mediaType, "kind equals legacy mediaType"); + + if (isRemote) { + // local id + if (stat.localId) { + ok(report.has(stat.localId), + `localId ${stat.localId} exists in report.`); + is(report.get(stat.localId).ssrc, stat.ssrc, + "remote ssrc and local ssrc match."); + is(report.get(stat.localId).remoteId, stat.id, + "local object has remote object as it's own remote object."); + } + } else { + // remote id + if (stat.remoteId) { + ok(report.has(stat.remoteId), + `remoteId ${stat.remoteId} exists in report.`); + is(report.get(stat.remoteId).ssrc, stat.ssrc, + "remote ssrc and local ssrc match."); + is(report.get(stat.remoteId).localId, stat.id, + "remote object has local object as it's own local object."); + } + } + + // nackCount + if (stat.nackCount) { + ok(stat.nackCount >= 0, stat.type + ".nackCount is sane."); + } + + if (!isRemote && stat.inner.kind == "video") { + // firCount + ok(stat.firCount >= 0 && stat.firCount < 100, + stat.type + ".firCount is a sane number for a short test. value=" + + stat.firCount); + + // pliCount + ok(stat.pliCount >= 0 && stat.pliCount < 200, + stat.type + ".pliCount is a sane number for a short test. value=" + + stat.pliCount); + } + } + + if (stat.type == "inbound-rtp") { + // + // Required fields + // + + // packetsReceived + ok(stat.packetsReceived >= 0 + && stat.packetsReceived < 10 ** 5, + stat.type + ".packetsReceived is a sane number for a short test. value=" + + stat.packetsReceived); + + // bytesReceived + ok(stat.bytesReceived >= 0 + && stat.bytesReceived < 10 ** 9, // Not a magic number, just a guess + stat.type + ".bytesReceived is a sane number for a short test. value=" + + stat.bytesReceived); + + // packetsLost + ok(stat.packetsLost < 100, + stat.type + ".packetsLost is a sane number for a short test. value=" + + stat.packetsLost); + + // This should be much lower for audio, TODO: Bug 1330575 + let expectedJitter = stat.kind == "video" ? 0.5 : 1; + // jitter + ok(stat.jitter < expectedJitter, + stat.type + ".jitter is sane number for a local only test. value=" + + stat.jitter); + + // packetsDiscarded + // special exception for, TODO: Bug 1335967 + // if (!stat.inner.isRemote && stat.discardedPackets !== undefined) { + // ok(stat.packetsDiscarded < 100, stat.type + // + ".packetsDiscarded is a sane number for a short test. value=" + // + stat.packetsDiscarded); + // } + // if (stat.packetsDiscarded !== undefined) { + // ok(!stat.inner.isRemote, + // stat.type + ".packetsDiscarded is only set when isRemote is " + // + "false"); + // } + + // + // Optional fields + // + + // + // Local video only stats + // + if (stat.inner.kind != "video") { + expectations.localVideoOnly.forEach(field => { + ok(stat[field] === undefined, `${stat.type} does not have field ${field}` + + ` when kind is not 'video'`); + }); + } else { + expectations.localVideoOnly.forEach(field => { + ok(stat.inner[field] !== undefined, stat.type + " has field " + field + + " when kind is video"); + }); + // discardedPackets + ok(stat.discardedPackets < 100, stat.type + + ".discardedPackets is a sane number for a short test. value=" + + stat.discardedPackets); + // framesDecoded + ok(stat.framesDecoded > 0 && stat.framesDecoded < 1000000, stat.type + + ".framesDecoded is a sane number for a short test. value=" + + stat.framesDecoded); + // bitrateMean + // special exception, TODO: Bug 1341533 + if (stat.bitrateMean !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25, + // stat.type + ".bitrateMean is sane. value=" + // + stat.bitrateMean); + } + + // bitrateStdDev + // special exception, TODO Bug 1341533 + if (stat.bitrateStdDev !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.bitrateStdDev >= 0 && stat.bitrateStdDev < 2 ** 25, + // stat.type + ".bitrateStdDev is sane. value=" + // + stat.bitrateStdDev); + } + + // framerateMean + // special exception, TODO: Bug 1341533 + if (stat.framerateMean !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.framerateMean >= 0 && stat.framerateMean < 120, + // stat.type + ".framerateMean is sane. value=" + // + stat.framerateMean); + } + + // framerateStdDev + // special exception, TODO: Bug 1341533 + if (stat.framerateStdDev !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.framerateStdDev >= 0 && stat.framerateStdDev < 120, + // stat.type + ".framerateStdDev is sane. value=" + // + stat.framerateStdDev); + } + + } + } else if (stat.type == "remote-inbound-rtp") { + // roundTripTime + ok(stat.roundTripTime >= 0, stat.type + ".roundTripTime is sane with" + + "value of:" + stat.roundTripTime); + // + // Required fields + // + + // packetsLost + ok(stat.packetsLost < 100, + stat.type + ".packetsLost is a sane number for a short test. value=" + + stat.packetsLost); + + // jitter + ok(stat.jitter >= 0, + stat.type + ".jitter is sane number. value=" + + stat.jitter); + + // + // Optional fields + // + + // packetsReceived + if (stat.packetsReceived) { + ok(stat.packetsReceived >= 0 + && stat.packetsReceived < 10 ** 5, + stat.type + ".packetsReceived is a sane number for a short test. value=" + + stat.packetsReceived); + } + + // bytesReceived + if (stat.bytesReceived) { + ok(stat.bytesReceived >= 0 + && stat.bytesReceived < 10 ** 9, // Not a magic number, just a guess + stat.type + ".bytesReceived is a sane number for a short test. value=" + + stat.bytesReceived); + } + + } else if (stat.type == "outbound-rtp") { + // + // Required fields + // + + // packetsSent + ok(stat.packetsSent > 0 && stat.packetsSent < 10000, + stat.type + ".packetsSent is a sane number for a short test. value=" + + stat.packetsSent); + + // bytesSent + ok(stat.bytesSent, stat.type + ".bytesSent has a value." + + " Value not expected to be sane, bug 1339104. value=" + + stat.bytesSent); + + // + // Optional fields + // + + // + // Local video only stats + // + if (stat.inner.kind != "video") { + expectations.localVideoOnly.forEach(field => { + ok(stat[field] === undefined, stat.type + " does not have field " + + field + " when kind is not 'video'"); + }); + } else { + expectations.localVideoOnly.forEach(field => { + ok(stat.inner[field] !== undefined, stat.type + " has field " + field + + " when kind is video and isRemote is false"); + }); + + // bitrateMean + if (stat.bitrateMean !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25, + // stat.type + ".bitrateMean is sane. value=" + // + stat.bitrateMean); + } + + // bitrateStdDev + if (stat.bitrateStdDev !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.bitrateStdDev >= 0 && stat.bitrateStdDev < 2 ** 25, + // stat.type + ".bitrateStdDev is sane. value=" + // + stat.bitrateStdDev); + } + + // framerateMean + if (stat.framerateMean !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.framerateMean >= 0 && stat.framerateMean < 120, + // stat.type + ".framerateMean is sane. value=" + // + stat.framerateMean); + } + + // framerateStdDev + if (stat.framerateStdDev !== undefined) { + // TODO: uncomment when Bug 1341533 lands + // ok(stat.framerateStdDev >= 0 && stat.framerateStdDev < 120, + // stat.type + ".framerateStdDev is sane. value=" + // + stat.framerateStdDev); + } + + // droppedFrames + ok(stat.droppedFrames >= 0 && stat.droppedFrames < 100000, + stat.type + ".droppedFrames is a sane number. value=" + + stat.droppedFrames); + + // framesEncoded + ok(stat.framesEncoded >= 0 && stat.framesEncoded < 100000, stat.type + + ".framesEncoded is a sane number for a short test. value=" + + stat.framesEncoded); + + // qpSum + // techinically optional but should be supported for all of our codecs (on the encode side, see bug 1519590) + ok(stat.qpSum >= 0, + `${stat.type} qpSum is a sane number. value=${stat.qpSum}`); + } + } else if (stat.type == "remote-outbound-rtp") { + // + // Required fields + // + + // packetsSent + ok(stat.packetsSent > 0 && stat.packetsSent < 10000, + stat.type + ".packetsSent is a sane number for a short test. value=" + + stat.packetsSent); + + // bytesSent + ok(stat.bytesSent, stat.type + ".bytesSent has a value." + + " Value not expected to be sane, bug 1339104. value=" + + stat.bytesSent); + + ok(stat.remoteTimestamp !== undefined, `${stat.type}.remoteTimestamp ` + + ` is not undefined`); + + } else if (stat.type == "candidate-pair") { + info("candidate-pair is: " + JSON.stringify(stat)); + // + // Required fields + // + + // transportId + ok(stat.transportId, + stat.type + ".transportId has a value. value=" + + stat.transportId); + + // localCandidateId + ok(stat.localCandidateId, + stat.type + ".localCandidateId has a value. value=" + + stat.localCandidateId); + + // remoteCandidateId + ok(stat.remoteCandidateId, + stat.type + ".remoteCandidateId has a value. value=" + + stat.remoteCandidateId); + + // priority + ok(stat.priority, + stat.type + ".priority has a value. value=" + + stat.priority); + + // readable + ok(stat.readable, + stat.type + ".readable is true. value=" + + stat.readable); + + // writable + ok(stat.writable, + stat.type + ".writable is true. value=" + + stat.writable); + + // state + if (stat.state == "succeeded" && + stat.selected !== undefined && + stat.selected) { + info("candidate-pair state is succeeded and selected is true"); + // nominated + ok(stat.nominated, + stat.type + ".nominated is true. value=" + + stat.nominated); + + // bytesSent + ok(stat.bytesSent > 5000, + stat.type + ".bytesSent is a sane number (>5,000) for a short test. value=" + + stat.bytesSent); + + // bytesReceived + ok(stat.bytesReceived > 5000, + stat.type + ".bytesReceived is a sane number (>5,000) for a short test. value=" + + stat.bytesReceived); + + // lastPacketSentTimestamp + ok(stat.lastPacketSentTimestamp, + stat.type + ".lastPacketSentTimestamp has a value. value=" + + stat.lastPacketSentTimestamp); + + // lastPacketReceivedTimestamp + ok(stat.lastPacketReceivedTimestamp, + stat.type + ".lastPacketReceivedTimestamp has a value. value=" + + stat.lastPacketReceivedTimestamp); + + } else { + info("candidate-pair is _not_ both state == succeeded and selected"); + // nominated + ok(stat.nominated !== undefined, + stat.type + ".nominated exists. value=" + + stat.nominated); + ok(stat.bytesSent !== undefined, + stat.type + ".bytesSent exists. value=" + + stat.bytesSent); + ok(stat.bytesReceived !== undefined, + stat.type + ".bytesReceived exists. value=" + + stat.bytesReceived); + ok(stat.lastPacketSentTimestamp !== undefined, + stat.type + ".lastPacketSentTimestamp exists. value=" + + stat.lastPacketSentTimestamp); + ok(stat.lastPacketReceivedTimestamp !== undefined, + stat.type + ".lastPacketReceivedTimestamp exists. value=" + + stat.lastPacketReceivedTimestamp); + } + + + // + // Optional fields + // + // selected + ok(stat.selected === undefined || + ((stat.state == "succeeded" && stat.selected) || + !stat.selected), + stat.type + ".selected is undefined, true when state is succeeded, " + + "or false. value=" + + stat.selected); + + } else if (stat.type == "local-candidate" || stat.type == "remote-candidate") { + info(`candidate is ${JSON.stringify(stat)}`); + + // address + ok(stat.address, `${stat.type} has address. value=${stat.address}`); + + // protocol + ok(stat.protocol, `${stat.type} has protocol. value=${stat.protocol}`); + + // port + ok(stat.port >= 0, `${stat.type} has port >= 0. value=${stat.port}`); + ok(stat.port <= 65535, `${stat.type} has port <= 65535. value=${stat.port}`); + + // candidateType + ok(stat.candidateType, `${stat.type} has candidateType. value=${stat.candidateType}`); + + // priority + ok(stat.priority > 0 && stat.priority < (2**32 - 1), + `${stat.type} has priority between 1 and 2^32 - 1 inc. ` + + `value=${stat.priority}`); + + // relayProtocol + if (stat.type == "local-candidate" && stat.candidateType == "relay") { + ok(stat.relayProtocol, + `relay ${stat.type} has relayProtocol. value=${stat.relayProtocol}`); + } else { + is(stat.relayProtocol, undefined, + `relayProtocol is undefined for candidates that are not relay and ` + + `local. value=${stat.relayProtocol}`); + } + + // proxied + if (stat.proxied) { + ok(stat.proxied == "proxied" || stat.proxied == "non-proxied", + `${stat.type} has proxied. value=${stat.proxied}`); + } + } + + // + // Ensure everything was tested + // + [...expectations.expected, ...expectations.optional].forEach(field => { + ok(Object.keys(tested).includes(field), stat.type + "." + field + + " was tested."); + }); + }); +} + +var PC_LOCAL_TEST_LOCAL_STATS = test => { + return test.pcLocal.waitForSyncedRtcp().then(stats => { + checkExpectedFields(stats); + pedanticChecks(stats); + }); +} + +var PC_REMOTE_TEST_REMOTE_STATS = test => { + return test.pcRemote.waitForSyncedRtcp().then(stats => { + checkExpectedFields(stats); + pedanticChecks(stats); + }); +} + +runNetworkTest(async function (options) { + // We don't know how to get QP value when using Android system codecs. + if (navigator.userAgent.includes("Android")) { + await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false], + ["media.webrtc.hw.h264.enabled", false]); + } + + const test = new PeerConnectionTest(options); + + test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW", + [PC_LOCAL_TEST_LOCAL_STATS]); + + test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW", + [PC_REMOTE_TEST_REMOTE_STATS]); + + test.setMediaConstraints([{audio: true}, {video: true}], + [{audio: true}, {video: true}]); + test.run(); +}); +</script> +</pre> +</body> +</html> |