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  }