Skip to content

Commit eca85ec

Browse files
committed
fix: refactor detector
1 parent 4f8b326 commit eca85ec

File tree

2 files changed

+84
-39
lines changed

2 files changed

+84
-39
lines changed

.eslintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"no-underscore-dangle": "off",
1616
"max-len": ["error", { "code": 120 }],
1717
"import/extensions": "off",
18-
"import/no-cycle": "off"
18+
"import/no-cycle": "off",
19+
"no-continue": "off"
1920
}
2021
}

src/detectors/VideoDecoderIssueDetector.ts

+82-38
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
import {
22
IssueDetectorResult,
3+
IssueReason,
4+
IssueType,
35
WebRTCStatsParsed,
46
} from '../types';
57
import BaseIssueDetector, { BaseIssueDetectorParams } from './BaseIssueDetector';
68

79
interface VideoDecoderIssueDetectorParams extends BaseIssueDetectorParams {
8-
decodeTimePerFrameIncreaseSpeedThreshold?: number;
9-
minDecodeTimePerFrameIncreaseCases?: number;
10+
volatilityThreshold?: number;
11+
affectedStreamsPercentThreshold?: number;
1012
}
1113

1214
class VideoDecoderIssueDetector extends BaseIssueDetector {
13-
#decodeTimePerFrameIncreaseSpeedThreshold: number;
15+
#volatilityThreshold: number;
1416

15-
#minDecodeTimePerFrameIncreaseCases: number;
17+
#affectedStreamsPercentThreshold: number;
1618

1719
constructor(params: VideoDecoderIssueDetectorParams = {}) {
1820
super(params);
19-
this.#decodeTimePerFrameIncreaseSpeedThreshold = params.decodeTimePerFrameIncreaseSpeedThreshold ?? 1.05;
20-
this.#minDecodeTimePerFrameIncreaseCases = params.minDecodeTimePerFrameIncreaseCases ?? 3;
21+
this.#volatilityThreshold = params.volatilityThreshold ?? 1.5;
22+
this.#affectedStreamsPercentThreshold = params.affectedStreamsPercentThreshold ?? 50;
2123
}
2224

2325
performDetection(data: WebRTCStatsParsed): IssueDetectorResult {
@@ -28,41 +30,83 @@ class VideoDecoderIssueDetector extends BaseIssueDetector {
2830
}
2931

3032
private processData(data: WebRTCStatsParsed): IssueDetectorResult {
31-
const currentIncomeVideoStreams = data.video.inbound;
32-
const allLastProcessedStats = this
33-
.getAllLastProcessedStats(data.connection.id);
34-
3533
const issues: IssueDetectorResult = [];
3634

37-
currentIncomeVideoStreams.forEach((incomeVideoStream) => {
38-
const lastIncomeVideoStreamStats = allLastProcessedStats
39-
.map((connectionStats) => connectionStats.video.inbound.find(
40-
(videoStreamStats) => videoStreamStats.id === incomeVideoStream.id,
41-
))
42-
.filter((stats) => (stats?.framesDecoded || 0) > 0 && (stats?.totalDecodeTime || 0) > 0);
43-
44-
if (lastIncomeVideoStreamStats.length < this.#minDecodeTimePerFrameIncreaseCases) {
45-
return;
46-
}
47-
48-
const decodeTimePerFrame = lastIncomeVideoStreamStats
49-
.map((stats) => (stats!.totalDecodeTime * 1000) / stats!.framesDecoded);
50-
51-
const currentDecodeTimePerFrame = (incomeVideoStream.totalDecodeTime * 1000) / incomeVideoStream.framesDecoded;
52-
decodeTimePerFrame.push(currentDecodeTimePerFrame);
53-
54-
const mean = decodeTimePerFrame.reduce((acc, val) => acc + val, 0) / decodeTimePerFrame.length;
55-
const squaredDiffs = decodeTimePerFrame.map((val) => (val - mean) ** 2);
56-
const variance = squaredDiffs.reduce((acc, val) => acc + val, 0) / squaredDiffs.length;
57-
const volatility = Math.sqrt(variance);
58-
59-
console.log({
60-
decodeTimePerFrame,
61-
mean,
62-
variance,
63-
volatility,
35+
const allProcessedStats = [
36+
...this.getAllLastProcessedStats(data.connection.id),
37+
data,
38+
];
39+
40+
const throtthedStreams = data.video.inbound
41+
.map((incomeVideoStream) => {
42+
const allDecodeTimePerFrame: number[] = [];
43+
44+
// We need at least 4 elements to have enough representation
45+
if (allProcessedStats.length < 4) {
46+
return;
47+
}
48+
49+
// exclude first element to calculate accurate delta
50+
for (let i = 1; i < allProcessedStats.length; i += 1) {
51+
let deltaFramesDecoded = 0;
52+
let deltaTotalDecodeTime = 0;
53+
let decodeTimePerFrame = 0;
54+
55+
const videoStreamStats = allProcessedStats[i].video.inbound.find(
56+
(stream) => stream.id === incomeVideoStream.id,
57+
);
58+
59+
if (!videoStreamStats) {
60+
continue;
61+
}
62+
63+
const prevVideoStreamStats = allProcessedStats[i - 1].video.inbound.find(
64+
(stream) => stream.id === incomeVideoStream.id,
65+
);
66+
67+
if (prevVideoStreamStats) {
68+
deltaFramesDecoded = videoStreamStats.framesDecoded - prevVideoStreamStats.framesDecoded;
69+
deltaTotalDecodeTime = videoStreamStats.totalDecodeTime - prevVideoStreamStats.totalDecodeTime;
70+
}
71+
72+
if (deltaTotalDecodeTime > 0 && deltaFramesDecoded > 0) {
73+
decodeTimePerFrame = deltaTotalDecodeTime * 1000 / deltaFramesDecoded;
74+
}
75+
76+
allDecodeTimePerFrame.push(decodeTimePerFrame);
77+
}
78+
79+
// Calculate volatility
80+
const mean = allDecodeTimePerFrame.reduce((acc, val) => acc + val, 0) / allDecodeTimePerFrame.length;
81+
const squaredDiffs = allDecodeTimePerFrame.map((val) => (val - mean) ** 2);
82+
const variance = squaredDiffs.reduce((acc, val) => acc + val, 0) / squaredDiffs.length;
83+
const volatility = Math.sqrt(variance);
84+
85+
const isDecodeTimePerFrameIncrease = allDecodeTimePerFrame.every(
86+
(decodeTimePerFrame, index) => {
87+
return index === 0 || decodeTimePerFrame > allDecodeTimePerFrame[index - 1];
88+
},
89+
);
90+
91+
console.log({
92+
allDecodeTimePerFrame,
93+
isDecodeTimePerFrameIncrease,
94+
mean,
95+
volatility,
96+
});
97+
98+
return volatility > this.#volatilityThreshold && isDecodeTimePerFrameIncrease;
99+
})
100+
.filter((throttled) => throttled);
101+
102+
103+
const affectedStreamsPercent = throtthedStreams.length / (data.video.inbound.length / 100);
104+
if (affectedStreamsPercent > this.#affectedStreamsPercentThreshold) {
105+
issues.push({
106+
type: IssueType.CPU,
107+
reason: IssueReason.DecoderCPUThrottling,
64108
});
65-
});
109+
}
66110

67111
return issues;
68112
}

0 commit comments

Comments
 (0)