Codebase list golang-github-go-kit-kit / 439c4d2
metrics/generic: fix uint64 alignment (#1007) * fix: uint64 alignment in metrics/generic * test: adds a weak test on atomic alignment. Ludovic Fernandez authored 2 years ago GitHub committed 2 years ago
2 changed file(s) with 77 addition(s) and 2 deletion(s). Raw diff Collapse all Expand all
1717
1818 // Counter is an in-memory implementation of a Counter.
1919 type Counter struct {
20 bits uint64 // bits has to be the first word in order to be 64-aligned on 32-bit
2021 Name string
2122 lvs lv.LabelValues
22 bits uint64
2323 }
2424
2525 // NewCounter returns a new, usable Counter.
8080
8181 // Gauge is an in-memory implementation of a Gauge.
8282 type Gauge struct {
83 bits uint64 // bits has to be the first word in order to be 64-aligned on 32-bit
8384 Name string
8485 lvs lv.LabelValues
85 bits uint64
8686 }
8787
8888 // NewGauge returns a new, usable Gauge.
44 // generic to use its Histogram in the Quantiles helper function.
55
66 import (
7 "go/ast"
8 "go/importer"
9 "go/parser"
10 "go/token"
11 "go/types"
12 "io/ioutil"
713 "math"
814 "math/rand"
915 "sync"
106112 t.Errorf("want %f, have %f", want, have)
107113 }
108114 }
115
116 // Naive atomic alignment test.
117 // The problem is related to the use of `atomic.*` and not directly to a structure.
118 // But currently works for Counter and Gauge.
119 // To have a more solid test, this test should be removed and the other tests should be run on a 32-bit arch.
120 func TestAtomicAlignment(t *testing.T) {
121 content, err := ioutil.ReadFile("./generic.go")
122 if err != nil {
123 t.Fatal(err)
124 }
125
126 fset := token.NewFileSet()
127
128 file, err := parser.ParseFile(fset, "generic.go", content, parser.ParseComments)
129 if err != nil {
130 t.Fatal(err)
131 }
132
133 conf := types.Config{Importer: importer.ForCompiler(fset, "source", nil)}
134
135 pkg, err := conf.Check(".", fset, []*ast.File{file}, nil)
136 if err != nil {
137 t.Fatal(err)
138 }
139
140 // uses ARM as reference for 32-bit arch
141 sizes := types.SizesFor("gc", "arm")
142
143 names := []string{"Counter", "Gauge"}
144
145 for _, name := range names {
146 t.Run(name, func(t *testing.T) {
147 checkAtomicAlignment(t, sizes, pkg.Scope().Lookup(name), pkg)
148 })
149 }
150 }
151
152 func checkAtomicAlignment(t *testing.T, sizes types.Sizes, obj types.Object, pkg *types.Package) {
153 t.Helper()
154
155 st := obj.Type().Underlying().(*types.Struct)
156
157 posToCheck := make(map[int]types.Type)
158
159 var vars []*types.Var
160 for i := 0; i < st.NumFields(); i++ {
161 field := st.Field(i)
162
163 if v, ok := field.Type().(*types.Basic); ok {
164 switch v.Kind() {
165 case types.Uint64, types.Float64, types.Int64:
166 posToCheck[i] = v
167 }
168 }
169
170 vars = append(vars, types.NewVar(field.Pos(), pkg, field.Name(), field.Type()))
171 }
172
173 offsets := sizes.Offsetsof(vars)
174 for i, offset := range offsets {
175 if _, ok := posToCheck[i]; !ok {
176 continue
177 }
178
179 if offset%8 != 0 {
180 t.Errorf("misalignment detected in %s for the type %s, offset %d", obj.Name(), posToCheck[i], offset)
181 }
182 }
183 }