github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/metadata/metadata.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package metadata
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"strings"
    27  
    28  	"github.com/m3db/m3/src/metrics/aggregation"
    29  	"github.com/m3db/m3/src/metrics/generated/proto/metricpb"
    30  	"github.com/m3db/m3/src/metrics/generated/proto/policypb"
    31  	"github.com/m3db/m3/src/metrics/metric"
    32  	"github.com/m3db/m3/src/metrics/pipeline/applied"
    33  	"github.com/m3db/m3/src/metrics/policy"
    34  	"github.com/m3db/m3/src/query/models"
    35  )
    36  
    37  var (
    38  	// DefaultPipelineMetadata is a default pipeline metadata.
    39  	DefaultPipelineMetadata PipelineMetadata
    40  
    41  	// DefaultPipelineMetadatas is a default list of pipeline metadatas.
    42  	DefaultPipelineMetadatas = PipelineMetadatas{DefaultPipelineMetadata}
    43  
    44  	// DefaultMetadata is a default metadata.
    45  	DefaultMetadata = Metadata{Pipelines: DefaultPipelineMetadatas}
    46  
    47  	// DefaultStagedMetadata is a default staged metadata.
    48  	DefaultStagedMetadata = StagedMetadata{Metadata: DefaultMetadata}
    49  
    50  	// DefaultStagedMetadatas represents default staged metadatas.
    51  	DefaultStagedMetadatas = StagedMetadatas{DefaultStagedMetadata}
    52  
    53  	// DropPipelineMetadata is the drop policy pipeline metadata.
    54  	DropPipelineMetadata = PipelineMetadata{DropPolicy: policy.DropMust}
    55  
    56  	// DropPipelineMetadatas is the drop policy list of pipeline metadatas.
    57  	DropPipelineMetadatas = []PipelineMetadata{DropPipelineMetadata}
    58  
    59  	// DropIfOnlyMatchPipelineMetadata is the drop if only match policy
    60  	// pipeline metadata.
    61  	DropIfOnlyMatchPipelineMetadata = PipelineMetadata{DropPolicy: policy.DropIfOnlyMatch}
    62  
    63  	// DropIfOnlyMatchPipelineMetadatas is the drop if only match policy list
    64  	// of pipeline metadatas.
    65  	DropIfOnlyMatchPipelineMetadatas = []PipelineMetadata{DropIfOnlyMatchPipelineMetadata}
    66  
    67  	// DropMetadata is the drop policy metadata.
    68  	DropMetadata = Metadata{Pipelines: DropPipelineMetadatas}
    69  
    70  	// DropStagedMetadata is the drop policy staged metadata.
    71  	DropStagedMetadata = StagedMetadata{Metadata: DropMetadata}
    72  
    73  	// DropStagedMetadatas is the drop policy staged metadatas.
    74  	DropStagedMetadatas = StagedMetadatas{DropStagedMetadata}
    75  )
    76  
    77  // PipelineMetadata contains pipeline metadata.
    78  type PipelineMetadata struct {
    79  	// List of storage policies.
    80  	StoragePolicies policy.StoragePolicies `json:"storagePolicies,omitempty"`
    81  	// Pipeline operations.
    82  	Pipeline applied.Pipeline `json:"-"`
    83  	// Tags.
    84  	Tags []models.Tag `json:"tags,omitempty"`
    85  	// GraphitePrefix is the list of graphite prefixes to apply.
    86  	GraphitePrefix [][]byte `json:"graphitePrefix,omitempty"`
    87  	// List of aggregation types.
    88  	AggregationID aggregation.ID `json:"aggregation,omitempty"`
    89  	// Drop policy.
    90  	DropPolicy policy.DropPolicy `json:"dropPolicy,omitempty"`
    91  	// ResendEnabled is true if the Pipeline supports resending aggregate values after the initial flush.
    92  	ResendEnabled bool `json:"resendEnabled,omitempty"`
    93  }
    94  
    95  func (m PipelineMetadata) String() string {
    96  	var b bytes.Buffer
    97  	b.WriteString(fmt.Sprintf("ResendEnabled: %v, StoragePolicies: %v, Pipeline: %v",
    98  		m.ResendEnabled, m.StoragePolicies, m.Pipeline))
    99  	return b.String()
   100  }
   101  
   102  // Equal returns true if two pipeline metadata are considered equal.
   103  func (m PipelineMetadata) Equal(other PipelineMetadata) bool {
   104  	return m.AggregationID.Equal(other.AggregationID) &&
   105  		m.StoragePolicies.Equal(other.StoragePolicies) &&
   106  		m.Pipeline.Equal(other.Pipeline) &&
   107  		m.DropPolicy == other.DropPolicy &&
   108  		m.ResendEnabled == other.ResendEnabled
   109  }
   110  
   111  // IsDefault returns whether this is the default standard pipeline metadata.
   112  func (m PipelineMetadata) IsDefault() bool {
   113  	return m.AggregationID == aggregation.DefaultID &&
   114  		len(m.StoragePolicies) == 0 &&
   115  		m.Pipeline.IsEmpty() &&
   116  		m.DropPolicy == policy.DefaultDropPolicy &&
   117  		!m.ResendEnabled
   118  }
   119  
   120  // IsMappingRule returns whether this is a mapping rule.
   121  // nolint:gocritic
   122  func (m PipelineMetadata) IsMappingRule() bool {
   123  	return m.Pipeline.IsMappingRule()
   124  }
   125  
   126  // IsAnyRollupRules returns whether any of the rules have rollups.
   127  func (m PipelineMetadata) IsAnyRollupRules() bool {
   128  	return !m.Pipeline.IsMappingRule()
   129  }
   130  
   131  // IsDropPolicyApplied returns whether this is the default standard pipeline
   132  // but with the drop policy applied.
   133  func (m PipelineMetadata) IsDropPolicyApplied() bool {
   134  	return m.AggregationID.IsDefault() &&
   135  		m.StoragePolicies.IsDefault() &&
   136  		m.Pipeline.IsEmpty() &&
   137  		m.IsDropPolicySet()
   138  }
   139  
   140  // IsDropPolicySet returns whether a drop policy is set.
   141  func (m PipelineMetadata) IsDropPolicySet() bool {
   142  	return !m.DropPolicy.IsDefault()
   143  }
   144  
   145  // Clone clones the pipeline metadata.
   146  func (m PipelineMetadata) Clone() PipelineMetadata {
   147  	return PipelineMetadata{
   148  		AggregationID:   m.AggregationID,
   149  		StoragePolicies: m.StoragePolicies.Clone(),
   150  		Pipeline:        m.Pipeline.Clone(),
   151  		ResendEnabled:   m.ResendEnabled,
   152  	}
   153  }
   154  
   155  // ToProto converts the pipeline metadata to a protobuf message in place.
   156  func (m PipelineMetadata) ToProto(pb *metricpb.PipelineMetadata) error {
   157  	m.AggregationID.ToProto(&pb.AggregationId)
   158  	if err := m.Pipeline.ToProto(&pb.Pipeline); err != nil {
   159  		return err
   160  	}
   161  	numStoragePolicies := len(m.StoragePolicies)
   162  	if cap(pb.StoragePolicies) >= numStoragePolicies {
   163  		pb.StoragePolicies = pb.StoragePolicies[:numStoragePolicies]
   164  	} else {
   165  		pb.StoragePolicies = make([]policypb.StoragePolicy, numStoragePolicies)
   166  	}
   167  	for i := 0; i < numStoragePolicies; i++ {
   168  		if err := m.StoragePolicies[i].ToProto(&pb.StoragePolicies[i]); err != nil {
   169  			return err
   170  		}
   171  	}
   172  	pb.DropPolicy = policypb.DropPolicy(m.DropPolicy)
   173  	pb.ResendEnabled = m.ResendEnabled
   174  	return nil
   175  }
   176  
   177  // FromProto converts the protobuf message to a pipeline metadata in place.
   178  func (m *PipelineMetadata) FromProto(pb metricpb.PipelineMetadata) error {
   179  	m.AggregationID.FromProto(pb.AggregationId)
   180  	if err := m.Pipeline.FromProto(pb.Pipeline); err != nil {
   181  		return err
   182  	}
   183  	numStoragePolicies := len(pb.StoragePolicies)
   184  	if cap(m.StoragePolicies) >= numStoragePolicies {
   185  		m.StoragePolicies = m.StoragePolicies[:numStoragePolicies]
   186  	} else {
   187  		m.StoragePolicies = make([]policy.StoragePolicy, numStoragePolicies)
   188  	}
   189  	for i := 0; i < numStoragePolicies; i++ {
   190  		if err := m.StoragePolicies[i].FromProto(pb.StoragePolicies[i]); err != nil {
   191  			return err
   192  		}
   193  	}
   194  	m.DropPolicy = policy.DropPolicy(pb.DropPolicy)
   195  	m.ResendEnabled = pb.ResendEnabled
   196  	return nil
   197  }
   198  
   199  // PipelineMetadatas is a list of pipeline metadatas.
   200  type PipelineMetadatas []PipelineMetadata
   201  
   202  // Equal returns true if two pipline metadatas are considered equal.
   203  func (metadatas PipelineMetadatas) Equal(other PipelineMetadatas) bool {
   204  	if len(metadatas) != len(other) {
   205  		return false
   206  	}
   207  	for i := 0; i < len(metadatas); i++ {
   208  		if !metadatas[i].Equal(other[i]) {
   209  			return false
   210  		}
   211  	}
   212  	return true
   213  }
   214  
   215  // Len returns the number of pipelines in the metadata.
   216  func (metadatas PipelineMetadatas) Len() int { return len(metadatas) }
   217  
   218  // Swap swaps the elements with indexes i and j.
   219  func (metadatas PipelineMetadatas) Swap(i, j int) {
   220  	metadatas[i], metadatas[j] = metadatas[j], metadatas[i]
   221  }
   222  
   223  // Less returns whether the element with
   224  // index i should sort before the element with index j.
   225  func (metadatas PipelineMetadatas) Less(i, j int) bool {
   226  	return strings.Compare(metadatas[i].String(), metadatas[j].String()) == -1
   227  }
   228  
   229  // Clone clones the list of pipeline metadatas.
   230  func (metadatas PipelineMetadatas) Clone() PipelineMetadatas {
   231  	cloned := make(PipelineMetadatas, 0, len(metadatas))
   232  	for i := 0; i < len(metadatas); i++ {
   233  		cloned = append(cloned, metadatas[i].Clone())
   234  	}
   235  	return cloned
   236  }
   237  
   238  // IsDropPolicySet returns whether any drop policies are set (but
   239  // does not discriminate if they have been applied or not).
   240  func (metadatas PipelineMetadatas) IsDropPolicySet() bool {
   241  	for i := range metadatas {
   242  		if metadatas[i].IsDropPolicySet() {
   243  			return true
   244  		}
   245  	}
   246  	return false
   247  }
   248  
   249  // ApplyOrRemoveDropPoliciesResult is the result of applying or removing
   250  // the drop policies for pipelines.
   251  type ApplyOrRemoveDropPoliciesResult uint
   252  
   253  const (
   254  	// NoDropPolicyPresentResult is the result of no drop policies being present.
   255  	NoDropPolicyPresentResult ApplyOrRemoveDropPoliciesResult = iota
   256  	// AppliedEffectiveDropPolicyResult is the result of applying the drop
   257  	// policy and returning just the single drop policy pipeline.
   258  	AppliedEffectiveDropPolicyResult
   259  	// RemovedIneffectiveDropPoliciesResult is the result of no drop policies
   260  	// being effective and returning the pipelines without any drop policies.
   261  	RemovedIneffectiveDropPoliciesResult
   262  )
   263  
   264  // ApplyOrRemoveDropPolicies applies or removes any drop policies, if
   265  // effective then just the drop pipeline is returned otherwise if not
   266  // effective it returns the drop policy pipelines that were not effective.
   267  func (metadatas PipelineMetadatas) ApplyOrRemoveDropPolicies() (
   268  	PipelineMetadatas,
   269  	ApplyOrRemoveDropPoliciesResult,
   270  ) {
   271  	// Check drop policies
   272  	dropIfOnlyMatchPipelines := 0
   273  	nonDropPipelines := 0
   274  	for i := range metadatas {
   275  		switch metadatas[i].DropPolicy {
   276  		case policy.DropMust:
   277  			// Immediately return, result is a drop
   278  			return DropPipelineMetadatas, AppliedEffectiveDropPolicyResult
   279  		case policy.DropIfOnlyMatch:
   280  			dropIfOnlyMatchPipelines++
   281  			continue
   282  		}
   283  		nonDropPipelines++
   284  	}
   285  
   286  	if dropIfOnlyMatchPipelines == 0 {
   287  		// No drop if only match pipelines, no need to remove anything
   288  		return metadatas, NoDropPolicyPresentResult
   289  	}
   290  
   291  	result := metadatas
   292  
   293  	// Drop is effective as no other non drop pipelines, result is a drop
   294  	if nonDropPipelines == 0 {
   295  		// nb: Do not directly return DropPipelineMetadatas, as the client could potentially save that reference
   296  		// and modify global state.
   297  		result = result[:0]
   298  		result = append(result, DropPipelineMetadata)
   299  		return result, AppliedEffectiveDropPolicyResult
   300  	}
   301  
   302  	// Remove all non-default drop policies as they must not be effective
   303  	for i := len(result) - 1; i >= 0; i-- {
   304  		if !result[i].DropPolicy.IsDefault() {
   305  			// Remove by moving to tail and decrementing length so we can do in
   306  			// place to avoid allocations of a new slice
   307  			if lastElem := i == len(result)-1; lastElem {
   308  				result = result[0:i]
   309  			} else {
   310  				result = append(result[0:i], result[i+1:]...)
   311  			}
   312  		}
   313  	}
   314  
   315  	return result, RemovedIneffectiveDropPoliciesResult
   316  }
   317  
   318  // ShouldDropTimestampOptions are options for the should drop timestamp method.
   319  type ShouldDropTimestampOptions struct {
   320  	UntimedRollups bool
   321  }
   322  
   323  // ShouldDropTimestamp applies custom M3 tags.
   324  func (metadatas PipelineMetadatas) ShouldDropTimestamp(opts ShouldDropTimestampOptions) bool {
   325  	// Go over metadatas and and look for drop timestamp tag.
   326  	for i := range metadatas {
   327  		if opts.UntimedRollups {
   328  			if metadatas[i].IsAnyRollupRules() {
   329  				return true
   330  			}
   331  		}
   332  		for j := range metadatas[i].Tags {
   333  			// If any metadata has the drop timestamp tag, then return that we
   334  			// should send untimed metrics to the aggregator.
   335  			if bytes.Equal(metadatas[i].Tags[j].Name, metric.M3MetricsDropTimestamp) {
   336  				return true
   337  			}
   338  		}
   339  	}
   340  	return false
   341  }
   342  
   343  // Metadata represents the metadata associated with a metric.
   344  type Metadata struct {
   345  	Pipelines PipelineMetadatas `json:"pipelines,omitempty"`
   346  }
   347  
   348  // IsDefault returns whether this is the default metadata.
   349  func (m Metadata) IsDefault() bool {
   350  	return len(m.Pipelines) == 1 && m.Pipelines[0].IsDefault()
   351  }
   352  
   353  // IsDropPolicyApplied returns whether this is the default metadata
   354  // but with the drop policy applied.
   355  func (m Metadata) IsDropPolicyApplied() bool {
   356  	return len(m.Pipelines) == 1 && m.Pipelines[0].IsDropPolicyApplied()
   357  }
   358  
   359  // IsDropPolicySet returns whether any drop policies are set (but
   360  // does not discriminate if they have been applied or not).
   361  func (m Metadata) IsDropPolicySet() bool {
   362  	for i := range m.Pipelines {
   363  		if m.Pipelines[i].IsDropPolicySet() {
   364  			return true
   365  		}
   366  	}
   367  	return false
   368  }
   369  
   370  // Equal returns true if two metadatas are considered equal.
   371  func (m Metadata) Equal(other Metadata) bool {
   372  	return m.Pipelines.Equal(other.Pipelines)
   373  }
   374  
   375  // ToProto converts the metadata to a protobuf message in place.
   376  func (m Metadata) ToProto(pb *metricpb.Metadata) error {
   377  	numPipelines := len(m.Pipelines)
   378  	if cap(pb.Pipelines) >= numPipelines {
   379  		pb.Pipelines = pb.Pipelines[:numPipelines]
   380  	} else {
   381  		pb.Pipelines = make([]metricpb.PipelineMetadata, numPipelines)
   382  	}
   383  	for i := 0; i < numPipelines; i++ {
   384  		if err := m.Pipelines[i].ToProto(&pb.Pipelines[i]); err != nil {
   385  			return err
   386  		}
   387  	}
   388  	return nil
   389  }
   390  
   391  // FromProto converts the protobuf message to a metadata in place.
   392  func (m *Metadata) FromProto(pb metricpb.Metadata) error {
   393  	numPipelines := len(pb.Pipelines)
   394  	if cap(m.Pipelines) >= numPipelines {
   395  		m.Pipelines = m.Pipelines[:numPipelines]
   396  	} else {
   397  		m.Pipelines = make(PipelineMetadatas, numPipelines)
   398  	}
   399  	for i := 0; i < numPipelines; i++ {
   400  		if err := m.Pipelines[i].FromProto(pb.Pipelines[i]); err != nil {
   401  			return err
   402  		}
   403  	}
   404  	return nil
   405  }
   406  
   407  // ForwardMetadata represents the metadata information associated with forwarded metrics.
   408  type ForwardMetadata struct {
   409  	// Pipeline of operations that may be applied to the metric.
   410  	Pipeline applied.Pipeline
   411  	// Storage policy.
   412  	StoragePolicy policy.StoragePolicy
   413  	// List of aggregation types.
   414  	AggregationID aggregation.ID
   415  	// Number of times this metric has been forwarded.
   416  	NumForwardedTimes int
   417  	// Metric source id that refers to the unique id of the source producing this metric.
   418  	SourceID uint32
   419  	// ResendEnabled is true if the Pipeline supports resending aggregate values after the initial flush.
   420  	ResendEnabled bool
   421  }
   422  
   423  // ToProto converts the forward metadata to a protobuf message in place.
   424  func (m ForwardMetadata) ToProto(pb *metricpb.ForwardMetadata) error {
   425  	m.AggregationID.ToProto(&pb.AggregationId)
   426  	if err := m.StoragePolicy.ToProto(&pb.StoragePolicy); err != nil {
   427  		return err
   428  	}
   429  	if err := m.Pipeline.ToProto(&pb.Pipeline); err != nil {
   430  		return err
   431  	}
   432  	pb.SourceId = m.SourceID
   433  	pb.NumForwardedTimes = int32(m.NumForwardedTimes)
   434  	pb.ResendEnabled = m.ResendEnabled
   435  	return nil
   436  }
   437  
   438  // FromProto converts the protobuf message to a forward metadata in place.
   439  func (m *ForwardMetadata) FromProto(pb metricpb.ForwardMetadata) error {
   440  	m.AggregationID.FromProto(pb.AggregationId)
   441  	if err := m.StoragePolicy.FromProto(pb.StoragePolicy); err != nil {
   442  		return err
   443  	}
   444  	if err := m.Pipeline.FromProto(pb.Pipeline); err != nil {
   445  		return err
   446  	}
   447  	m.SourceID = pb.SourceId
   448  	m.NumForwardedTimes = int(pb.NumForwardedTimes)
   449  	m.ResendEnabled = pb.ResendEnabled
   450  	return nil
   451  }
   452  
   453  // StagedMetadata represents metadata with a staged cutover time.
   454  type StagedMetadata struct {
   455  	Metadata `json:"metadata,omitempty"`
   456  
   457  	// Cutover is when the metadata is applicable.
   458  	CutoverNanos int64 `json:"cutoverNanos,omitempty"`
   459  
   460  	// Tombstoned determines whether the associated metric has been tombstoned.
   461  	Tombstoned bool `json:"tombstoned,omitempty"`
   462  }
   463  
   464  // Equal returns true if two staged metadatas are considered equal.
   465  func (sm StagedMetadata) Equal(other StagedMetadata) bool {
   466  	return sm.Metadata.Equal(other.Metadata) &&
   467  		sm.CutoverNanos == other.CutoverNanos &&
   468  		sm.Tombstoned == other.Tombstoned
   469  }
   470  
   471  // IsDefault returns whether this is a default staged metadata.
   472  func (sm StagedMetadata) IsDefault() bool {
   473  	return sm.CutoverNanos == 0 && !sm.Tombstoned && sm.Metadata.IsDefault()
   474  }
   475  
   476  // IsDropPolicyApplied returns whether this is the default staged metadata
   477  // but with the drop policy applied.
   478  func (sm StagedMetadata) IsDropPolicyApplied() bool {
   479  	return !sm.Tombstoned && sm.Metadata.IsDropPolicyApplied()
   480  }
   481  
   482  // IsDropPolicySet returns whether a drop policy is set.
   483  func (sm StagedMetadata) IsDropPolicySet() bool {
   484  	return sm.Pipelines.IsDropPolicySet()
   485  }
   486  
   487  // ToProto converts the staged metadata to a protobuf message in place.
   488  func (sm StagedMetadata) ToProto(pb *metricpb.StagedMetadata) error {
   489  	if err := sm.Metadata.ToProto(&pb.Metadata); err != nil {
   490  		return err
   491  	}
   492  	pb.CutoverNanos = sm.CutoverNanos
   493  	pb.Tombstoned = sm.Tombstoned
   494  	return nil
   495  }
   496  
   497  // FromProto converts the protobuf message to a staged metadata in place.
   498  func (sm *StagedMetadata) FromProto(pb metricpb.StagedMetadata) error {
   499  	if err := sm.Metadata.FromProto(pb.Metadata); err != nil {
   500  		return err
   501  	}
   502  	sm.CutoverNanos = pb.CutoverNanos
   503  	sm.Tombstoned = pb.Tombstoned
   504  	return nil
   505  }
   506  
   507  func (sm StagedMetadata) String() string {
   508  	var b bytes.Buffer
   509  	for _, p := range sm.Pipelines {
   510  		b.WriteString(p.String())
   511  	}
   512  	return b.String()
   513  }
   514  
   515  // StagedMetadatas contains a list of staged metadatas.
   516  type StagedMetadatas []StagedMetadata
   517  
   518  // Equal returns true if two staged metadatas slices are considered equal.
   519  func (sms StagedMetadatas) Equal(other StagedMetadatas) bool {
   520  	if len(sms) != len(other) {
   521  		return false
   522  	}
   523  	for i := range sms {
   524  		if !sms[i].Equal(other[i]) {
   525  			return false
   526  		}
   527  	}
   528  	return true
   529  }
   530  
   531  // IsDefault determines whether the list of staged metadata is a default list.
   532  func (sms StagedMetadatas) IsDefault() bool {
   533  	// very ugly but need to help out the go compiler here, as function calls have a high inlining cost
   534  	return len(sms) == 1 && len(sms[0].Pipelines) == 1 &&
   535  		sms[0].CutoverNanos == 0 && !sms[0].Tombstoned &&
   536  		sms[0].Pipelines[0].AggregationID == aggregation.DefaultID &&
   537  		len(sms[0].Pipelines[0].StoragePolicies) == 0 &&
   538  		sms[0].Pipelines[0].Pipeline.IsEmpty() &&
   539  		sms[0].Pipelines[0].DropPolicy == policy.DefaultDropPolicy
   540  }
   541  
   542  // IsDropPolicyApplied returns whether the list of staged metadata is the
   543  // default list but with the drop policy applied.
   544  func (sms StagedMetadatas) IsDropPolicyApplied() bool {
   545  	return len(sms) == 1 && sms[0].IsDropPolicyApplied()
   546  }
   547  
   548  // IsDropPolicySet returns if the active staged metadata has a drop policy set.
   549  func (sms StagedMetadatas) IsDropPolicySet() bool {
   550  	return len(sms) > 0 && sms[len(sms)-1].IsDropPolicySet()
   551  }
   552  
   553  // ToProto converts the staged metadatas to a protobuf message in place.
   554  func (sms StagedMetadatas) ToProto(pb *metricpb.StagedMetadatas) error {
   555  	numMetadatas := len(sms)
   556  	if cap(pb.Metadatas) >= numMetadatas {
   557  		pb.Metadatas = pb.Metadatas[:numMetadatas]
   558  	} else {
   559  		pb.Metadatas = make([]metricpb.StagedMetadata, numMetadatas)
   560  	}
   561  	for i := 0; i < numMetadatas; i++ {
   562  		if err := sms[i].ToProto(&pb.Metadatas[i]); err != nil {
   563  			return err
   564  		}
   565  	}
   566  	return nil
   567  }
   568  
   569  // FromProto converts the protobuf message to a staged metadatas in place.
   570  // This is an optimized method that merges some nested steps.
   571  func (sms *StagedMetadatas) FromProto(pb metricpb.StagedMetadatas) error {
   572  	numMetadatas := len(pb.Metadatas)
   573  	if cap(*sms) >= numMetadatas {
   574  		*sms = (*sms)[:numMetadatas]
   575  	} else {
   576  		*sms = make([]StagedMetadata, numMetadatas)
   577  	}
   578  
   579  	for i := 0; i < numMetadatas; i++ {
   580  		metadata := &(*sms)[i]
   581  		metadataPb := &pb.Metadatas[i]
   582  		numPipelines := len(metadataPb.Metadata.Pipelines)
   583  
   584  		metadata.CutoverNanos = metadataPb.CutoverNanos
   585  		metadata.Tombstoned = metadataPb.Tombstoned
   586  
   587  		if cap(metadata.Pipelines) >= numPipelines {
   588  			metadata.Pipelines = metadata.Pipelines[:numPipelines]
   589  		} else {
   590  			metadata.Pipelines = make(PipelineMetadatas, numPipelines)
   591  		}
   592  
   593  		for j := 0; j < numPipelines; j++ {
   594  			var (
   595  				pipelinePb         = &metadataPb.Metadata.Pipelines[j]
   596  				pipeline           = &metadata.Pipelines[j]
   597  				numStoragePolicies = len(pipelinePb.StoragePolicies)
   598  				numOps             = len(pipelinePb.Pipeline.Ops)
   599  				err                error
   600  			)
   601  
   602  			pipeline.AggregationID[0] = pipelinePb.AggregationId.Id
   603  			pipeline.DropPolicy = policy.DropPolicy(pipelinePb.DropPolicy)
   604  			pipeline.ResendEnabled = pipelinePb.ResendEnabled
   605  
   606  			if len(pipeline.Tags) > 0 {
   607  				pipeline.Tags = pipeline.Tags[:0]
   608  			}
   609  			if len(pipeline.GraphitePrefix) > 0 {
   610  				pipeline.GraphitePrefix = pipeline.GraphitePrefix[:0]
   611  			}
   612  
   613  			if cap(pipeline.StoragePolicies) >= numStoragePolicies {
   614  				pipeline.StoragePolicies = pipeline.StoragePolicies[:numStoragePolicies]
   615  			} else {
   616  				pipeline.StoragePolicies = make([]policy.StoragePolicy, numStoragePolicies)
   617  			}
   618  
   619  			if cap(pipeline.Pipeline.Operations) >= numOps {
   620  				pipeline.Pipeline.Operations = pipeline.Pipeline.Operations[:numOps]
   621  			} else {
   622  				pipeline.Pipeline.Operations = make([]applied.OpUnion, numOps)
   623  			}
   624  
   625  			if len(pipelinePb.Pipeline.Ops) > 0 {
   626  				err = applied.OperationsFromProto(pipelinePb.Pipeline.Ops, pipeline.Pipeline.Operations)
   627  				if err != nil {
   628  					return err
   629  				}
   630  			}
   631  			if len(pipelinePb.StoragePolicies) > 0 {
   632  				err = policy.StoragePoliciesFromProto(pipelinePb.StoragePolicies, pipeline.StoragePolicies)
   633  				if err != nil {
   634  					return err
   635  				}
   636  			}
   637  		}
   638  	}
   639  
   640  	return nil
   641  }
   642  
   643  // fromProto is a non-optimized in place protobuf conversion method, used as a reference for tests.
   644  func (sms *StagedMetadatas) fromProto(pb metricpb.StagedMetadatas) error {
   645  	numMetadatas := len(pb.Metadatas)
   646  	if cap(*sms) >= numMetadatas {
   647  		*sms = (*sms)[:numMetadatas]
   648  	} else {
   649  		*sms = make([]StagedMetadata, numMetadatas)
   650  	}
   651  	for i := 0; i < numMetadatas; i++ {
   652  		if err := (*sms)[i].FromProto(pb.Metadatas[i]); err != nil {
   653  			return err
   654  		}
   655  	}
   656  	return nil
   657  }
   658  
   659  // VersionedStagedMetadatas is a versioned staged metadatas.
   660  type VersionedStagedMetadatas struct {
   661  	StagedMetadatas StagedMetadatas `json:"stagedMetadatas"`
   662  	Version         int             `json:"version"`
   663  }
   664  
   665  // TimedMetadata represents the metadata information associated with timed metrics.
   666  type TimedMetadata struct {
   667  	// List of aggregation types.
   668  	AggregationID aggregation.ID
   669  
   670  	// Storage policy.
   671  	StoragePolicy policy.StoragePolicy
   672  }
   673  
   674  // ToProto converts the timed metadata to a protobuf message in place.
   675  func (m TimedMetadata) ToProto(pb *metricpb.TimedMetadata) error {
   676  	m.AggregationID.ToProto(&pb.AggregationId)
   677  	if err := m.StoragePolicy.ToProto(&pb.StoragePolicy); err != nil {
   678  		return err
   679  	}
   680  	return nil
   681  }
   682  
   683  // FromProto converts the protobuf message to a timed metadata in place.
   684  func (m *TimedMetadata) FromProto(pb metricpb.TimedMetadata) error {
   685  	m.AggregationID.FromProto(pb.AggregationId)
   686  	if err := m.StoragePolicy.FromProto(pb.StoragePolicy); err != nil {
   687  		return err
   688  	}
   689  	return nil
   690  }