Skip to content

Commit 2d21bfe

Browse files
committed
try: ffi for metrics
1 parent a7612c4 commit 2d21bfe

File tree

8 files changed

+485
-6
lines changed

8 files changed

+485
-6
lines changed

livekit-ffi/generate_proto.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ protoc \
3131
$PROTOCOL/e2ee.proto \
3232
$PROTOCOL/stats.proto \
3333
$PROTOCOL/rpc.proto \
34-
$PROTOCOL/data_stream.proto
34+
$PROTOCOL/data_stream.proto \
35+
$PROTOCOL/metrics.proto

livekit-ffi/protocol/ffi.proto

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import "video_frame.proto";
2626
import "audio_frame.proto";
2727
import "rpc.proto";
2828
import "data_stream.proto";
29+
import "metrics.proto";
2930

3031
// **How is the livekit-ffi working:
3132
// We refer as the ffi server the Rust server that is running the LiveKit client implementation, and we
@@ -148,7 +149,9 @@ message FfiRequest {
148149
TextStreamWriterWriteRequest text_stream_write = 65;
149150
TextStreamWriterCloseRequest text_stream_close = 66;
150151

151-
// NEXT_ID: 67
152+
PublishMetricsRequest publish_metrics = 67;
153+
154+
// NEXT_ID: 68
152155
}
153156
}
154157

@@ -243,7 +246,9 @@ message FfiResponse {
243246
TextStreamWriterWriteResponse text_stream_write = 64;
244247
TextStreamWriterCloseResponse text_stream_close = 65;
245248

246-
// NEXT_ID: 66
249+
PublishMetricsResponse publish_metrics = 66;
250+
251+
// NEXT_ID: 67
247252
}
248253
}
249254

@@ -301,6 +306,18 @@ message FfiEvent {
301306
}
302307
}
303308

309+
message PublishMetricsRequest {
310+
required uint64 local_participant_handle = 1;
311+
repeated string str_data = 2;
312+
repeated TimeSeriesMetric time_series = 3;
313+
repeated EventMetric events = 4;
314+
optional uint64 async_id = 5;
315+
}
316+
317+
message PublishMetricsResponse {
318+
required uint64 async_id = 1;
319+
}
320+
304321
// Stop all rooms synchronously (Do we need async here?).
305322
// e.g: This is used for the Unity Editor after each assemblies reload.
306323
// TODO(theomonnom): Implement a debug mode where we can find all leaked handles?

livekit-ffi/protocol/metrics.proto

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
syntax = "proto3";
2+
3+
package livekit.proto;
4+
option go_package = "github.com/livekit/protocol/livekit";
5+
option csharp_namespace = "LiveKit.Proto";
6+
option ruby_package = "LiveKit::Proto";
7+
8+
import "google/protobuf/timestamp.proto";
9+
10+
11+
/*
12+
Protocol used to record metrics for a specific session.
13+
14+
Clients send their timestamp in their own monotonically increasing time (e.g `performance.now` on JS).
15+
These timestamps are then augmented by the SFU to its time base.
16+
17+
A metric can be linked to a specific track by setting `track_sid`.
18+
*/
19+
20+
21+
// index from [0: MAX_LABEL_PREDEFINED_MAX_VALUE) are for predefined labels (`MetricLabel`)
22+
enum MetricLabel {
23+
AGENTS_LLM_TTFT = 0; // time to first token from LLM
24+
AGENTS_STT_TTFT = 1; // time to final transcription
25+
AGENTS_TTS_TTFB = 2; // time to first byte
26+
27+
CLIENT_VIDEO_SUBSCRIBER_FREEZE_COUNT = 3; // Number of video freezes
28+
CLIENT_VIDEO_SUBSCRIBER_TOTAL_FREEZE_DURATION = 4; // total duration of freezes
29+
CLIENT_VIDEO_SUBSCRIBER_PAUSE_COUNT = 5; // number of video pauses
30+
CLIENT_VIDEO_SUBSCRIBER_TOTAL_PAUSES_DURATION = 6; // total duration of pauses
31+
CLIENT_AUDIO_SUBSCRIBER_CONCEALED_SAMPLES = 7; // number of concealed (synthesized) audio samples
32+
CLIENT_AUDIO_SUBSCRIBER_SILENT_CONCEALED_SAMPLES = 8; // number of silent concealed samples
33+
CLIENT_AUDIO_SUBSCRIBER_CONCEALMENT_EVENTS = 9; // number of concealment events
34+
CLIENT_AUDIO_SUBSCRIBER_INTERRUPTION_COUNT = 10; // number of interruptions
35+
CLIENT_AUDIO_SUBSCRIBER_TOTAL_INTERRUPTION_DURATION = 11; // total duration of interruptions
36+
CLIENT_SUBSCRIBER_JITTER_BUFFER_DELAY = 12; // total time spent in jitter buffer
37+
CLIENT_SUBSCRIBER_JITTER_BUFFER_EMITTED_COUNT = 13; // total time spent in jitter buffer
38+
CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_BANDWIDTH = 14; // total duration spent in bandwidth quality limitation
39+
CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_CPU = 15; // total duration spent in cpu quality limitation
40+
CLIENT_VIDEO_PUBLISHER_QUALITY_LIMITATION_DURATION_OTHER = 16; // total duration spent in other quality limitation
41+
42+
PUBLISHER_RTT = 17; // Publisher RTT (participant -> server)
43+
SERVER_MESH_RTT = 18; // RTT between publisher node and subscriber node (could involve intermedia node(s))
44+
SUBSCRIBER_RTT = 19; // Subscribe RTT (server -> participant)
45+
46+
METRIC_LABEL_PREDEFINED_MAX_VALUE = 4096;
47+
}
48+
49+
message MetricsBatch {
50+
int64 timestamp_ms = 1; // time at which this batch is sent based on a monotonic clock (millisecond resolution)
51+
google.protobuf.Timestamp normalized_timestamp = 2;
52+
// To avoid repeating string values, we store them in a separate list and reference them by index
53+
// This is useful for storing participant identities, track names, etc.
54+
// There is also a predefined list of labels that can be used to reference common metrics.
55+
// They have reserved indices from 0 to (METRIC_LABEL_PREDEFINED_MAX_VALUE - 1).
56+
// Indexes pointing at str_data should start from METRIC_LABEL_PREDEFINED_MAX_VALUE,
57+
// such that str_data[0] == index of METRIC_LABEL_PREDEFINED_MAX_VALUE.
58+
repeated string str_data = 3;
59+
repeated TimeSeriesMetric time_series = 4;
60+
repeated EventMetric events = 5;
61+
}
62+
63+
message TimeSeriesMetric {
64+
// Metric name e.g "speech_probablity". The string value is not directly stored in the message, but referenced by index
65+
// in the `str_data` field of `MetricsBatch`
66+
uint32 label = 1;
67+
uint32 participant_identity = 2; // index into `str_data`
68+
uint32 track_sid = 3; // index into `str_data`
69+
repeated MetricSample samples = 4;
70+
uint32 rid = 5; // index into 'str_data'
71+
}
72+
73+
message MetricSample {
74+
int64 timestamp_ms = 1; // time of metric based on a monotonic clock (in milliseconds)
75+
google.protobuf.Timestamp normalized_timestamp = 2;
76+
float value = 3;
77+
}
78+
79+
message EventMetric {
80+
uint32 label = 1;
81+
uint32 participant_identity = 2; // index into `str_data`
82+
uint32 track_sid = 3; // index into `str_data`
83+
int64 start_timestamp_ms = 4; // start time of event based on a monotonic clock (in milliseconds)
84+
optional int64 end_timestamp_ms = 5; // end time of event based on a monotonic clock (in milliseconds), if needed
85+
google.protobuf.Timestamp normalized_start_timestamp = 6;
86+
optional google.protobuf.Timestamp normalized_end_timestamp = 7;
87+
string metadata = 8;
88+
uint32 rid = 9; // index into 'str_data'
89+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2023 LiveKit, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use crate::proto;
16+
use livekit_protocol as protocol;
17+
18+
impl From<protocol::MetricLabel> for proto::MetricLabel {
19+
fn from(value: protocol::MetricLabel) -> Self {
20+
match value {
21+
protocol::MetricLabel::AgentsLlmTtft => Self::AgentsLlmTtft,
22+
protocol::MetricLabel::AgentsSttTtft => Self::AgentsSttTtft,
23+
protocol::MetricLabel::AgentsTtsTtfb => Self::AgentsTtsTtfb,
24+
protocol::MetricLabel::ClientVideoSubscriberFreezeCount => Self::ClientVideoSubscriberFreezeCount,
25+
protocol::MetricLabel::ClientVideoSubscriberTotalFreezeDuration => Self::ClientVideoSubscriberTotalFreezeDuration,
26+
protocol::MetricLabel::ClientVideoSubscriberPauseCount => Self::ClientVideoSubscriberPauseCount,
27+
protocol::MetricLabel::ClientVideoSubscriberTotalPausesDuration => Self::ClientVideoSubscriberTotalPausesDuration,
28+
protocol::MetricLabel::ClientAudioSubscriberConcealedSamples => Self::ClientAudioSubscriberConcealedSamples,
29+
protocol::MetricLabel::ClientAudioSubscriberSilentConcealedSamples => Self::ClientAudioSubscriberSilentConcealedSamples,
30+
protocol::MetricLabel::ClientAudioSubscriberConcealmentEvents => Self::ClientAudioSubscriberConcealmentEvents,
31+
protocol::MetricLabel::ClientAudioSubscriberInterruptionCount => Self::ClientAudioSubscriberInterruptionCount,
32+
protocol::MetricLabel::ClientAudioSubscriberTotalInterruptionDuration => Self::ClientAudioSubscriberTotalInterruptionDuration,
33+
protocol::MetricLabel::ClientSubscriberJitterBufferDelay => Self::ClientSubscriberJitterBufferDelay,
34+
protocol::MetricLabel::ClientSubscriberJitterBufferEmittedCount => Self::ClientSubscriberJitterBufferEmittedCount,
35+
protocol::MetricLabel::ClientVideoPublisherQualityLimitationDurationBandwidth => Self::ClientVideoPublisherQualityLimitationDurationBandwidth,
36+
protocol::MetricLabel::ClientVideoPublisherQualityLimitationDurationCpu => Self::ClientVideoPublisherQualityLimitationDurationCpu,
37+
protocol::MetricLabel::ClientVideoPublisherQualityLimitationDurationOther => Self::ClientVideoPublisherQualityLimitationDurationOther,
38+
protocol::MetricLabel::PublisherRtt => Self::PublisherRtt,
39+
protocol::MetricLabel::ServerMeshRtt => Self::ServerMeshRtt,
40+
protocol::MetricLabel::SubscriberRtt => Self::SubscriberRtt,
41+
protocol::MetricLabel::PredefinedMaxValue => Self::MetricLabelPredefinedMaxValue,
42+
}
43+
}
44+
}
45+
46+
impl From<protocol::MetricSample> for proto::MetricSample {
47+
fn from(value: protocol::MetricSample) -> Self {
48+
Self {
49+
timestamp_ms: value.timestamp_ms,
50+
normalized_timestamp: value.normalized_timestamp.map(Into::into),
51+
value: value.value,
52+
}
53+
}
54+
}
55+
56+
impl From<protocol::TimeSeriesMetric> for proto::TimeSeriesMetric {
57+
fn from(value: protocol::TimeSeriesMetric) -> Self {
58+
Self {
59+
label: value.label,
60+
participant_identity: value.participant_identity,
61+
track_sid: value.track_sid,
62+
samples: value.samples.into_iter().map(Into::into).collect(),
63+
rid: value.rid,
64+
}
65+
}
66+
}
67+
68+
impl From<protocol::EventMetric> for proto::EventMetric {
69+
fn from(value: protocol::EventMetric) -> Self {
70+
Self {
71+
label: value.label,
72+
participant_identity: value.participant_identity,
73+
track_sid: value.track_sid,
74+
start_timestamp_ms: value.start_timestamp_ms,
75+
end_timestamp_ms: value.end_timestamp_ms,
76+
normalized_start_timestamp: value.normalized_start_timestamp.map(Into::into),
77+
normalized_end_timestamp: value.normalized_end_timestamp.map(Into::into),
78+
metadata: value.metadata,
79+
rid: value.rid,
80+
}
81+
}
82+
}
83+
84+
impl From<protocol::MetricsBatch> for proto::MetricsBatch {
85+
fn from(value: protocol::MetricsBatch) -> Self {
86+
Self {
87+
timestamp_ms: value.timestamp_ms,
88+
normalized_timestamp: value.normalized_timestamp.map(Into::into),
89+
str_data: value.str_data,
90+
time_series: value.time_series.into_iter().map(Into::into).collect(),
91+
events: value.events.into_iter().map(Into::into).collect(),
92+
}
93+
}
94+
}
95+

livekit-ffi/src/conversion/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ pub mod room;
2020
pub mod stats;
2121
pub mod track;
2222
pub mod video_frame;
23+
pub mod metrics;

0 commit comments

Comments
 (0)