You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	Add comments and test for 64-bit field alignment (#418)
* Add comments on needed filed alignment Add comment about alignment requirements to all struct fields who's values are passed to 64-bit atomic operations. Update any struct's field ordering if one or more of those fields has alignment requirements to support 64-bit atomic operations. * Add 64-bit alignment tests Most `struct` that have field alignment requirements are now statically validated prior to testing. The only `struct`s not validated that have these requirements are ones defined in tests themselves where multiple `TestMain` functions would be needed to test them. Given the fields are already identified with comments specifying the alignment requirements and they are in the test themselves, this seems like an OK omission. Co-authored-by: Liz Fong-Jones <elizabeth@ctyalcove.org>
This commit is contained in:
		
				
					committed by
					
						 Liz Fong-Jones
						Liz Fong-Jones
					
				
			
			
				
	
			
			
			
						parent
						
							aefc49cfe6
						
					
				
				
					commit
					91091b44eb
				
			| @@ -28,10 +28,11 @@ import ( | ||||
|  | ||||
| type ( | ||||
| 	Aggregator struct { | ||||
| 		// ckptSum needs to be aligned for 64-bit atomic operations. | ||||
| 		ckptSum    core.Number | ||||
| 		lock       sync.Mutex | ||||
| 		current    points | ||||
| 		checkpoint points | ||||
| 		ckptSum    core.Number | ||||
| 	} | ||||
|  | ||||
| 	points []core.Number | ||||
|   | ||||
| @@ -18,16 +18,34 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/core" | ||||
| 	ottest "go.opentelemetry.io/otel/internal/testing" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregator" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/test" | ||||
| ) | ||||
|  | ||||
| // Ensure struct alignment prior to running tests. | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "Aggregator.ckptSum", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.ckptSum), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| type updateTest struct { | ||||
| 	count    int | ||||
| 	absolute bool | ||||
|   | ||||
| @@ -25,9 +25,11 @@ import ( | ||||
| // Aggregator aggregates counter events. | ||||
| type Aggregator struct { | ||||
| 	// current holds current increments to this counter record | ||||
| 	// current needs to be aligned for 64-bit atomic operations. | ||||
| 	current core.Number | ||||
|  | ||||
| 	// checkpoint is a temporary used during Checkpoint() | ||||
| 	// checkpoint needs to be aligned for 64-bit atomic operations. | ||||
| 	checkpoint core.Number | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -16,17 +16,39 @@ package counter | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/core" | ||||
| 	ottest "go.opentelemetry.io/otel/internal/testing" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/test" | ||||
| ) | ||||
|  | ||||
| const count = 100 | ||||
|  | ||||
| // Ensure struct alignment prior to running tests. | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "Aggregator.current", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.current), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "Aggregator.checkpoint", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.checkpoint), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| func TestCounterMonotonic(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
|   | ||||
| @@ -45,6 +45,8 @@ type ( | ||||
| 	// a sequence number to determine the winner of a race. | ||||
| 	gaugeData struct { | ||||
| 		// value is the int64- or float64-encoded Set() data | ||||
| 		// | ||||
| 		// value needs to be aligned for 64-bit atomic operations. | ||||
| 		value core.Number | ||||
|  | ||||
| 		// timestamp indicates when this record was submitted. | ||||
|   | ||||
| @@ -17,11 +17,14 @@ package gauge | ||||
| import ( | ||||
| 	"context" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/core" | ||||
| 	ottest "go.opentelemetry.io/otel/internal/testing" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregator" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/test" | ||||
| @@ -31,6 +34,21 @@ const count = 100 | ||||
|  | ||||
| var _ export.Aggregator = &Aggregator{} | ||||
|  | ||||
| // Ensure struct alignment prior to running tests. | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "gaugeData.value", | ||||
| 			Offset: unsafe.Offsetof(gaugeData{}.value), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| func TestGaugeNonMonotonic(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
|   | ||||
| @@ -26,12 +26,15 @@ type ( | ||||
| 	// Aggregator aggregates measure events, keeping only the max, | ||||
| 	// sum, and count. | ||||
| 	Aggregator struct { | ||||
| 		current    state | ||||
| 		// current has to be aligned for 64-bit atomic operations. | ||||
| 		current state | ||||
| 		// checkpoint has to be aligned for 64-bit atomic operations. | ||||
| 		checkpoint state | ||||
| 		kind       core.NumberKind | ||||
| 	} | ||||
|  | ||||
| 	state struct { | ||||
| 		// all fields have to be aligned for 64-bit atomic operations. | ||||
| 		count core.Number | ||||
| 		sum   core.Number | ||||
| 		min   core.Number | ||||
|   | ||||
| @@ -18,11 +18,14 @@ import ( | ||||
| 	"context" | ||||
| 	"math" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/core" | ||||
| 	ottest "go.opentelemetry.io/otel/internal/testing" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregator" | ||||
| 	"go.opentelemetry.io/otel/sdk/metric/aggregator/test" | ||||
| @@ -59,6 +62,41 @@ var ( | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // Ensure struct alignment prior to running tests. | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "Aggregator.current", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.current), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "Aggregator.checkpoint", | ||||
| 			Offset: unsafe.Offsetof(Aggregator{}.checkpoint), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "state.count", | ||||
| 			Offset: unsafe.Offsetof(state{}.count), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "state.sum", | ||||
| 			Offset: unsafe.Offsetof(state{}.sum), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "state.min", | ||||
| 			Offset: unsafe.Offsetof(state{}.min), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "state.max", | ||||
| 			Offset: unsafe.Offsetof(state{}.max), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| func TestMinMaxSumCountAbsolute(t *testing.T) { | ||||
| 	test.RunProfiles(t, func(t *testing.T, profile test.Profile) { | ||||
| 		minMaxSumCount(t, profile, positiveOnly) | ||||
|   | ||||
| @@ -17,10 +17,13 @@ package test | ||||
| import ( | ||||
| 	"context" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"sort" | ||||
| 	"testing" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/api/core" | ||||
| 	ottest "go.opentelemetry.io/otel/internal/testing" | ||||
| 	export "go.opentelemetry.io/otel/sdk/export/metric" | ||||
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregator" | ||||
| ) | ||||
| @@ -63,9 +66,25 @@ func RunProfiles(t *testing.T, f func(*testing.T, Profile)) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Ensure local struct alignment prior to running tests. | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "Numbers.numbers", | ||||
| 			Offset: unsafe.Offsetof(Numbers{}.numbers), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
|  | ||||
| type Numbers struct { | ||||
| 	kind    core.NumberKind | ||||
| 	// numbers has to be aligned for 64-bit atomic operations. | ||||
| 	numbers []core.Number | ||||
| 	kind    core.NumberKind | ||||
| } | ||||
|  | ||||
| func NewNumbers(kind core.NumberKind) Numbers { | ||||
|   | ||||
							
								
								
									
										36
									
								
								sdk/metric/alignment_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								sdk/metric/alignment_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| package metric | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	ottest "go.opentelemetry.io/otel/internal/testing" | ||||
| ) | ||||
|  | ||||
| // Ensure struct alignment prior to running tests. | ||||
| func TestMain(m *testing.M) { | ||||
| 	fields := []ottest.FieldOffset{ | ||||
| 		{ | ||||
| 			Name:   "record.refcount", | ||||
| 			Offset: unsafe.Offsetof(record{}.refcount), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "record.collectedEpoch", | ||||
| 			Offset: unsafe.Offsetof(record{}.collectedEpoch), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "record.modifiedEpoch", | ||||
| 			Offset: unsafe.Offsetof(record{}.modifiedEpoch), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:   "record.reclaim", | ||||
| 			Offset: unsafe.Offsetof(record{}.reclaim), | ||||
| 		}, | ||||
| 	} | ||||
| 	if !ottest.Aligned8Byte(fields, os.Stderr) { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(m.Run()) | ||||
| } | ||||
| @@ -31,11 +31,12 @@ import ( | ||||
| ) | ||||
|  | ||||
| type monotoneBatcher struct { | ||||
| 	t *testing.T | ||||
|  | ||||
| 	collections  int | ||||
| 	// currentValue needs to be aligned for 64-bit atomic operations. | ||||
| 	currentValue *core.Number | ||||
| 	collections  int | ||||
| 	currentTime  *time.Time | ||||
|  | ||||
| 	t *testing.T | ||||
| } | ||||
|  | ||||
| func (*monotoneBatcher) AggregatorFor(*export.Descriptor) export.Aggregator { | ||||
|   | ||||
| @@ -97,31 +97,39 @@ type ( | ||||
| 	// `record` in existence at a time, although at most one can | ||||
| 	// be referenced from the `SDK.current` map. | ||||
| 	record struct { | ||||
| 		// labels is the LabelSet passed by the user. | ||||
| 		labels *labels | ||||
|  | ||||
| 		// descriptor describes the metric instrument. | ||||
| 		descriptor *export.Descriptor | ||||
|  | ||||
| 		// refcount counts the number of active handles on | ||||
| 		// referring to this record.  active handles prevent | ||||
| 		// removing the record from the current map. | ||||
| 		// | ||||
| 		// refcount has to be aligned for 64-bit atomic operations. | ||||
| 		refcount int64 | ||||
|  | ||||
| 		// collectedEpoch is the epoch number for which this | ||||
| 		// record has been exported.  This is modified by the | ||||
| 		// `Collect()` method. | ||||
| 		// | ||||
| 		// collectedEpoch has to be aligned for 64-bit atomic operations. | ||||
| 		collectedEpoch int64 | ||||
|  | ||||
| 		// modifiedEpoch is the latest epoch number for which | ||||
| 		// this record was updated.  Generally, if | ||||
| 		// modifiedEpoch is less than collectedEpoch, this | ||||
| 		// record is due for reclaimation. | ||||
| 		// | ||||
| 		// modifiedEpoch has to be aligned for 64-bit atomic operations. | ||||
| 		modifiedEpoch int64 | ||||
|  | ||||
| 		// reclaim is an atomic to control the start of reclaiming. | ||||
| 		// | ||||
| 		// reclaim has to be aligned for 64-bit atomic operations. | ||||
| 		reclaim int64 | ||||
|  | ||||
| 		// labels is the LabelSet passed by the user. | ||||
| 		labels *labels | ||||
|  | ||||
| 		// descriptor describes the metric instrument. | ||||
| 		descriptor *export.Descriptor | ||||
|  | ||||
| 		// recorder implements the actual RecordOne() API, | ||||
| 		// depending on the type of aggregation.  If nil, the | ||||
| 		// metric was disabled by the exporter. | ||||
|   | ||||
| @@ -51,6 +51,7 @@ const ( | ||||
|  | ||||
| type ( | ||||
| 	testFixture struct { | ||||
| 		// stop has to be aligned for 64-bit atomic operations. | ||||
| 		stop     int64 | ||||
| 		expected sync.Map | ||||
| 		received sync.Map // Note: doesn't require synchronization | ||||
| @@ -93,6 +94,7 @@ type ( | ||||
| 	// where a race condition causes duplicate records.  We always | ||||
| 	// take the later timestamp. | ||||
| 	gaugeState struct { | ||||
| 		// raw has to be aligned for 64-bit atomic operations. | ||||
| 		raw core.Number | ||||
| 		ts  time.Time | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user