github.com/waldiirawan/apm-agent-go/v2@v2.2.2/breakdown_internal_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
    19  
    20  import (
    21  	"go/ast"
    22  	"go/importer"
    23  	"go/parser"
    24  	"go/token"
    25  	"go/types"
    26  	"os"
    27  	"os/exec"
    28  	"runtime"
    29  	"strings"
    30  	"testing"
    31  
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  func TestBreakdownMetricsAlignment(t *testing.T) {
    37  	// This test is to ensure the alignment properties
    38  	// of the breakdownMetricsMapEntry are maintained
    39  	// for both 32-bit and 64-bit systems, since we use
    40  	// sync/atomic operations on them.
    41  	if runtime.GOOS != "darwin" {
    42  		// Go 1.15 dropped support for darwin/386
    43  		t.Run("32-bit", func(t *testing.T) { testBreakdownMetricsAlignment(t, "386") })
    44  	}
    45  	t.Run("64-bit", func(t *testing.T) { testBreakdownMetricsAlignment(t, "amd64") })
    46  }
    47  
    48  func testBreakdownMetricsAlignment(t *testing.T, arch string) {
    49  	cfg := types.Config{
    50  		IgnoreFuncBodies: true,
    51  		Importer:         importer.For("source", nil),
    52  		Sizes:            types.SizesFor("gc", arch),
    53  	}
    54  
    55  	cmd := exec.Command("go", "list", "-f", "{{.GoFiles}}")
    56  	cmd.Env = os.Environ()
    57  	cmd.Env = append(cmd.Env, "GOARCH="+arch)
    58  	output, err := cmd.Output()
    59  	require.NoError(t, err, string(output))
    60  	filenames := strings.Fields(string(output[1 : len(output)-2])) // strip "[" and "]"
    61  
    62  	fset := token.NewFileSet()
    63  	files := make([]*ast.File, len(filenames))
    64  	for i, filename := range filenames {
    65  		f, err := parser.ParseFile(fset, filename, nil, 0)
    66  		require.NoError(t, err)
    67  		files[i] = f
    68  	}
    69  
    70  	pkg, err := cfg.Check("github.com/waldiirawan/apm-agent-go/v2", fset, files, nil)
    71  	require.NoError(t, err)
    72  
    73  	// breakdownMetricsMapEntry's size must be multiple of 8,
    74  	// as it is used in a slice. This ensures that the embedded
    75  	// fields are always aligned.
    76  	breakdownMetricsMapEntryObj := pkg.Scope().Lookup("breakdownMetricsMapEntry")
    77  	require.NotNil(t, breakdownMetricsMapEntryObj)
    78  	assert.Equal(t, int64(0), cfg.Sizes.Sizeof(breakdownMetricsMapEntryObj.Type())%8)
    79  
    80  	// breakdownMetricsMapEntry.breakdownTiming must be the first field,
    81  	// to ensure it remains 64-bit aligned.
    82  	breakdownTimingObj, breakdownTimingFieldIndex, _ := types.LookupFieldOrMethod(
    83  		breakdownMetricsMapEntryObj.Type(), false, pkg, "breakdownTiming",
    84  	)
    85  	require.NotNil(t, breakdownTimingObj)
    86  	assert.Equal(t, []int{1}, breakdownTimingFieldIndex)
    87  
    88  	// breakdownTiming.transaction.duration and breakdownTiming.span.duration
    89  	// should be 64-bit aligned. We know that the breakdownTiming type is
    90  	// 64-bit aligned, so check that its transaction/span fields are also,
    91  	// and that spanTiming's duration field is its first field.
    92  
    93  	spanTimingObj := pkg.Scope().Lookup("spanTiming")
    94  	require.NotNil(t, spanTimingObj)
    95  	_, durationFieldIndex, _ := types.LookupFieldOrMethod(spanTimingObj.Type(), false, pkg, "duration")
    96  	assert.Equal(t, []int{0}, durationFieldIndex)
    97  
    98  	breakdownTimingStruct := breakdownTimingObj.Type().Underlying().(*types.Struct)
    99  	var spanTimingFieldIndices []int
   100  	fields := make([]*types.Var, breakdownTimingStruct.NumFields())
   101  	for i := range fields {
   102  		field := breakdownTimingStruct.Field(i)
   103  		fields[i] = field
   104  		if field.Type() == spanTimingObj.Type() {
   105  			spanTimingFieldIndices = append(spanTimingFieldIndices, i)
   106  		}
   107  	}
   108  	require.NotEmpty(t, spanTimingFieldIndices)
   109  	offsets := cfg.Sizes.Offsetsof(fields)
   110  	for _, fieldIndex := range spanTimingFieldIndices {
   111  		assert.Equal(t, int64(0), offsets[fieldIndex]%8)
   112  	}
   113  }