github.com/livekit/protocol@v1.39.3/utils/metrics_batch_builder.go (about) 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 package utils 16 17 import ( 18 "errors" 19 "time" 20 21 "github.com/livekit/protocol/livekit" 22 "google.golang.org/protobuf/types/known/timestamppb" 23 ) 24 25 const ( 26 MetricsBatchBuilderInvalidTimeSeriesMetricId = -1 27 ) 28 29 var ( 30 ErrInvalidMetricLabel = errors.New("invalid metric label") 31 ErrFilteredMetricLabel = errors.New("filtered metric label") 32 ErrInvalidTimeSeriesMetricIndex = errors.New("invalid time series metric index") 33 ) 34 35 type MetricsBatchBuilder struct { 36 *livekit.MetricsBatch 37 38 stringData map[string]uint32 39 restrictedLabels MetricRestrictedLabels 40 } 41 42 func NewMetricsBatchBuilder() *MetricsBatchBuilder { 43 return &MetricsBatchBuilder{ 44 MetricsBatch: &livekit.MetricsBatch{}, 45 stringData: make(map[string]uint32), 46 } 47 } 48 49 func (m *MetricsBatchBuilder) ToProto() *livekit.MetricsBatch { 50 return m.MetricsBatch 51 } 52 53 func (m *MetricsBatchBuilder) SetTime(at time.Time, normalizedAt time.Time) { 54 m.MetricsBatch.TimestampMs = at.UnixMilli() 55 m.MetricsBatch.NormalizedTimestamp = timestamppb.New(normalizedAt) 56 } 57 58 type MetricLabelRange struct { 59 StartInclusive livekit.MetricLabel 60 EndInclusive livekit.MetricLabel 61 } 62 63 type MetricRestrictedLabels struct { 64 LabelRanges []MetricLabelRange 65 ParticipantIdentity livekit.ParticipantIdentity 66 } 67 68 func (m *MetricsBatchBuilder) SetRestrictedLabels(mrl MetricRestrictedLabels) { 69 m.restrictedLabels = mrl 70 } 71 72 type MetricSample struct { 73 At time.Time 74 NormalizedAt time.Time 75 Value float32 76 } 77 78 type TimeSeriesMetric struct { 79 MetricLabel livekit.MetricLabel 80 CustomMetricLabel string 81 ParticipantIdentity livekit.ParticipantIdentity 82 TrackID livekit.TrackID 83 Samples []MetricSample 84 Rid string 85 } 86 87 func (m *MetricsBatchBuilder) AddTimeSeriesMetric(tsm TimeSeriesMetric) (int, error) { 88 ptsm := &livekit.TimeSeriesMetric{} 89 90 if tsm.CustomMetricLabel != "" { 91 ptsm.Label = m.getStrDataIndex(tsm.CustomMetricLabel) 92 } else { 93 if tsm.MetricLabel >= livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE { 94 return MetricsBatchBuilderInvalidTimeSeriesMetricId, ErrInvalidMetricLabel 95 } 96 97 if m.isLabelFiltered(tsm.MetricLabel, tsm.ParticipantIdentity) { 98 return MetricsBatchBuilderInvalidTimeSeriesMetricId, ErrFilteredMetricLabel 99 } 100 101 ptsm.Label = uint32(tsm.MetricLabel) 102 } 103 104 if tsm.ParticipantIdentity != "" { 105 ptsm.ParticipantIdentity = m.getStrDataIndex(string(tsm.ParticipantIdentity)) 106 } 107 108 if tsm.TrackID != "" { 109 ptsm.TrackSid = m.getStrDataIndex(string(tsm.TrackID)) 110 } 111 112 for _, sample := range tsm.Samples { 113 ptsm.Samples = append(ptsm.Samples, &livekit.MetricSample{ 114 TimestampMs: sample.At.UnixMilli(), 115 NormalizedTimestamp: timestamppb.New(sample.NormalizedAt), 116 Value: sample.Value, 117 }) 118 } 119 120 if tsm.Rid != "" { 121 ptsm.Rid = m.getStrDataIndex(tsm.Rid) 122 } 123 124 m.MetricsBatch.TimeSeries = append(m.MetricsBatch.TimeSeries, ptsm) 125 return len(m.MetricsBatch.TimeSeries) - 1, nil 126 } 127 128 func (m *MetricsBatchBuilder) AddMetricSamplesToTimeSeriesMetric(timeSeriesMetricIdx int, samples []MetricSample) error { 129 if timeSeriesMetricIdx < 0 || timeSeriesMetricIdx >= len(m.MetricsBatch.TimeSeries) { 130 return ErrInvalidTimeSeriesMetricIndex 131 } 132 133 ptsm := m.MetricsBatch.TimeSeries[timeSeriesMetricIdx] 134 for _, sample := range samples { 135 ptsm.Samples = append(ptsm.Samples, &livekit.MetricSample{ 136 TimestampMs: sample.At.UnixMilli(), 137 NormalizedTimestamp: timestamppb.New(sample.NormalizedAt), 138 Value: sample.Value, 139 }) 140 } 141 142 return nil 143 } 144 145 type EventMetric struct { 146 MetricLabel livekit.MetricLabel 147 CustomMetricLabel string 148 ParticipantIdentity livekit.ParticipantIdentity 149 TrackID livekit.TrackID 150 StartedAt time.Time 151 EndedAt time.Time 152 NormalizedStartedAt time.Time 153 NormalizedEndedAt time.Time 154 Metadata string 155 Rid string 156 } 157 158 func (m *MetricsBatchBuilder) AddEventMetric(em EventMetric) error { 159 pem := &livekit.EventMetric{} 160 161 if em.CustomMetricLabel != "" { 162 pem.Label = m.getStrDataIndex(em.CustomMetricLabel) 163 } else { 164 if em.MetricLabel >= livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE { 165 return ErrInvalidMetricLabel 166 } 167 168 if m.isLabelFiltered(em.MetricLabel, em.ParticipantIdentity) { 169 return ErrFilteredMetricLabel 170 } 171 172 pem.Label = uint32(em.MetricLabel) 173 } 174 175 if em.ParticipantIdentity != "" { 176 pem.ParticipantIdentity = m.getStrDataIndex(string(em.ParticipantIdentity)) 177 } 178 179 if em.TrackID != "" { 180 pem.TrackSid = m.getStrDataIndex(string(em.TrackID)) 181 } 182 183 pem.StartTimestampMs = em.StartedAt.UnixMilli() 184 if !em.EndedAt.IsZero() { 185 endTimestampMs := em.EndedAt.UnixMilli() 186 pem.EndTimestampMs = &endTimestampMs 187 } 188 189 pem.NormalizedStartTimestamp = timestamppb.New(em.NormalizedStartedAt) 190 if !em.NormalizedEndedAt.IsZero() { 191 pem.NormalizedEndTimestamp = timestamppb.New(em.NormalizedEndedAt) 192 } 193 194 pem.Metadata = em.Metadata 195 196 if em.Rid != "" { 197 pem.Rid = m.getStrDataIndex(em.Rid) 198 } 199 200 m.MetricsBatch.Events = append(m.MetricsBatch.Events, pem) 201 return nil 202 } 203 204 func (m *MetricsBatchBuilder) Merge(other *livekit.MetricsBatch) { 205 // Timestamp and NormalizedTimestamp are not merged 206 207 for _, optsm := range other.TimeSeries { 208 ptsm := &livekit.TimeSeriesMetric{ 209 Samples: optsm.Samples, 210 } 211 if optsm.Label < uint32(int(livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE)) { 212 participantIdentity, ok := getStrDataForIndex(other, optsm.ParticipantIdentity) 213 if ok && m.isLabelFiltered(livekit.MetricLabel(optsm.Label), livekit.ParticipantIdentity(participantIdentity)) { 214 continue 215 } 216 217 ptsm.Label = optsm.Label 218 } else { 219 if tidx, ok := m.translateStrDataIndex(other.StrData, optsm.Label); ok { 220 ptsm.Label = tidx 221 } 222 } 223 224 if tidx, ok := m.translateStrDataIndex(other.StrData, optsm.ParticipantIdentity); ok { 225 ptsm.ParticipantIdentity = tidx 226 } 227 228 if tidx, ok := m.translateStrDataIndex(other.StrData, optsm.TrackSid); ok { 229 ptsm.TrackSid = tidx 230 } 231 232 if tidx, ok := m.translateStrDataIndex(other.StrData, optsm.Rid); ok { 233 ptsm.Rid = tidx 234 } 235 236 m.MetricsBatch.TimeSeries = append(m.MetricsBatch.TimeSeries, ptsm) 237 } 238 239 for _, opem := range other.Events { 240 pem := &livekit.EventMetric{} 241 if opem.Label < uint32(int(livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE)) { 242 participantIdentity, ok := getStrDataForIndex(other, opem.ParticipantIdentity) 243 if ok && m.isLabelFiltered(livekit.MetricLabel(opem.Label), livekit.ParticipantIdentity(participantIdentity)) { 244 continue 245 } 246 247 pem.Label = opem.Label 248 } else { 249 if tidx, ok := m.translateStrDataIndex(other.StrData, opem.Label); ok { 250 pem.Label = tidx 251 } 252 } 253 254 if tidx, ok := m.translateStrDataIndex(other.StrData, opem.ParticipantIdentity); ok { 255 pem.ParticipantIdentity = tidx 256 } 257 258 if tidx, ok := m.translateStrDataIndex(other.StrData, opem.TrackSid); ok { 259 pem.TrackSid = tidx 260 } 261 262 pem.StartTimestampMs = opem.StartTimestampMs 263 pem.EndTimestampMs = opem.EndTimestampMs 264 pem.NormalizedStartTimestamp = opem.NormalizedStartTimestamp 265 pem.NormalizedEndTimestamp = opem.NormalizedEndTimestamp 266 267 pem.Metadata = opem.Metadata 268 269 if tidx, ok := m.translateStrDataIndex(other.StrData, opem.Rid); ok { 270 pem.Rid = tidx 271 } 272 273 m.MetricsBatch.Events = append(m.MetricsBatch.Events, pem) 274 } 275 } 276 277 func (m *MetricsBatchBuilder) IsEmpty() bool { 278 return len(m.MetricsBatch.TimeSeries) == 0 && len(m.MetricsBatch.Events) == 0 279 } 280 281 func (m *MetricsBatchBuilder) isLabelFiltered(label livekit.MetricLabel, participantIdentity livekit.ParticipantIdentity) bool { 282 if participantIdentity == m.restrictedLabels.ParticipantIdentity { 283 // all labels allowed for restricted participant 284 return false 285 } 286 287 for _, mlr := range m.restrictedLabels.LabelRanges { 288 if label >= mlr.StartInclusive && label <= mlr.EndInclusive { 289 return true 290 } 291 } 292 293 return false 294 } 295 296 func (m *MetricsBatchBuilder) getStrDataIndex(s string) uint32 { 297 idx, ok := m.stringData[s] 298 if !ok { 299 m.MetricsBatch.StrData = append(m.MetricsBatch.StrData, s) 300 idx = uint32(int(livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE) + len(m.MetricsBatch.StrData) - 1) 301 m.stringData[s] = idx 302 } 303 return idx 304 } 305 306 func (m *MetricsBatchBuilder) translateStrDataIndex(strData []string, index uint32) (uint32, bool) { 307 if index < uint32(livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE) { 308 return 0, false 309 } 310 311 baseIdx := index - uint32(livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE) 312 if len(strData) <= int(baseIdx) { 313 return 0, false 314 } 315 316 // add if necessary 317 return m.getStrDataIndex(strData[baseIdx]), true 318 } 319 320 // ----------------------------------------------------- 321 322 func getStrDataForIndex(mb *livekit.MetricsBatch, index uint32) (string, bool) { 323 if index < uint32(livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE) { 324 return "", false 325 } 326 327 baseIdx := index - uint32(livekit.MetricLabel_METRIC_LABEL_PREDEFINED_MAX_VALUE) 328 if len(mb.StrData) <= int(baseIdx) { 329 return "", false 330 } 331 332 return mb.StrData[baseIdx], true 333 }