diff --git a/go.mod b/go.mod index db360e52..c99685fe 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/fatih/color v1.18.0 github.com/go-faster/errors v0.7.1 github.com/go-faster/jx v1.1.0 - github.com/go-faster/sdk v0.19.0 + github.com/go-faster/sdk v0.20.0 github.com/go-faster/yaml v0.4.6 github.com/go-logfmt/logfmt v0.6.0 github.com/gogo/protobuf v1.3.2 diff --git a/go.sum b/go.sum index 5a174b6b..908d6080 100644 --- a/go.sum +++ b/go.sum @@ -211,8 +211,8 @@ github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AY github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg= github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg= -github.com/go-faster/sdk v0.19.0 h1:CvdacKSqL7hQEg3YTzQKBMuoW97YQZ2lc8zH9RGh2Qw= -github.com/go-faster/sdk v0.19.0/go.mod h1:UJWFlbuRJHmXJwl4JxStMbbIZtMAz4fxrD4CnuDXCIc= +github.com/go-faster/sdk v0.20.0 h1:3/oZLDBMX+2MLOKSHYLZPLkY9XPFpX4sjV4Q55qJU98= +github.com/go-faster/sdk v0.20.0/go.mod h1:UJWFlbuRJHmXJwl4JxStMbbIZtMAz4fxrD4CnuDXCIc= github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= diff --git a/internal/autometric/autometric.go b/internal/autometric/autometric.go deleted file mode 100644 index a6bd2bca..00000000 --- a/internal/autometric/autometric.go +++ /dev/null @@ -1,174 +0,0 @@ -// Package autometric contains a simple reflect-based OpenTelemetry metric initializer. -package autometric - -import ( - "reflect" - "strconv" - "strings" - - "github.com/go-faster/errors" - "go.opentelemetry.io/otel/metric" -) - -var ( - int64CounterType = reflect.TypeOf(new(metric.Int64Counter)).Elem() - int64UpDownCounterType = reflect.TypeOf(new(metric.Int64UpDownCounter)).Elem() - int64HistogramType = reflect.TypeOf(new(metric.Int64Histogram)).Elem() - int64GaugeType = reflect.TypeOf(new(metric.Int64Gauge)).Elem() - int64ObservableCounterType = reflect.TypeOf(new(metric.Int64ObservableCounter)).Elem() - int64ObservableUpDownCounterType = reflect.TypeOf(new(metric.Int64ObservableUpDownCounter)).Elem() - int64ObservableGaugeType = reflect.TypeOf(new(metric.Int64ObservableGauge)).Elem() -) - -var ( - float64CounterType = reflect.TypeOf(new(metric.Float64Counter)).Elem() - float64UpDownCounterType = reflect.TypeOf(new(metric.Float64UpDownCounter)).Elem() - float64HistogramType = reflect.TypeOf(new(metric.Float64Histogram)).Elem() - float64GaugeType = reflect.TypeOf(new(metric.Float64Gauge)).Elem() - float64ObservableCounterType = reflect.TypeOf(new(metric.Float64ObservableCounter)).Elem() - float64ObservableUpDownCounterType = reflect.TypeOf(new(metric.Float64ObservableUpDownCounter)).Elem() - float64ObservableGaugeType = reflect.TypeOf(new(metric.Float64ObservableGauge)).Elem() -) - -// InitOptions defines options for [Init]. -type InitOptions struct { - // Prefix defines common prefix for all metrics. - Prefix string - // FieldName returns name for given field. - FieldName func(prefix string, sf reflect.StructField) string -} - -func (opts *InitOptions) setDefaults() { - if opts.FieldName == nil { - opts.FieldName = fieldName - } -} - -func fieldName(prefix string, sf reflect.StructField) string { - name := snakeCase(sf.Name) - if tag, ok := sf.Tag.Lookup("name"); ok { - name = tag - } - return prefix + name -} - -// Init initialize metrics in given struct s using given meter. -func Init(m metric.Meter, s any, opts InitOptions) error { - opts.setDefaults() - - ptr := reflect.ValueOf(s) - if !isValidPtrStruct(ptr) { - return errors.Errorf("a pointer-to-struct expected, got %T", s) - } - - var ( - struct_ = ptr.Elem() - structType = struct_.Type() - ) - for i := 0; i < struct_.NumField(); i++ { - fieldType := structType.Field(i) - if fieldType.Anonymous || !fieldType.IsExported() { - continue - } - if n, ok := fieldType.Tag.Lookup("autometric"); ok && n == "-" { - continue - } - - field := struct_.Field(i) - if !field.CanSet() { - continue - } - - mt, err := makeField(m, fieldType, opts) - if err != nil { - return errors.Wrapf(err, "field (%s).%s", structType, fieldType.Name) - } - field.Set(reflect.ValueOf(mt)) - } - - return nil -} - -func makeField(m metric.Meter, sf reflect.StructField, opts InitOptions) (any, error) { - var ( - name = opts.FieldName(opts.Prefix, sf) - unit = sf.Tag.Get("unit") - desc = sf.Tag.Get("description") - boundaries []float64 - ) - if b, ok := sf.Tag.Lookup("boundaries"); ok { - switch ftyp := sf.Type; ftyp { - case int64HistogramType, float64HistogramType: - default: - return nil, errors.Errorf("boundaries tag should be used only on histogram metrics: got %v", ftyp) - } - for _, val := range strings.Split(b, ",") { - f, err := strconv.ParseFloat(val, 64) - if err != nil { - return nil, errors.Wrap(err, "parse boundaries") - } - boundaries = append(boundaries, f) - } - } - - switch ftyp := sf.Type; ftyp { - case int64CounterType: - return m.Int64Counter(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - ) - case int64UpDownCounterType: - return m.Int64UpDownCounter(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - ) - case int64HistogramType: - return m.Int64Histogram(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - metric.WithExplicitBucketBoundaries(boundaries...), - ) - case int64GaugeType: - return m.Int64Gauge(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - ) - case int64ObservableCounterType, - int64ObservableUpDownCounterType, - int64ObservableGaugeType: - return nil, errors.New("observables are not supported") - - case float64CounterType: - return m.Float64Counter(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - ) - case float64UpDownCounterType: - return m.Float64UpDownCounter(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - ) - case float64HistogramType: - return m.Float64Histogram(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - metric.WithExplicitBucketBoundaries(boundaries...), - ) - case float64GaugeType: - return m.Float64Gauge(name, - metric.WithUnit(unit), - metric.WithDescription(desc), - ) - case float64ObservableCounterType, - float64ObservableUpDownCounterType, - float64ObservableGaugeType: - return nil, errors.New("observables are not supported") - default: - return nil, errors.Errorf("unexpected type %v", ftyp) - } -} - -func isValidPtrStruct(ptr reflect.Value) bool { - return ptr.Kind() == reflect.Pointer && - ptr.Elem().Kind() == reflect.Struct -} diff --git a/internal/autometric/autometric_test.go b/internal/autometric/autometric_test.go deleted file mode 100644 index b0dd15b3..00000000 --- a/internal/autometric/autometric_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// Package autometric contains a simple reflect-based OpenTelemetry metric initializer. -package autometric - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric" - sdkmetric "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" -) - -func TestInit(t *testing.T) { - ctx := context.Background() - - reader := sdkmetric.NewManualReader() - mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) - meter := mp.Meter("test-meter") - - var test struct { - // Ignored fields. - _ int - _ metric.Int64Counter - // Embedded fields. - fmt.Stringer - // Private fields. - private int - privateCounter metric.Int64Counter - // Skip. - SkipMe metric.Int64Counter `autometric:"-"` - SkipMe2 metric.Int64ObservableCounter `autometric:"-"` - - Int64Counter metric.Int64Counter - Int64UpDownCounter metric.Int64UpDownCounter - Int64Histogram metric.Int64Histogram - Int64Gauge metric.Int64Gauge - Float64Counter metric.Float64Counter - Float64UpDownCounter metric.Float64UpDownCounter - Float64Histogram metric.Float64Histogram - Float64Gauge metric.Float64Gauge - - Renamed metric.Int64Counter `name:"mega_counter"` - WithDesc metric.Int64Counter `name:"with_desc" description:"foo"` - WithUnit metric.Int64Counter `name:"with_unit" unit:"By"` - WithBounds metric.Float64Histogram `name:"with_bounds" boundaries:"1,2,5"` - } - const prefix = "testmetrics.points." - require.NoError(t, Init(meter, &test, InitOptions{ - Prefix: prefix, - })) - - require.Nil(t, test.privateCounter) - - require.NotNil(t, test.Int64Counter) - test.Int64Counter.Add(ctx, 1) - require.NotNil(t, test.Int64UpDownCounter) - test.Int64UpDownCounter.Add(ctx, 1) - require.NotNil(t, test.Int64Histogram) - test.Int64Histogram.Record(ctx, 1) - require.NotNil(t, test.Int64Gauge) - test.Int64Gauge.Record(ctx, 1) - require.NotNil(t, test.Float64Counter) - test.Float64Counter.Add(ctx, 1) - require.NotNil(t, test.Float64UpDownCounter) - test.Float64UpDownCounter.Add(ctx, 1) - require.NotNil(t, test.Float64Histogram) - test.Float64Histogram.Record(ctx, 1) - require.NotNil(t, test.Float64Gauge) - test.Float64Gauge.Record(ctx, 1) - - require.NotNil(t, test.Renamed) - test.Renamed.Add(ctx, 1) - require.NotNil(t, test.WithDesc) - test.WithDesc.Add(ctx, 1) - require.NotNil(t, test.WithUnit) - test.WithUnit.Add(ctx, 1) - require.NotNil(t, test.WithBounds) - test.WithBounds.Record(ctx, 1) - - require.NoError(t, mp.ForceFlush(ctx)) - var data metricdata.ResourceMetrics - require.NoError(t, reader.Collect(ctx, &data)) - - type MetricInfo struct { - Name string - Description string - Unit string - } - var infos []MetricInfo - for _, scope := range data.ScopeMetrics { - for _, metric := range scope.Metrics { - infos = append(infos, MetricInfo{ - Name: metric.Name, - Description: metric.Description, - Unit: metric.Unit, - }) - } - } - require.Equal(t, - []MetricInfo{ - {Name: prefix + "int64_counter"}, - {Name: prefix + "int64_up_down_counter"}, - {Name: prefix + "int64_histogram"}, - {Name: prefix + "int64_gauge"}, - {Name: prefix + "float64_counter"}, - {Name: prefix + "float64_up_down_counter"}, - {Name: prefix + "float64_histogram"}, - {Name: prefix + "float64_gauge"}, - - {Name: prefix + "mega_counter"}, - {Name: prefix + "with_desc", Description: "foo"}, - {Name: prefix + "with_unit", Unit: "By"}, - {Name: prefix + "with_bounds"}, - }, - infos, - ) -} - -func TestInitErrors(t *testing.T) { - type ( - JustStruct struct{} - - UnexpectedType struct { - Foo metric.Observable - } - UnsupportedInt64Observable struct { - Observable metric.Int64ObservableCounter - } - UnsupportedFloat64Observable struct { - Observable metric.Float64ObservableCounter - } - - BoundariesOnNonHistogram struct { - C metric.Int64Counter `boundaries:"foo"` - } - - BadBoundaries struct { - H metric.Float64Histogram `boundaries:"foo"` - } - BadBoundaries2 struct { - H metric.Float64Histogram `boundaries:"foo,"` - } - ) - - for i, tt := range []struct { - s any - err string - }{ - {0, "a pointer-to-struct expected, got int"}, - {JustStruct{}, "a pointer-to-struct expected, got autometric.JustStruct"}, - - {&UnexpectedType{}, "field (autometric.UnexpectedType).Foo: unexpected type metric.Observable"}, - {&UnsupportedInt64Observable{}, "field (autometric.UnsupportedInt64Observable).Observable: observables are not supported"}, - {&UnsupportedFloat64Observable{}, "field (autometric.UnsupportedFloat64Observable).Observable: observables are not supported"}, - - {&BoundariesOnNonHistogram{}, `field (autometric.BoundariesOnNonHistogram).C: boundaries tag should be used only on histogram metrics: got metric.Int64Counter`}, - {&BadBoundaries{}, `field (autometric.BadBoundaries).H: parse boundaries: strconv.ParseFloat: parsing "foo": invalid syntax`}, - {&BadBoundaries2{}, `field (autometric.BadBoundaries2).H: parse boundaries: strconv.ParseFloat: parsing "foo": invalid syntax`}, - } { - t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { - mp := sdkmetric.NewMeterProvider() - meter := mp.Meter("test-meter") - require.EqualError(t, Init(meter, tt.s, InitOptions{}), tt.err) - }) - } -} diff --git a/internal/autometric/strcase.go b/internal/autometric/strcase.go deleted file mode 100644 index 42bd2943..00000000 --- a/internal/autometric/strcase.go +++ /dev/null @@ -1,71 +0,0 @@ -package autometric - -import ( - "strings" - "unicode" -) - -func snakeCase(s string) string { - const delim = '_' - s = strings.TrimSpace(s) - for _, c := range s { - if isUpper(c) { - goto slow - } - } - return s - -slow: - var sb strings.Builder - sb.Grow(len(s) + 8) - - var prev, curr rune - for i, next := range s { - switch { - case isDelim(curr): - if !isDelim(prev) { - sb.WriteByte(delim) - } - case isUpper(curr): - if isLower(prev) || - (isUpper(prev) && isLower(next)) || - (isDigit(prev) && isAlpha(next)) { - sb.WriteByte(delim) - } - sb.WriteRune(unicode.ToLower(curr)) - case i != 0: - sb.WriteRune(unicode.ToLower(curr)) - } - prev = curr - curr = next - } - - if s != "" { - if isUpper(curr) && isLower(prev) { - sb.WriteByte(delim) - } - sb.WriteRune(unicode.ToLower(curr)) - } - - return sb.String() -} - -func isDelim(ch rune) bool { - return unicode.IsSpace(ch) || ch == '_' || ch == '-' -} - -func isAlpha(ch rune) bool { - return isUpper(ch) || isLower(ch) -} - -func isDigit(ch rune) bool { - return ch >= '0' && ch <= '9' -} - -func isUpper(ch rune) bool { - return ch >= 'A' && ch <= 'Z' -} - -func isLower(ch rune) bool { - return ch >= 'a' && ch <= 'z' -} diff --git a/internal/autometric/strcase_test.go b/internal/autometric/strcase_test.go deleted file mode 100644 index 95700c78..00000000 --- a/internal/autometric/strcase_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package autometric - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" -) - -func Test_snakeCase(t *testing.T) { - tests := []struct { - s string - want string - }{ - {"", ""}, - {"f", "f"}, - {"F", "f"}, - {"Foo", "foo"}, - {"FooB", "foo_b"}, - {" FooBar\t", "foo_bar"}, - {"foo__Bar", "foo_bar"}, - {"foo--Bar", "foo_bar"}, - {"foo Bar", "foo_bar"}, - {"foo\tBar", "foo_bar"}, - {"Int64UpDownCounter", "int64_up_down_counter"}, - } - for i, tt := range tests { - tt := tt - t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { - require.Equal(t, tt.want, snakeCase(tt.s)) - }) - } -} diff --git a/internal/chstorage/inserter.go b/internal/chstorage/inserter.go index c62b1185..082f95b3 100644 --- a/internal/chstorage/inserter.go +++ b/internal/chstorage/inserter.go @@ -2,11 +2,11 @@ package chstorage import ( "github.com/go-faster/errors" + "github.com/go-faster/sdk/autometric" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" - "github.com/go-faster/oteldb/internal/autometric" "github.com/go-faster/oteldb/internal/tracestorage" )