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

Time-to-first-frame metrics benchmarking using datachannel #1846

Merged
merged 54 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
0ac6851
data channel benchmarking message
niyatim23 Oct 16, 2023
f2e83e1
fix calculation
niyatim23 Oct 18, 2023
42e641a
send peerconnection, iceagent, signaling metrics to viewer via dc
niyatim23 Oct 30, 2023
049cc17
signaling breakdown
niyatim23 Nov 9, 2023
6d6b7a6
data channel benchmarking message
niyatim23 Oct 16, 2023
30800b4
fix calculation
niyatim23 Oct 18, 2023
d0ecea0
send peerconnection, iceagent, signaling metrics to viewer via dc
niyatim23 Oct 30, 2023
4ee0431
signaling breakdown
niyatim23 Nov 9, 2023
195b48e
Merge branch 'datachannel-benchmarking' of https://github.com/awslabs…
niyatim23 Nov 9, 2023
435ec74
cmake flag, ifdef
niyatim23 Nov 13, 2023
b08b79e
move signaling and offer / answer metrics out of signaling stats
niyatim23 Nov 13, 2023
a6bccba
remove callTime for start and end times in signaling
niyatim23 Nov 14, 2023
8a932db
remove new lines
niyatim23 Nov 14, 2023
8f6dfe8
clang format
niyatim23 Nov 14, 2023
9b821dc
fix clang format
niyatim23 Nov 14, 2023
03c919b
fix macos-gcc-ci
niyatim23 Nov 14, 2023
ea23df4
put cleanup in ifdef
niyatim23 Nov 15, 2023
fe1782f
add cmake flag
niyatim23 Nov 16, 2023
da23a05
additional builds with cmake flags for mac and ubuntu, max string siz…
niyatim23 Nov 17, 2023
dde5e17
fix clang-format
niyatim23 Nov 17, 2023
7be92b5
mac-os-dc-metrics-build-gcc
niyatim23 Nov 17, 2023
317c08c
increase signaling metrics message size
niyatim23 Nov 20, 2023
81ec0ba
address comments: bool, add back calltimes, remove hns
niyatim23 Dec 7, 2023
d743fe7
missed an hns
niyatim23 Dec 7, 2023
7558ed4
dlogp
niyatim23 Dec 7, 2023
a56346e
fix the build
niyatim23 Dec 7, 2023
1bf7ffa
remove extra space from ci
niyatim23 Dec 7, 2023
d9bdcf5
cleanup-1
niyatim23 Dec 8, 2023
064027c
clang-format
niyatim23 Dec 8, 2023
870deb3
fix git_tag from master to fix the ci
niyatim23 Dec 8, 2023
435d74b
Revert "fix git_tag from master to fix the ci"
niyatim23 Dec 8, 2023
d34456e
move message defs to common
niyatim23 Dec 8, 2023
cc88f47
remove unused var
niyatim23 Dec 8, 2023
271a214
remove unused var
niyatim23 Dec 8, 2023
802cfa8
cleanup-3
niyatim23 Dec 8, 2023
1e3863e
cleanup-4
niyatim23 Dec 8, 2023
041c858
fix macos build
niyatim23 Dec 8, 2023
5b99d19
Revert "fix macos build"
niyatim23 Dec 8, 2023
6ffbb9b
use double quotes instead of single for char array
niyatim23 Dec 8, 2023
e560ad5
forgot to rename in the string
niyatim23 Dec 8, 2023
e430f29
fix initialization + readme
niyatim23 Dec 8, 2023
5bfadc6
conclude merge
niyatim23 Dec 18, 2023
5c97ac5
remove offerTime and use offerReceivedTime instead
niyatim23 Dec 18, 2023
2ce7a16
move globals to streamingsession
niyatim23 Dec 18, 2023
0f1a567
rename + memset
niyatim23 Dec 18, 2023
f2daa19
fix clang format
niyatim23 Dec 18, 2023
eaa74a2
add null check for streaming session
niyatim23 Dec 19, 2023
713716d
Merge branch 'develop' into datachannel-benchmarking
niyatim23 Dec 19, 2023
5fa8636
null check for streaming session and psampleconfigurtion
niyatim23 Dec 19, 2023
0bcba85
add error messages
niyatim23 Dec 19, 2023
af07dbf
clang
niyatim23 Dec 19, 2023
ebcdc41
error message
niyatim23 Dec 20, 2023
9f8d6ed
ubuntu sample check update
niyatim23 Dec 20, 2023
15d7fab
fix sample check
niyatim23 Dec 20, 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
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -416,27 +416,29 @@ jobs:
timeout --signal=SIGABRT 60m ./tst/webrtc_client_test
sample-check:
if: github.repository == 'awslabs/amazon-kinesis-video-streams-webrtc-sdk-c'
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
env:
AWS_KVS_LOG_LEVEL: 2
permissions:
id-token: write
contents: read
steps:
- name: Clone repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}
role-duration-seconds: 10800
- name: Build repository
run: |
sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'
mkdir build && cd build
cmake ..
make
cd ..
- name: Sample check
run: |
./scripts/check-sample.sh
ubuntu-os-build:
runs-on: ubuntu-20.04
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ The SDK also tracks entry and exit of functions which increases the verbosity of
`add_definitions(-DLOG_STREAMING)`
Note: This log level is extremely VERBOSE and could flood the files if using file based logging strategy.

<details>
<summary>Time-to-first-frame breakdown metrics</summary>
There is a flag in the sample application which (pSampleConfiguration->enableSendingMetricsToViewerViaDc) can be set to TRUE to send metrics from the master to the [JS](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-js/examples/index.html) viewer. This helps get a detailed breakdown of time-to-first-frame and all the processes and API calls on master and the viewer both. This is intended to be used with the KVS WebRTC C SDK running as the master and the JS SDK as the viewer. The master sends peer, ice-agent, signaling and data-channel metrics to the viewer which are plotted ~ 20 seconds after the viewer is started. Since the timeline plot is intended to understand the time-to-first-frame, the sample web page needs to be refreshed and the master needs to be restarted if a new / updated plot is needed. While using the SDK in this mode, it is expected that all datachannel messages are JSON messages. This feature is only meant to be used for a single viewer at a time.
</details>

### Set path to SSL CA certificate (**Optional**)

If you have a custom CA certificate path to set, you can set it using:
Expand Down
172 changes: 165 additions & 7 deletions samples/Common.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,172 @@ STATUS signalingCallFailed(STATUS status)

VOID onDataChannelMessage(UINT64 customData, PRtcDataChannel pDataChannel, BOOL isBinary, PBYTE pMessage, UINT32 pMessageLen)
{
UNUSED_PARAM(customData);
if (isBinary) {
DLOGI("DataChannel Binary Message");
STATUS retStatus = STATUS_SUCCESS;
UINT32 i, strLen, tokenCount;
CHAR pMessageSend[MAX_DATA_CHANNEL_METRICS_MESSAGE_SIZE], errorMessage[200];
PCHAR json;
PSampleStreamingSession pSampleStreamingSession = (PSampleStreamingSession) customData;
PSampleConfiguration pSampleConfiguration;
DataChannelMessage dataChannelMessage;
niyatim23 marked this conversation as resolved.
Show resolved Hide resolved
jsmn_parser parser;
jsmntok_t tokens[MAX_JSON_TOKEN_COUNT];

CHK(pMessage != NULL && pDataChannel != NULL, STATUS_NULL_ARG);
niyatim23 marked this conversation as resolved.
Show resolved Hide resolved

if (pSampleStreamingSession == NULL) {
STRCPY(errorMessage, "Could not generate stats since the streaming session is NULL");
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage));
DLOGE("%s", errorMessage);
goto CleanUp;
}

pSampleConfiguration = pSampleStreamingSession->pSampleConfiguration;
if (pSampleConfiguration == NULL) {
STRCPY(errorMessage, "Could not generate stats since the sample configuration is NULL");
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage));
DLOGE("%s", errorMessage);
goto CleanUp;
}

if (pSampleConfiguration->enableSendingMetricsToViewerViaDc) {
jsmn_init(&parser);
json = (PCHAR) pMessage;
tokenCount = jsmn_parse(&parser, json, STRLEN(json), tokens, SIZEOF(tokens) / SIZEOF(jsmntok_t));

MEMSET(dataChannelMessage.content, '\0', SIZEOF(dataChannelMessage.content));
MEMSET(dataChannelMessage.firstMessageFromViewerTs, '\0', SIZEOF(dataChannelMessage.firstMessageFromViewerTs));
MEMSET(dataChannelMessage.firstMessageFromMasterTs, '\0', SIZEOF(dataChannelMessage.firstMessageFromMasterTs));
MEMSET(dataChannelMessage.secondMessageFromViewerTs, '\0', SIZEOF(dataChannelMessage.secondMessageFromViewerTs));
MEMSET(dataChannelMessage.secondMessageFromMasterTs, '\0', SIZEOF(dataChannelMessage.secondMessageFromMasterTs));
MEMSET(dataChannelMessage.lastMessageFromViewerTs, '\0', SIZEOF(dataChannelMessage.lastMessageFromViewerTs));

if (tokenCount > 1) {
if (tokens[0].type != JSMN_OBJECT) {
STRCPY(errorMessage, "Invalid JSON received, please send a valid json as the SDK is operating in datachannel-benchmarking mode");
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage));
DLOGE("%s", errorMessage);
retStatus = STATUS_INVALID_API_CALL_RETURN_JSON;
goto CleanUp;
}
DLOGI("DataChannel json message: %.*s\n", pMessageLen, pMessage);

for (i = 1; i < tokenCount; i++) {
if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "content")) {
strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start);
if (strLen != 0) {
STRNCPY(dataChannelMessage.content, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start);
}
} else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "firstMessageFromViewerTs")) {
strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start);
// parse and retain this message from the viewer to send it back again
if (strLen != 0) {
// since the length is not zero, we have already attached this timestamp to structure in the last iteration
STRNCPY(dataChannelMessage.firstMessageFromViewerTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start);
}
} else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "firstMessageFromMasterTs")) {
strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start);
if (strLen != 0) {
// since the length is not zero, we have already attached this timestamp to structure in the last iteration
STRNCPY(dataChannelMessage.firstMessageFromMasterTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start);
} else {
// if this timestamp was not assigned during the previous message session, add it now
SNPRINTF(dataChannelMessage.firstMessageFromMasterTs, 20, "%llu", GETTIME() / 10000);
break;
}
} else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "secondMessageFromViewerTs")) {
strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start);
// parse and retain this message from the viewer to send it back again
if (strLen != 0) {
STRNCPY(dataChannelMessage.secondMessageFromViewerTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start);
}
} else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "secondMessageFromMasterTs")) {
strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start);
if (strLen != 0) {
// since the length is not zero, we have already attached this timestamp to structure in the last iteration
STRNCPY(dataChannelMessage.secondMessageFromMasterTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start);
} else {
// if this timestamp was not assigned during the previous message session, add it now
SNPRINTF(dataChannelMessage.secondMessageFromMasterTs, 20, "%llu", GETTIME() / 10000);
break;
}
} else if (compareJsonString(json, &tokens[i], JSMN_STRING, (PCHAR) "lastMessageFromViewerTs")) {
strLen = (UINT32) (tokens[i + 1].end - tokens[i + 1].start);
if (strLen != 0) {
STRNCPY(dataChannelMessage.lastMessageFromViewerTs, json + tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start);
}
}
}

if (STRLEN(dataChannelMessage.lastMessageFromViewerTs) == 0) {
// continue sending the data_channel_metrics_message with new timestamps until we receive the lastMessageFromViewerTs from the viewer
SNPRINTF(pMessageSend, MAX_DATA_CHANNEL_METRICS_MESSAGE_SIZE, DATA_CHANNEL_MESSAGE_TEMPLATE, MASTER_DATA_CHANNEL_MESSAGE,
dataChannelMessage.firstMessageFromViewerTs, dataChannelMessage.firstMessageFromMasterTs,
dataChannelMessage.secondMessageFromViewerTs, dataChannelMessage.secondMessageFromMasterTs,
dataChannelMessage.lastMessageFromViewerTs);
DLOGI("Master's response: %s", pMessageSend);

retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pMessageSend, STRLEN(pMessageSend));
} else {
// now that we've received the last message, send across the signaling, peerConnection, ice metrics
SNPRINTF(pSampleStreamingSession->pSignalingClientMetricsMessage, MAX_SIGNALING_CLIENT_METRICS_MESSAGE_SIZE,
SIGNALING_CLIENT_METRICS_JSON_TEMPLATE, pSampleConfiguration->signalingClientMetrics.signalingStartTime,
pSampleConfiguration->signalingClientMetrics.signalingEndTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.offerReceivedTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.answerTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.describeChannelStartTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.describeChannelEndTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.getSignalingChannelEndpointStartTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.getSignalingChannelEndpointEndTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.getIceServerConfigStartTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.getIceServerConfigEndTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.getTokenStartTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.getTokenEndTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.createChannelStartTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.createChannelEndTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.connectStartTime,
pSampleConfiguration->signalingClientMetrics.signalingClientStats.connectEndTime);
DLOGI("Sending signaling metrics to the viewer: %s", pSampleStreamingSession->pSignalingClientMetricsMessage);

CHK_STATUS(peerConnectionGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->peerConnectionMetrics));
SNPRINTF(pSampleStreamingSession->pPeerConnectionMetricsMessage, MAX_PEER_CONNECTION_METRICS_MESSAGE_SIZE,
PEER_CONNECTION_METRICS_JSON_TEMPLATE,
pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionStartTime,
pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionConnectedTime);
DLOGI("Sending peer-connection metrics to the viewer: %s", pSampleStreamingSession->pPeerConnectionMetricsMessage);

CHK_STATUS(iceAgentGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->iceMetrics));
SNPRINTF(pSampleStreamingSession->pIceAgentMetricsMessage, MAX_ICE_AGENT_METRICS_MESSAGE_SIZE, ICE_AGENT_METRICS_JSON_TEMPLATE,
pSampleStreamingSession->iceMetrics.kvsIceAgentStats.candidateGatheringStartTime,
pSampleStreamingSession->iceMetrics.kvsIceAgentStats.candidateGatheringEndTime);
DLOGI("Sending ice-agent metrics to the viewer: %s", pSampleStreamingSession->pIceAgentMetricsMessage);

retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pSampleStreamingSession->pSignalingClientMetricsMessage,
STRLEN(pSampleStreamingSession->pSignalingClientMetricsMessage));
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pSampleStreamingSession->pPeerConnectionMetricsMessage,
STRLEN(pSampleStreamingSession->pPeerConnectionMetricsMessage));
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) pSampleStreamingSession->pIceAgentMetricsMessage,
STRLEN(pSampleStreamingSession->pIceAgentMetricsMessage));
}
} else {
DLOGI("DataChannel string message: %.*s\n", pMessageLen, pMessage);
STRCPY(errorMessage, "Send a json message for benchmarking as the C SDK is operating in benchmarking mode");
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) errorMessage, STRLEN(errorMessage));
}
} else {
DLOGI("DataChannel String Message: %.*s\n", pMessageLen, pMessage);
if (isBinary) {
DLOGI("DataChannel Binary Message");
} else {
DLOGI("DataChannel String Message: %.*s\n", pMessageLen, pMessage);
}
// Send a response to the message sent by the viewer
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) MASTER_DATA_CHANNEL_MESSAGE, STRLEN(MASTER_DATA_CHANNEL_MESSAGE));
}
// Send a response to the message sent by the viewer
STATUS retStatus = STATUS_SUCCESS;
retStatus = dataChannelSend(pDataChannel, FALSE, (PBYTE) MASTER_DATA_CHANNEL_MESSAGE, STRLEN(MASTER_DATA_CHANNEL_MESSAGE));
if (retStatus != STATUS_SUCCESS) {
DLOGI("[KVS Master] dataChannelSend(): operation returned status code: 0x%08x \n", retStatus);
}

CleanUp:
CHK_LOG_ERR(retStatus);
}

VOID onDataChannel(UINT64 customData, PRtcDataChannel pRtcDataChannel)
Expand All @@ -68,6 +222,8 @@ VOID onConnectionStateChange(UINT64 customData, RTC_PEER_CONNECTION_STATE newSta
ATOMIC_STORE_BOOL(&pSampleConfiguration->connected, TRUE);
CVAR_BROADCAST(pSampleConfiguration->cvar);

pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionConnectedTime =
GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND;
CHK_STATUS(peerConnectionGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->peerConnectionMetrics));
CHK_STATUS(iceAgentGetMetrics(pSampleStreamingSession->pPeerConnection, &pSampleStreamingSession->iceMetrics));

Expand Down Expand Up @@ -552,6 +708,7 @@ STATUS createSampleStreamingSession(PSampleConfiguration pSampleConfiguration, P
ATOMIC_STORE_BOOL(&pSampleStreamingSession->terminateFlag, FALSE);
ATOMIC_STORE_BOOL(&pSampleStreamingSession->candidateGatheringDone, FALSE);

pSampleStreamingSession->peerConnectionMetrics.peerConnectionStats.peerConnectionStartTime = GETTIME() / HUNDREDS_OF_NANOS_IN_A_MILLISECOND;
CHK_STATUS(initializePeerConnection(pSampleConfiguration, &pSampleStreamingSession->pPeerConnection));
CHK_STATUS(peerConnectionOnIceCandidate(pSampleStreamingSession->pPeerConnection, (UINT64) pSampleStreamingSession, onIceCandidateHandler));
CHK_STATUS(
Expand Down Expand Up @@ -864,6 +1021,7 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE
* not ahead of time. */
pSampleConfiguration->trickleIce = trickleIce;
pSampleConfiguration->useTurn = useTurn;
pSampleConfiguration->enableSendingMetricsToViewerViaDc = FALSE;

pSampleConfiguration->channelInfo.version = CHANNEL_INFO_CURRENT_VERSION;
pSampleConfiguration->channelInfo.pChannelName = channelName;
Expand Down
Loading
Loading