github.com/waldiirawan/apm-agent-go/v2@v2.2.2/breakdown_test.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package apm_test
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"os"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"github.com/waldiirawan/apm-agent-go/v2"
    31  	"github.com/waldiirawan/apm-agent-go/v2/apmtest"
    32  	"github.com/waldiirawan/apm-agent-go/v2/model"
    33  	"github.com/waldiirawan/apm-agent-go/v2/transport/transporttest"
    34  )
    35  
    36  func TestBreakdownMetrics_NonSampled(t *testing.T) {
    37  	tracer, transport := transporttest.NewRecorderTracer()
    38  	defer tracer.Close()
    39  
    40  	// Non-sampled transactions and their dropped spans still attribute to breakdown metrics.
    41  	tracer.SetSampler(apm.NewRatioSampler(0))
    42  
    43  	t0 := time.Now()
    44  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
    45  	span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
    46  	span.Duration = 10 * time.Millisecond // t0 + 20ms
    47  	span.End()
    48  	tx.Duration = 30 * time.Millisecond
    49  	tx.End()
    50  
    51  	tracer.Flush(nil)
    52  	tracer.SendMetrics(nil)
    53  
    54  	assertBreakdownMetrics(t, []model.Metrics{
    55  		spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond),
    56  		spanSelfTimeMetrics("test", "request", "db", "mysql", 1, 10*time.Millisecond),
    57  	}, payloadsBreakdownMetrics(transport))
    58  }
    59  
    60  func TestBreakdownMetrics_SpanDropped(t *testing.T) {
    61  	tracer, transport := transporttest.NewRecorderTracer()
    62  	defer tracer.Close()
    63  
    64  	// Dropped spans still attribute to breakdown metrics.
    65  	tracer.SetMaxSpans(1)
    66  
    67  	t0 := time.Now()
    68  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
    69  	var spans []*apm.Span
    70  	for i := 0; i < 2; i++ {
    71  		span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
    72  		spans = append(spans, span)
    73  	}
    74  	for _, span := range spans {
    75  		span.Duration = 10 * time.Millisecond // t0 + 20ms
    76  		span.End()
    77  	}
    78  	tx.Duration = 30 * time.Millisecond
    79  	tx.End()
    80  
    81  	tracer.Flush(nil)
    82  	tracer.SendMetrics(nil)
    83  
    84  	assertBreakdownMetrics(t, []model.Metrics{
    85  		spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond),
    86  		spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond),
    87  	}, payloadsBreakdownMetrics(transport))
    88  }
    89  
    90  func TestBreakdownMetrics_MetricsLimit(t *testing.T) {
    91  	tracer, transport := transporttest.NewRecorderTracer()
    92  	defer tracer.Close()
    93  
    94  	var logger apmtest.RecordLogger
    95  	tracer.SetLogger(&logger)
    96  
    97  	for i := 0; i < 3000; i++ {
    98  		tx := tracer.StartTransaction(fmt.Sprintf("%d", i), "request")
    99  		tx.End()
   100  	}
   101  	tracer.Flush(nil)
   102  	tracer.SendMetrics(nil)
   103  
   104  	// Make sure there's just one warning logged.
   105  	var warnings []apmtest.LogRecord
   106  	for _, record := range logger.Records {
   107  		if record.Level == "warning" {
   108  			warnings = append(warnings, record)
   109  		}
   110  	}
   111  	require.Len(t, warnings, 1)
   112  	assert.Regexp(t, "The limit of 1000 breakdown (.|\n)*", warnings[0].Message)
   113  
   114  	// There should be 1000 breakdown metrics keys buckets retained
   115  	// in-memory.
   116  	metrics := payloadsBreakdownMetrics(transport)
   117  	assert.Len(t, metrics, 1000)
   118  }
   119  
   120  func TestBreakdownMetrics_TransactionDropped(t *testing.T) {
   121  	tracer, transport := transporttest.NewRecorderTracer()
   122  	defer tracer.Close()
   123  
   124  	// Send transactions until one gets dropped, so we can check that
   125  	// the dropped transactions are included in the breakdown.
   126  	var count int
   127  	for tracer.Stats().TransactionsDropped == 0 {
   128  		for i := 0; i < 1000; i++ {
   129  			tx := tracer.StartTransaction("test", "request")
   130  			tx.Duration = 10 * time.Millisecond
   131  			tx.End()
   132  			count++
   133  		}
   134  	}
   135  
   136  	tracer.Flush(nil)
   137  	tracer.SendMetrics(nil)
   138  
   139  	assertBreakdownMetrics(t, []model.Metrics{
   140  		spanSelfTimeMetrics("test", "request", "app", "", count, time.Duration(count)*10*time.Millisecond),
   141  	}, payloadsBreakdownMetrics(transport))
   142  }
   143  
   144  func TestBreakdownMetrics_Disabled(t *testing.T) {
   145  	os.Setenv("ELASTIC_APM_BREAKDOWN_METRICS", "false")
   146  	defer os.Unsetenv("ELASTIC_APM_BREAKDOWN_METRICS")
   147  
   148  	tracer, transport := transporttest.NewRecorderTracer()
   149  	defer tracer.Close()
   150  
   151  	t0 := time.Now()
   152  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   153  	span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   154  	span.Duration = 10 * time.Millisecond // t0 + 20ms
   155  	span.End()
   156  	tx.Duration = 30 * time.Millisecond
   157  	tx.End()
   158  
   159  	tracer.Flush(nil)
   160  	tracer.SendMetrics(nil)
   161  
   162  	assertBreakdownMetrics(t, []model.Metrics{}, payloadsBreakdownMetrics(transport))
   163  }
   164  
   165  func TestBreakdownMetrics_AcceptanceTest1(t *testing.T) {
   166  	//                                 total self type
   167  	//  ██████████████████████████████    30   30 transaction
   168  	//           10        20        30
   169  
   170  	tracer, transport := transporttest.NewRecorderTracer()
   171  	defer tracer.Close()
   172  
   173  	tx := tracer.StartTransaction("test", "request")
   174  	tx.Duration = 30 * time.Millisecond
   175  	tx.End()
   176  
   177  	tracer.Flush(nil)
   178  	tracer.SendMetrics(nil)
   179  
   180  	assertBreakdownMetrics(t, []model.Metrics{
   181  		spanSelfTimeMetrics("test", "request", "app", "", 1, 30*time.Millisecond),
   182  	}, payloadsBreakdownMetrics(transport))
   183  }
   184  
   185  func TestBreakdownMetrics_AcceptanceTest2(t *testing.T) {
   186  	//                                 total self type
   187  	//  ██████████░░░░░░░░░░██████████    30   20 transaction
   188  	//  └─────────██████████              10   10 db.mysql
   189  	//           10        20        30
   190  
   191  	tracer, transport := transporttest.NewRecorderTracer()
   192  	defer tracer.Close()
   193  
   194  	t0 := time.Now()
   195  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   196  	span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   197  	span.Duration = 10 * time.Millisecond // t0 + 20ms
   198  	span.End()
   199  	tx.Duration = 30 * time.Millisecond // t0 + 30ms
   200  	tx.End()
   201  
   202  	tracer.Flush(nil)
   203  	tracer.SendMetrics(nil)
   204  
   205  	assertBreakdownMetrics(t, []model.Metrics{
   206  		spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond),
   207  		spanSelfTimeMetrics("test", "request", "db", "mysql", 1, 10*time.Millisecond),
   208  	}, payloadsBreakdownMetrics(transport))
   209  }
   210  
   211  func TestBreakdownMetrics_AcceptanceTest3(t *testing.T) {
   212  	//                                 total self type
   213  	//  ██████████░░░░░░░░░░██████████    30   20 transaction
   214  	//  └─────────██████████              10   10 app
   215  	//           10        20        30
   216  
   217  	tracer, transport := transporttest.NewRecorderTracer()
   218  	defer tracer.Close()
   219  
   220  	t0 := time.Now()
   221  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   222  	span := tx.StartSpanOptions("whatever", "app", apm.SpanOptions{
   223  		Start: t0.Add(10 * time.Millisecond),
   224  	})
   225  	span.Duration = 10 * time.Millisecond // t0 + 20ms
   226  	span.End()
   227  	tx.Duration = 30 * time.Millisecond // t0 + 30ms
   228  	tx.End()
   229  
   230  	tracer.Flush(nil)
   231  	tracer.SendMetrics(nil)
   232  
   233  	assertBreakdownMetrics(t, []model.Metrics{
   234  		spanSelfTimeMetrics("test", "request", "app", "", 2, 30*time.Millisecond),
   235  	}, payloadsBreakdownMetrics(transport))
   236  }
   237  
   238  func TestBreakdownMetrics_AcceptanceTest4(t *testing.T) {
   239  	//                                 total self type
   240  	//  ██████████░░░░░░░░░░██████████    30   20 transaction
   241  	//  └─────────██████████              10   10 db.mysql
   242  	//  └─────────██████████              10   10 db.mysql
   243  	//           10        20        30
   244  
   245  	tracer, transport := transporttest.NewRecorderTracer()
   246  	defer tracer.Close()
   247  
   248  	t0 := time.Now()
   249  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   250  	span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   251  	span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   252  	span1.Duration = 10 * time.Millisecond // t0 + 20ms
   253  	span2.Duration = 10 * time.Millisecond // t0 + 20ms
   254  	span1.End()
   255  	span2.End()
   256  	tx.Duration = 30 * time.Millisecond // t0 + 30ms
   257  	tx.End()
   258  
   259  	tracer.Flush(nil)
   260  	tracer.SendMetrics(nil)
   261  
   262  	assertBreakdownMetrics(t, []model.Metrics{
   263  		spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond),
   264  		spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond),
   265  	}, payloadsBreakdownMetrics(transport))
   266  }
   267  
   268  func TestBreakdownMetrics_AcceptanceTest5(t *testing.T) {
   269  	//                                 total self type
   270  	//  ██████████░░░░░░░░░░░░░░░█████    30   15 transaction
   271  	//  └─────────██████████              10   10 db.mysql
   272  	//  └──────────────██████████         10   10 db.mysql
   273  	//           10        20        30
   274  
   275  	tracer, transport := transporttest.NewRecorderTracer()
   276  	defer tracer.Close()
   277  
   278  	t0 := time.Now()
   279  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   280  	span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   281  	span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(15 * time.Millisecond)})
   282  	span1.Duration = 10 * time.Millisecond // t0 + 20ms
   283  	span2.Duration = 10 * time.Millisecond // t0 + 25ms
   284  	span1.End()
   285  	span2.End()
   286  	tx.Duration = 30 * time.Millisecond // t0 + 30ms
   287  	tx.End()
   288  
   289  	tracer.Flush(nil)
   290  	tracer.SendMetrics(nil)
   291  
   292  	assertBreakdownMetrics(t, []model.Metrics{
   293  		spanSelfTimeMetrics("test", "request", "app", "", 1, 15*time.Millisecond),
   294  		spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond),
   295  	}, payloadsBreakdownMetrics(transport))
   296  }
   297  
   298  func TestBreakdownMetrics_AcceptanceTest6(t *testing.T) {
   299  	//                                 total self type
   300  	//  █████░░░░░░░░░░░░░░░░░░░░█████    30   15 transaction
   301  	//  └────██████████                   10   10 db.mysql
   302  	//  └──────────────██████████         10   10 db.mysql
   303  	//           10        20        30
   304  
   305  	tracer, transport := transporttest.NewRecorderTracer()
   306  	defer tracer.Close()
   307  
   308  	t0 := time.Now()
   309  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   310  	span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(5 * time.Millisecond)})
   311  	span1.Duration = 10 * time.Millisecond // t0 + 15ms
   312  	span1.End()
   313  	span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(15 * time.Millisecond)})
   314  	span2.Duration = 10 * time.Millisecond // t0 + 25ms
   315  	span2.End()
   316  	tx.Duration = 30 * time.Millisecond // t0 + 30ms
   317  	tx.End()
   318  
   319  	tracer.Flush(nil)
   320  	tracer.SendMetrics(nil)
   321  
   322  	assertBreakdownMetrics(t, []model.Metrics{
   323  		spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond),
   324  		spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 20*time.Millisecond),
   325  	}, payloadsBreakdownMetrics(transport))
   326  }
   327  
   328  func TestBreakdownMetrics_AcceptanceTest7(t *testing.T) {
   329  	//                                 total self type
   330  	//  ██████████░░░░░█████░░░░░█████    30   15 transaction
   331  	//  └─────────█████                    5    5 db.mysql
   332  	//  └───────────────────█████          5    5 db.mysql
   333  	//           10        20        30
   334  
   335  	tracer, transport := transporttest.NewRecorderTracer()
   336  	defer tracer.Close()
   337  
   338  	t0 := time.Now()
   339  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   340  	span1 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   341  	span1.Duration = 5 * time.Millisecond // t0 + 15ms
   342  	span1.End()
   343  	span2 := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(20 * time.Millisecond)})
   344  	span2.Duration = 5 * time.Millisecond // t0 + 25ms
   345  	span2.End()
   346  	tx.Duration = 30 * time.Millisecond // t0 + 30ms
   347  	tx.End()
   348  
   349  	tracer.Flush(nil)
   350  	tracer.SendMetrics(nil)
   351  
   352  	assertBreakdownMetrics(t, []model.Metrics{
   353  		spanSelfTimeMetrics("test", "request", "app", "", 1, 20*time.Millisecond),
   354  		spanSelfTimeMetrics("test", "request", "db", "mysql", 2, 10*time.Millisecond),
   355  	}, payloadsBreakdownMetrics(transport))
   356  }
   357  
   358  func TestBreakdownMetrics_AcceptanceTest8(t *testing.T) {
   359  	//                                 total self type
   360  	//  ██████████░░░░░░░░░░██████████    30   20 transaction
   361  	//  └─────────█████░░░░░              10    5 app
   362  	//            └────██████████         10   10 db.mysql
   363  	//           10        20        30
   364  
   365  	tracer, transport := transporttest.NewRecorderTracer()
   366  	defer tracer.Close()
   367  
   368  	t0 := time.Now()
   369  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   370  	ctx := apm.ContextWithTransaction(context.Background(), tx)
   371  
   372  	span1, ctx := apm.StartSpanOptions(ctx, "whatever", "app", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   373  	span2, ctx := apm.StartSpanOptions(ctx, "whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(15 * time.Millisecond)})
   374  	span1.Duration = 10 * time.Millisecond // t0 + 20ms
   375  	span2.Duration = 10 * time.Millisecond // t0 + 25ms
   376  	span1.End()
   377  	span2.End()
   378  	tx.Duration = 30 * time.Millisecond // t0 + 30ms
   379  	tx.End()
   380  
   381  	tracer.Flush(nil)
   382  	tracer.SendMetrics(nil)
   383  
   384  	assertBreakdownMetrics(t, []model.Metrics{
   385  		spanSelfTimeMetrics("test", "request", "app", "", 2, 25*time.Millisecond),
   386  		spanSelfTimeMetrics("test", "request", "db", "mysql", 1, 10*time.Millisecond),
   387  	}, payloadsBreakdownMetrics(transport))
   388  }
   389  
   390  func TestBreakdownMetrics_AcceptanceTest9(t *testing.T) {
   391  	//                                 total self type
   392  	//  ██████████░░░░░░░░░░              20   20 transaction
   393  	//  └─────────██████████░░░░░░░░░░    20   10 app
   394  	//            └─────────██████████    10   10 db.mysql
   395  	//           10        20        30
   396  
   397  	tracer, transport := transporttest.NewRecorderTracer()
   398  	defer tracer.Close()
   399  
   400  	t0 := time.Now()
   401  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   402  	ctx := apm.ContextWithTransaction(context.Background(), tx)
   403  
   404  	span1, ctx := apm.StartSpanOptions(ctx, "whatever", "app", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   405  	tx.Duration = 20 * time.Millisecond // t0 + 20ms
   406  	tx.End()
   407  	span2, ctx := apm.StartSpanOptions(ctx, "whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(20 * time.Millisecond)})
   408  	span1.Duration = 20 * time.Millisecond // t0 + 30ms
   409  	span2.Duration = 10 * time.Millisecond // t0 + 30ms
   410  	span1.End()
   411  	span2.End()
   412  
   413  	tracer.Flush(nil)
   414  	tracer.SendMetrics(nil)
   415  
   416  	assertBreakdownMetrics(t, []model.Metrics{
   417  		// The db.mysql span should not be included in breakdown,
   418  		// as it started after the transaction ended. The explicit
   419  		// "app" span should not be included in the self_time value,
   420  		// it should only have been used for subtracting from the
   421  		// transaction's duration.
   422  		spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond),
   423  	}, payloadsBreakdownMetrics(transport))
   424  }
   425  
   426  func TestBreakdownMetrics_AcceptanceTest10(t *testing.T) {
   427  	//                                 total self type
   428  	//  ██████████░░░░░░░░░░              20   10 transaction
   429  	//  └─────────████████████████████    20   20 db.mysql
   430  	//           10        20        30
   431  
   432  	tracer, transport := transporttest.NewRecorderTracer()
   433  	defer tracer.Close()
   434  
   435  	t0 := time.Now()
   436  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   437  	span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(10 * time.Millisecond)})
   438  	tx.Duration = 20 * time.Millisecond // t0 + 20ms
   439  	tx.End()
   440  	span.Duration = 20 * time.Millisecond // t0 + 30ms
   441  	span.End()
   442  
   443  	tracer.Flush(nil)
   444  	tracer.SendMetrics(nil)
   445  
   446  	assertBreakdownMetrics(t, []model.Metrics{
   447  		// The db.mysql span should not be included in breakdown,
   448  		// as it ended after the transaction ended.
   449  		spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond),
   450  	}, payloadsBreakdownMetrics(transport))
   451  }
   452  
   453  func TestBreakdownMetrics_AcceptanceTest11(t *testing.T) {
   454  	//                                 total self type
   455  	//  ██████████                        10   10 transaction
   456  	//  └───────────────────██████████    10   10 db.mysql
   457  	//           10        20        30
   458  
   459  	tracer, transport := transporttest.NewRecorderTracer()
   460  	defer tracer.Close()
   461  
   462  	t0 := time.Now()
   463  	tx := tracer.StartTransactionOptions("test", "request", apm.TransactionOptions{Start: t0})
   464  	tx.Duration = 10 * time.Millisecond // t0 + 10ms
   465  	tx.End()
   466  	span := tx.StartSpanOptions("whatever", "db.mysql", apm.SpanOptions{Start: t0.Add(20 * time.Millisecond)})
   467  	span.Duration = 10 * time.Millisecond // t0 + 30ms
   468  	span.End()
   469  
   470  	tracer.Flush(nil)
   471  	tracer.SendMetrics(nil)
   472  
   473  	assertBreakdownMetrics(t, []model.Metrics{
   474  		// The db.mysql span should not be included in breakdown,
   475  		// as it started and ended after the transaction ended.
   476  		spanSelfTimeMetrics("test", "request", "app", "", 1, 10*time.Millisecond),
   477  	}, payloadsBreakdownMetrics(transport))
   478  }
   479  
   480  func spanSelfTimeMetrics(txName, txType, spanType, spanSubtype string, count int, sum time.Duration) model.Metrics {
   481  	return model.Metrics{
   482  		Transaction: model.MetricsTransaction{
   483  			Type: txType,
   484  			Name: txName,
   485  		},
   486  		Span: model.MetricsSpan{
   487  			Type:    spanType,
   488  			Subtype: spanSubtype,
   489  		},
   490  		Samples: map[string]model.Metric{
   491  			"span.self_time.count":  {Value: float64(count)},
   492  			"span.self_time.sum.us": {Value: float64(sum) / 1e3},
   493  		},
   494  	}
   495  }
   496  
   497  func assertBreakdownMetrics(t *testing.T, expect []model.Metrics, metrics []model.Metrics) {
   498  	for i := range metrics {
   499  		metrics[i].Timestamp = model.Time{}
   500  	}
   501  	assert.ElementsMatch(t, expect, metrics)
   502  }
   503  
   504  func payloadsBreakdownMetrics(t *transporttest.RecorderTransport) []model.Metrics {
   505  	p := t.Payloads()
   506  	ms := make([]model.Metrics, 0, len(p.Metrics))
   507  	for _, m := range p.Metrics {
   508  		if m.Transaction.Type == "" {
   509  			continue
   510  		}
   511  		ms = append(ms, m)
   512  	}
   513  	return ms
   514  }