Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend stats summary with call device and user count based on room state #3424

Merged
merged 56 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
82303b9
send expected peer connections to posthog.
toger5 May 30, 2023
91dfdd4
add tests
toger5 May 31, 2023
538b787
change GroupCallStats initialized
toger5 May 31, 2023
0bc2bfd
prettier
toger5 May 31, 2023
dd21ee2
more test and catch for promise
toger5 May 31, 2023
d383d2d
Merge branch 'develop' into splitBrainIndicatorRoomState
toger5 May 31, 2023
b23b8fd
seperate the participant logic in a summary extend function
toger5 Jun 1, 2023
45e8ae4
remove unused
toger5 Jun 2, 2023
2c512c8
Merge branch 'develop' into splitBrainIndicatorRoomState
toger5 Jun 2, 2023
1b42313
rename summaryStatsReportGatherer to "Reporter"
toger5 Jun 2, 2023
1f99cd0
review
toger5 Jun 5, 2023
c4e3b90
Update src/webrtc/stats/groupCallStats.ts
toger5 Jun 5, 2023
39465e3
revert rename
toger5 Jun 7, 2023
0fd11ac
Update all non-major dependencies (#3433)
renovate[bot] Jun 2, 2023
600f26d
Update definitelyTyped (#3430)
renovate[bot] Jun 2, 2023
de34d24
Export FALLBACK_ICE_SERVER (#3429)
t3chguy Jun 2, 2023
6b60287
Add an integration test for verification (#3436)
richvdh Jun 2, 2023
44ed483
Always show a summary after Jest tests (#3440)
richvdh Jun 2, 2023
dd23e4b
Use correct /v3 prefix for /refresh (#3016)
davidisaaclee Jun 3, 2023
5976d69
Update Mutual Rooms (MSC2666) support (#3381)
ShadowJonathan Jun 5, 2023
3895fbb
GHA: build and cypress-test a copy of element-web after each push (#3…
richvdh Jun 5, 2023
6f79cd2
Fix downstream-artifacts build (#3443)
richvdh Jun 5, 2023
1e2f701
Fix edge cases around 2nd order relations and threads (#3437)
t3chguy Jun 5, 2023
d4e3452
Make sliding sync linearize processing of sync requests (#3442)
t3chguy Jun 5, 2023
8ccbb8d
Disable downstream artifacts build for develop branch (#3444)
richvdh Jun 5, 2023
e1ce16a
Export thread-related types from SDK (#3447)
stas-demydiuk Jun 6, 2023
a21736c
Integration test for QR code verification (#3439)
richvdh Jun 6, 2023
9afad10
Add `getShowSasCallbacks`, `getShowQrCodeCallbacks` to VerifierBase (…
richvdh Jun 6, 2023
2c94bae
Fix changelog_head.py script to be Python 3 compatible
RiotRobot May 16, 2023
8bab6cd
Prepare changelog for v25.2.0-rc.1
RiotRobot May 16, 2023
1aeaba5
v25.2.0-rc.1
RiotRobot May 16, 2023
b0a73fc
Fix tsconfig-build.json
RiotRobot May 16, 2023
ec3fd91
Prepare changelog for v25.2.0-rc.2
RiotRobot May 16, 2023
1c67b38
v25.2.0-rc.2
RiotRobot May 16, 2023
a3c1656
Fix docs deployment
t3chguy May 16, 2023
6969446
Prepare changelog for v25.2.0-rc.3
RiotRobot May 16, 2023
9a79554
v25.2.0-rc.3
RiotRobot May 16, 2023
6ad2cc3
Prepare changelog for v25.2.0-rc.4
RiotRobot May 16, 2023
4c250b5
v25.2.0-rc.4
RiotRobot May 16, 2023
a8b327e
[Backport staging] Attempt a potential workaround for stuck notifs (#…
RiotRobot May 19, 2023
e255bab
Prepare changelog for v25.2.0-rc.5
RiotRobot May 19, 2023
bf107ac
v25.2.0-rc.5
RiotRobot May 19, 2023
e53368a
[Backport staging] Fix mark as unread button (#3401)
RiotRobot May 24, 2023
29b9889
Prepare changelog for v26.0.0-rc.1
RiotRobot Jun 1, 2023
144edca
v26.0.0-rc.1
RiotRobot Jun 1, 2023
e7a02b0
Prepare changelog for v26.0.0
RiotRobot Jun 6, 2023
3e1b3d9
v26.0.0
RiotRobot Jun 6, 2023
4c219cf
Resetting package fields for development
RiotRobot Jun 6, 2023
accd4a5
use cli.canSupport to determine intentional mentions support (#3445)
Jun 6, 2023
0fcfe9c
git fixup
toger5 Jun 7, 2023
4c09840
import updates
toger5 Jun 7, 2023
dd58296
merge with develop
toger5 Jun 7, 2023
7a19ddf
dont revert enricos change
toger5 Jun 7, 2023
d2c8b04
temp rename for lowercase
toger5 Jun 7, 2023
1eb1fdb
lowercase
toger5 Jun 7, 2023
ce5e00f
Merge branch 'develop' into toger5/splitbrainIndicatorRoomState
toger5 Jun 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions spec/test-utils/webrtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -745,3 +745,78 @@ export const REMOTE_SFU_DESCRIPTION =
"a=sctp-port:5000\n" +
"a=ice-ufrag:obZwzAcRtxwuozPZ\n" +
"a=ice-pwd:TWXNaPeyKTTvRLyIQhWHfHlZHJjtcoKs";

export const groupCallParticipantsFourOtherDevices = new Map([
[
new RoomMember("roomId0", "user1"),
new Map([
[
"deviceId0",
{
sessionId: "0",
screensharing: false,
},
],
[
"deviceId1",
{
sessionId: "1",
screensharing: false,
},
],
[
"deviceId2",
{
sessionId: "2",
screensharing: false,
},
],
]),
],
[
new RoomMember("roomId0", "user2"),
new Map([
[
"deviceId3",
{
sessionId: "0",
screensharing: false,
},
],
[
"deviceId4",
{
sessionId: "1",
screensharing: false,
},
],
]),
],
]);

export const groupCallParticipantsOneOtherDevice = new Map([
[
new RoomMember("roomId1", "thisMember"),
new Map([
[
"deviceId0",
{
sessionId: "0",
screensharing: false,
},
],
]),
],
[
new RoomMember("roomId1", "opponentMember"),
new Map([
[
"deviceId1",
{
sessionId: "1",
screensharing: false,
},
],
]),
],
]);
119 changes: 115 additions & 4 deletions spec/unit/webrtc/stats/summaryStatsReportGatherer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { SummaryStatsReportGatherer } from "../../../../src/webrtc/stats/summaryStatsReportGatherer";
import { SummaryStatsReporter } from "../../../../src/webrtc/stats/summaryStatsReporter";
import { StatsReportEmitter } from "../../../../src/webrtc/stats/statsReportEmitter";
import { groupCallParticipantsFourOtherDevices } from "../../../test-utils/webrtc";

describe("SummaryStatsReportGatherer", () => {
let reporter: SummaryStatsReportGatherer;
let reporter: SummaryStatsReporter;
let emitter: StatsReportEmitter;
beforeEach(() => {
emitter = new StatsReportEmitter();
emitter.emitSummaryStatsReport = jest.fn();
reporter = new SummaryStatsReportGatherer(emitter);
reporter = new SummaryStatsReporter(emitter);
});

describe("build Summary Stats Report", () => {
Expand Down Expand Up @@ -584,8 +585,118 @@ describe("SummaryStatsReportGatherer", () => {
percentageReceivedVideoMedia: 1,
maxJitter: 2,
maxPacketLoss: 40,
peerConnections: 3,
peerConnections: 4,
percentageConcealedAudio: 0,
});
});
it("should report missing peer connections", async () => {
const summary = [
{
isFirstCollection: true,
receivedMedia: 1,
receivedAudioMedia: 1,
receivedVideoMedia: 1,
audioTrackSummary: {
count: 1,
muted: 0,
maxJitter: 20,
maxPacketLoss: 5,
concealedAudio: 0,
totalAudio: 0,
},
videoTrackSummary: {
count: 1,
muted: 0,
maxJitter: 0,
maxPacketLoss: 0,
concealedAudio: 0,
totalAudio: 0,
},
},
{
isFirstCollection: false,
receivedMedia: 1,
receivedAudioMedia: 1,
receivedVideoMedia: 1,
audioTrackSummary: {
count: 1,
muted: 0,
maxJitter: 2,
maxPacketLoss: 5,
concealedAudio: 0,
totalAudio: 0,
},
videoTrackSummary: {
count: 1,
muted: 0,
maxJitter: 0,
maxPacketLoss: 40,
concealedAudio: 0,
totalAudio: 0,
},
},
];
reporter.build(summary);
expect(emitter.emitSummaryStatsReport).toHaveBeenCalledWith({
percentageReceivedMedia: 1,
percentageReceivedAudioMedia: 1,
percentageReceivedVideoMedia: 1,
maxJitter: 2,
maxPacketLoss: 40,
peerConnections: 2,
percentageConcealedAudio: 0,
});
});
});
describe("extend Summary Stats Report", () => {
it("should extend the report with the appropriate data based on a user map", async () => {
const summary = {
percentageReceivedMedia: 1,
percentageReceivedAudioMedia: 1,
percentageReceivedVideoMedia: 1,
maxJitter: 2,
maxPacketLoss: 40,
peerConnections: 4,
percentageConcealedAudio: 0,
};
SummaryStatsReporter.extendSummaryReport(summary, groupCallParticipantsFourOtherDevices);
expect(summary).toStrictEqual({
percentageReceivedMedia: 1,
percentageReceivedAudioMedia: 1,
percentageReceivedVideoMedia: 1,
maxJitter: 2,
maxPacketLoss: 40,
peerConnections: 4,
percentageConcealedAudio: 0,
oppUsersInCall: 1,
oppDevicesInCall: 4,
diffDevicesToPeerConnections: 0,
ratioPeerConnectionToDevices: 1,
});
});
it("should extend the report data based on a user map", async () => {
const summary = {
percentageReceivedMedia: 1,
percentageReceivedAudioMedia: 1,
percentageReceivedVideoMedia: 1,
maxJitter: 2,
maxPacketLoss: 40,
peerConnections: 4,
percentageConcealedAudio: 0,
};
SummaryStatsReporter.extendSummaryReport(summary, new Map());
expect(summary).toStrictEqual({
percentageReceivedMedia: 1,
percentageReceivedAudioMedia: 1,
percentageReceivedVideoMedia: 1,
maxJitter: 2,
maxPacketLoss: 40,
peerConnections: 4,
percentageConcealedAudio: 0,
oppUsersInCall: 0,
oppDevicesInCall: 0,
diffDevicesToPeerConnections: -4,
ratioPeerConnectionToDevices: 0,
});
});
});
Expand Down
10 changes: 10 additions & 0 deletions src/webrtc/groupCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { IScreensharingOpts } from "./mediaHandler";
import { mapsEqual } from "../utils";
import { GroupCallStats } from "./stats/groupCallStats";
import { ByteSentStatsReport, ConnectionStatsReport, StatsReport, SummaryStatsReport } from "./stats/statsReport";
import { SummaryStatsReporter } from "./stats/summaryStatsReporter";

export enum GroupCallIntent {
Ring = "m.ring",
Expand Down Expand Up @@ -98,6 +99,9 @@ export enum GroupCallStatsReportEvent {
SummaryStats = "GroupCall.summary_stats",
}

/**
* The final report-events that get consumed by client.
*/
export type GroupCallStatsReportEventHandlerMap = {
[GroupCallStatsReportEvent.ConnectionStats]: (report: GroupCallStatsReport<ConnectionStatsReport>) => void;
[GroupCallStatsReportEvent.ByteSentStats]: (report: GroupCallStatsReport<ByteSentStatsReport>) => void;
Expand Down Expand Up @@ -269,14 +273,18 @@ export class GroupCall extends TypedEventEmitter<
}

private onConnectionStats = (report: ConnectionStatsReport): void => {
// Final emit of the summary event, to be consumed by the client
this.emit(GroupCallStatsReportEvent.ConnectionStats, { report });
};

private onByteSentStats = (report: ByteSentStatsReport): void => {
// Final emit of the summary event, to be consumed by the client
this.emit(GroupCallStatsReportEvent.ByteSentStats, { report });
};

private onSummaryStats = (report: SummaryStatsReport): void => {
SummaryStatsReporter.extendSummaryReport(report, this.participants);
// Final emit of the summary event, to be consumed by the client
this.emit(GroupCallStatsReportEvent.SummaryStats, { report });
};

Expand Down Expand Up @@ -1595,6 +1603,8 @@ export class GroupCall extends TypedEventEmitter<
});

if (this.state === GroupCallState.Entered) this.placeOutgoingCalls();

// Update the participants stored in the stats object
};

private onStateChanged = (newState: GroupCallState, oldState: GroupCallState): void => {
Expand Down
8 changes: 5 additions & 3 deletions src/webrtc/stats/groupCallStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ limitations under the License.
import { CallStatsReportGatherer } from "./callStatsReportGatherer";
import { StatsReportEmitter } from "./statsReportEmitter";
import { CallStatsReportSummary } from "./callStatsReportSummary";
import { SummaryStatsReportGatherer } from "./summaryStatsReportGatherer";
import { SummaryStatsReporter } from "./summaryStatsReporter";

export class GroupCallStats {
private timer: undefined | ReturnType<typeof setTimeout>;
private readonly gatherers: Map<string, CallStatsReportGatherer> = new Map<string, CallStatsReportGatherer>();
public readonly reports = new StatsReportEmitter();
private readonly summaryStatsReportGatherer = new SummaryStatsReportGatherer(this.reports);
private readonly summaryStatsReportGatherer = new SummaryStatsReporter(this.reports);

public constructor(private groupCallId: string, private userId: string, private interval: number = 10000) {}

Expand Down Expand Up @@ -75,7 +75,9 @@ export class GroupCallStats {
summary.push(c.processStats(this.groupCallId, this.userId));
});

Promise.all(summary).then((s: Awaited<CallStatsReportSummary>[]) => this.summaryStatsReportGatherer.build(s));
Promise.all(summary)
.then((s: Awaited<CallStatsReportSummary>[]) => this.summaryStatsReportGatherer.build(s))
.catch((err) => alert(err));
robintown marked this conversation as resolved.
Show resolved Hide resolved
}

public setInterval(interval: number): void {
Expand Down
5 changes: 5 additions & 0 deletions src/webrtc/stats/statsReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,9 @@ export interface SummaryStatsReport {
maxPacketLoss: number;
percentageConcealedAudio: number;
peerConnections: number;
oppUsersInCall?: number;
robintown marked this conversation as resolved.
Show resolved Hide resolved
oppDevicesInCall?: number;
diffDevicesToPeerConnections?: number;
ratioPeerConnectionToDevices?: number;
// Todo: Decide if we want an index (or a timestamp) of this report in relation to the group call, to help differenciate when issues occur and ignore/track initial connection delays.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the names are interpretations of the actual statistics. I would name the values according to their real meaning and do the interpretation in Posthog. And I would also add the joined users in the room. Because it is unusual for a user to take part in a conference with several devices. If it happens too often, we can use this to identify a caching issues.

usersInRoom: number;
devicesInRoom: number;
diffDevicesToPeerConnections: number;
ratioPeerConnectionToDevices : number;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make the devices more comparable with the peerConnections i only count the devices without the "local" device. I changed it to oppDevicesInCall and oppUsersInCall.

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ limitations under the License.
import { StatsReportEmitter } from "./statsReportEmitter";
import { CallStatsReportSummary } from "./callStatsReportSummary";
import { SummaryStatsReport } from "./statsReport";
import { ParticipantState } from "../groupCall";
import { RoomMember } from "../../matrix";

interface CallStatsReportSummaryCounter {
receivedAudio: number;
Expand All @@ -22,7 +24,7 @@ interface CallStatsReportSummaryCounter {
totalAudio: number;
}

export class SummaryStatsReportGatherer {
export class SummaryStatsReporter {
toger5 marked this conversation as resolved.
Show resolved Hide resolved
public constructor(private emitter: StatsReportEmitter) {}

public build(allSummary: CallStatsReportSummary[]): void {
Expand All @@ -31,9 +33,12 @@ export class SummaryStatsReportGatherer {
// webrtcStats as basement all the calculation are 0. We don't want track the 0 stats.
const summary = allSummary.filter((s) => !s.isFirstCollection);
const summaryTotalCount = summary.length;
// For counting the peer connections we also want to consider the ignored summaries
const peerConnectionsCount = allSummary.length;
if (summaryTotalCount === 0) {
return;
}

const summaryCounter: CallStatsReportSummaryCounter = {
receivedAudio: 0,
receivedVideo: 0,
Expand Down Expand Up @@ -65,11 +70,33 @@ export class SummaryStatsReportGatherer {
? (summaryCounter.concealedAudio / summaryCounter.totalAudio).toFixed(decimalPlaces)
: 0,
),
peerConnections: summaryTotalCount,
peerConnections: peerConnectionsCount,
} as SummaryStatsReport;
this.emitter.emitSummaryStatsReport(report);
}

public static extendSummaryReport(
report: SummaryStatsReport,
callParticipants: Map<RoomMember, Map<string, ParticipantState>>,
): void {
// Calculate the actual number of devices based on the participants state event
// (this is used, to compare the expected participant count from the room state with the acutal peer connections)
// const devices = callParticipants.()
const devices: [string, ParticipantState][] = [];
const users: [RoomMember, Map<string, ParticipantState>][] = [];
for (const userEntry of callParticipants) {
users.push(userEntry);
for (const device of userEntry[1]) {
devices.push(device);
}
}
report.oppDevicesInCall = Math.max(0, devices.length - 1);
report.oppUsersInCall = Math.max(0, users.length - 1);
report.diffDevicesToPeerConnections = Math.max(0, devices.length - 1) - report.peerConnections;
report.ratioPeerConnectionToDevices =
Math.max(0, devices.length - 1) == 0 ? 0 : report.peerConnections / (devices.length - 1);
}

private countTrackListReceivedMedia(counter: CallStatsReportSummaryCounter, stats: CallStatsReportSummary): void {
let hasReceivedAudio = false;
let hasReceivedVideo = false;
Expand Down