Skip to content

Commit

Permalink
add Slow sql connector (#25)
Browse files Browse the repository at this point in the history
* slow sql connector

Signed-off-by: Jared Tan <jian.tan@daocloud.io>

* slow sql connector

Signed-off-by: Jared Tan <jian.tan@daocloud.io>

* update builder-config.yaml

Signed-off-by: Jared Tan <jian.tan@daocloud.io>

* update db sysystem filter

Signed-off-by: Jared Tan <jian.tan@daocloud.io>

* update span name

Signed-off-by: Jared Tan <jian.tan@daocloud.io>

---------

Signed-off-by: Jared Tan <jian.tan@daocloud.io>
  • Loading branch information
JaredTan95 authored Aug 26, 2024
1 parent 66756a8 commit eb233ab
Show file tree
Hide file tree
Showing 26 changed files with 1,152 additions and 13 deletions.
2 changes: 2 additions & 0 deletions cmd/otelcontribcol/builder-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ connectors:
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector v0.107.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/servicegraphconnector v0.107.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.107.0
- gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/slowsqlconnector v0.107.0

providers:
- gomod: go.opentelemetry.io/collector/confmap/provider/envprovider v0.107.0
Expand Down Expand Up @@ -493,3 +494,4 @@ replaces:
- github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otelarrowreceiver => ../../receiver/otelarrowreceiver
- github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/cfgardenobserver => ../../extension/observer/cfgardenobserver
- github.com/open-telemetry/opentelemetry-collector-contrib/exporter/rabbitmqexporter => ../../exporter/rabbitmqexporter
- github.com/open-telemetry/opentelemetry-collector-contrib/connector/slowsqlconnector => ../../connector/slowsqlconnector
4 changes: 4 additions & 0 deletions cmd/otelcontribcol/components.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions cmd/otelcontribcol/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/open-telemetry/opentelemetry-collector-contrib/connector/roundrobinconnector v0.107.0
github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector v0.107.0
github.com/open-telemetry/opentelemetry-collector-contrib/connector/servicegraphconnector v0.107.0
github.com/open-telemetry/opentelemetry-collector-contrib/connector/slowsqlconnector v0.107.0
github.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.107.0
github.com/open-telemetry/opentelemetry-collector-contrib/exporter/alertmanagerexporter v0.107.0
github.com/open-telemetry/opentelemetry-collector-contrib/exporter/alibabacloudlogserviceexporter v0.107.0
Expand Down Expand Up @@ -1344,3 +1345,5 @@ replace github.com/open-telemetry/opentelemetry-collector-contrib/receiver/otela
replace github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/cfgardenobserver => ../../extension/observer/cfgardenobserver

replace github.com/open-telemetry/opentelemetry-collector-contrib/exporter/rabbitmqexporter => ../../exporter/rabbitmqexporter

replace github.com/open-telemetry/opentelemetry-collector-contrib/connector/slowsqlconnector => ../../connector/slowsqlconnector
1 change: 1 addition & 0 deletions connector/slowsqlconnector/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
66 changes: 66 additions & 0 deletions connector/slowsqlconnector/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package slowsqlconnector // import "github.com/open-telemetry/opentelemetry-collector-contrib/connector/slowsqlconnector"

import (
"fmt"

"go.opentelemetry.io/collector/component"
)

// Dimension defines the dimension name and optional default value if the Dimension is missing from a span attribute.
type Dimension struct {
Name string `mapstructure:"name"`
Default *string `mapstructure:"default"`
}

type Exemplars struct {
Enabled bool `mapstructure:"enabled"`
}

// Config defines the configuration options for exceptionsconnector
type Config struct {
// Threshold of slow sql. unit is seconds, default 1s.
Threshold float64 `mapstructure:"threshold"`
// Filter specific db systems, default "h2", "mongodb", "mssql", "mysql", "oracle", "progress", "postgresql", "mariadb", ref: https://opentelemetry.io/docs/specs/semconv/attributes-registry/db/
DBSystem []string `mapstructure:"db_system"`
// Dimensions defines the list of additional dimensions on top of the provided:
// - service.name
// - span.name
// - span.kind
// - status.code
// The dimensions will be fetched from the span's attributes. Examples of some conventionally used attributes:
// https://github.com/open-telemetry/opentelemetry-collector/blob/main/model/semconv/opentelemetry.go.
Dimensions []Dimension `mapstructure:"dimensions"`
// Exemplars defines the configuration for exemplars.
Exemplars Exemplars `mapstructure:"exemplars"`
}

var _ component.ConfigValidator = (*Config)(nil)

// Validate checks if the connector configuration is valid
func (c Config) Validate() error {
err := validateDimensions(c.Dimensions)
if err != nil {
return err
}
return nil
}

// validateDimensions checks duplicates for reserved dimensions and additional dimensions.
func validateDimensions(dimensions []Dimension) error {
labelNames := make(map[string]struct{})
for _, key := range []string{serviceNameKey, spanKindKey, spanNameKey, statusCodeKey} {
labelNames[key] = struct{}{}
}

for _, key := range dimensions {
if _, ok := labelNames[key.Name]; ok {
return fmt.Errorf("duplicate dimension name %q", key.Name)
}
labelNames[key.Name] = struct{}{}
}

return nil
}
104 changes: 104 additions & 0 deletions connector/slowsqlconnector/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package slowsqlconnector

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap/confmaptest"

"github.com/open-telemetry/opentelemetry-collector-contrib/connector/slowsqlconnector/internal/metadata"
)

func TestLoadConfig(t *testing.T) {
t.Parallel()

cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)

tests := []struct {
id component.ID
expected component.Config
}{
{
id: component.NewIDWithName(metadata.Type, "default"),
expected: createDefaultConfig(),
},
{
id: component.NewIDWithName(metadata.Type, "full"),
expected: &Config{
Threshold: 1,
Dimensions: []Dimension{
{Name: "k8s.uuid"},
{Name: "k8s.namespace.name"},
},
Exemplars: Exemplars{
Enabled: false,
},
},
},
}

for _, tt := range tests {
t.Run(tt.id.String(), func(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()

sub, err := cm.Sub(tt.id.String())
require.NoError(t, err)
err = sub.Unmarshal(cfg)
assert.NoError(t, err)
assert.NoError(t, component.ValidateConfig(cfg))
assert.Equal(t, tt.expected, cfg)
})
}
}

func TestValidateDimensions(t *testing.T) {
for _, tc := range []struct {
name string
dimensions []Dimension
expectedErr string
}{
{
name: "no additional dimensions",
dimensions: []Dimension{},
},
{
name: "no duplicate dimensions",
dimensions: []Dimension{
{Name: "http.service_name"},
{Name: "http.status_code"},
},
},
{
name: "duplicate dimension with reserved labels",
dimensions: []Dimension{
{Name: "service.name"},
},
expectedErr: "duplicate dimension name \"service.name\"",
},
{
name: "duplicate additional dimensions",
dimensions: []Dimension{
{Name: "service_name"},
{Name: "service_name"},
},
expectedErr: "duplicate dimension name \"service_name\"",
},
} {
t.Run(tc.name, func(t *testing.T) {
err := validateDimensions(tc.dimensions)
if tc.expectedErr != "" {
assert.EqualError(t, err, tc.expectedErr)
} else {
assert.NoError(t, err)
}
})
}
}
71 changes: 71 additions & 0 deletions connector/slowsqlconnector/connector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package slowsqlconnector // import "github.com/open-telemetry/opentelemetry-collector-contrib/connector/slowsqlconnector"

import (
"go.opentelemetry.io/collector/pdata/pcommon"
conventions "go.opentelemetry.io/collector/semconv/v1.18.0"
)

const (
serviceNameKey = conventions.AttributeServiceName
spanKindKey = "span.kind" // OpenTelemetry non-standard constant.
spanNameKey = "span.name" // OpenTelemetry non-standard constant.
statusCodeKey = "status.code" // OpenTelemetry non-standard constant.
dbStatementKey = conventions.AttributeDBStatement // OpenTelemetry non-standard constant.
dbSystemKey = conventions.AttributeDBSystem // OpenTelemetry non-standard constant.
statementExecDuration = "db.statement.duration" // OpenTelemetry non-standard constant.
)

type dimension struct {
name string
value *pcommon.Value
}

func newDimensions(cfgDims []Dimension) []dimension {
if len(cfgDims) == 0 {
return nil
}
dims := make([]dimension, len(cfgDims))
for i := range cfgDims {
dims[i].name = cfgDims[i].Name
if cfgDims[i].Default != nil {
val := pcommon.NewValueStr(*cfgDims[i].Default)
dims[i].value = &val
}
}
return dims
}

// getDimensionValue gets the dimension value for the given configured dimension.
// It searches through the span's attributes first, being the more specific;
// falling back to searching in resource attributes if it can't be found in the span.
// Finally, falls back to the configured default value if provided.
//
// The ok flag indicates if a dimension value was fetched in order to differentiate
// an empty string value from a state where no value was found.
func getDimensionValue(d dimension, spanAttrs pcommon.Map, resourceAttr pcommon.Map) (v pcommon.Value, ok bool) {
// The more specific span attribute should take precedence.
if attr, exists := spanAttrs.Get(d.name); exists {
return attr, true
}
// falling back to searching in resource attributes
if attr, exists := resourceAttr.Get(d.name); exists {
return attr, true
}
// Set the default if configured, otherwise this metric will have no value set for the dimension.
if d.value != nil {
return *d.value, true
}
return v, ok
}

func findAttributeValue(key string, attributes ...pcommon.Map) (string, bool) {
for _, attr := range attributes {
if v, ok := attr.Get(key); ok {
return v.AsString(), true
}
}
return "", false
}
Loading

0 comments on commit eb233ab

Please sign in to comment.