Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[receiver/oracledbreceiver] add implementation of oracledbreceiver #16044

Merged
merged 7 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .chloggen/oracledbreceiver-impl.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: oracledbreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add oracledbreceiver implementation (config, scraper, db client)

# One or more tracking issues related to the change
issues: [16043]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
47 changes: 47 additions & 0 deletions receiver/oracledbreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,51 @@ This receiver collects metrics from an Oracle Database.

The receiver connects to a database host and performs periodically queries.

Supported pipeline types: metrics

## Getting Started

The following settings are required:

- `datasource`: Oracle database connection string. Refer to Oracle Go Driver go_ora documentation for full connection string options.

Example:

```yaml
receivers:
oracledb:
datasource: "oracle://otel:password@localhost:51521/XE"
```

## Permissions

Depending on which metrics you collect, you will need to assign those permissions to the database user:
```
GRANT SELECT ON V_$SESSION TO <username>;
GRANT SELECT ON V_$SYSSTAT TO <username>;
GRANT SELECT ON V_$RESOURCE_LIMIT TO <username>;
GRANT SELECT ON DBA_TABLESPACES TO <username>;
GRANT SELECT ON DBA_DATA_FILES TO <username>;
```

## Enabling metrics.

See [documentation.md].

You can enable or disable selective metrics.

Example:

```yaml
receivers:
oracledb:
datasource: "oracle://otel:password@localhost:51521/XE"
metrics:
oracledb.query.cpu_time:
enabled: false
oracledb.query.physical_read_requests:
enabled: true
```

[development]: https://github.com/open-telemetry/opentelemetry-collector#development

38 changes: 36 additions & 2 deletions receiver/oracledbreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,42 @@

package oracledbreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/oracledbreceiver"

import "go.opentelemetry.io/collector/config"
import (
"errors"
"fmt"
"net/url"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/receiver/scraperhelper"

"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/oracledbreceiver/internal/metadata"
)

type Config struct {
config.ReceiverSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
config.ReceiverSettings `mapstructure:",squash"`
DataSource string `mapstructure:"datasource"`
scraperhelper.ScraperControllerSettings `mapstructure:",squash"`
MetricsSettings metadata.MetricsSettings `mapstructure:"metrics"`
}

func (c Config) Validate() error {
if c.DataSource == "" {
return errors.New("'datasource' cannot be empty")
}
if _, err := url.Parse(c.DataSource); err != nil {
return fmt.Errorf("'datasource' is invalid: %w", err)
}
return nil
}

func CreateDefaultConfig() component.Config {
return &Config{
ScraperControllerSettings: scraperhelper.ScraperControllerSettings{
ReceiverSettings: config.NewReceiverSettings(component.NewID(typeStr)),
CollectionInterval: 10 * time.Second,
},
MetricsSettings: metadata.DefaultMetricsSettings(),
}
}
52 changes: 52 additions & 0 deletions receiver/oracledbreceiver/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oracledbreceiver // import "github.com/open-telemetry/open-telemetry-collector-contrib/receiver/oracledbreceiver"

import (
"path"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/service/servicetest"
)

func TestCreateDefaultConfig(t *testing.T) {
cfg := CreateDefaultConfig().(*Config)
assert.Equal(t, 10*time.Second, cfg.ScraperControllerSettings.CollectionInterval)
}

func TestParseConfig(t *testing.T) {
cfg, err := servicetest.LoadConfigAndValidate(path.Join("testdata", "config_test.yaml"), testFactories(t))
require.NoError(t, err)
sqlCfg := cfg.Receivers[component.NewID("oracledb")].(*Config)
assert.Equal(t, "oracle://otel:password@localhost:51521/XE", sqlCfg.DataSource)
settings := sqlCfg.MetricsSettings
assert.False(t, settings.OracledbTablespaceSizeUsage.Enabled)
assert.False(t, settings.OracledbExchangeDeadlocks.Enabled)
}

func testFactories(t *testing.T) component.Factories {
factories, err := componenttest.NopFactories()
require.NoError(t, err)
factories.Receivers["oracledb"] = component.NewReceiverFactory(
"oracledb",
CreateDefaultConfig,
)
return factories
}
102 changes: 102 additions & 0 deletions receiver/oracledbreceiver/db_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oracledbreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/oracledbreceiver"

import (
"context"
"database/sql"
"fmt"
"reflect"
"strings"

// register db driver
_ "github.com/sijms/go-ora/v2"
"go.uber.org/zap"
)

// This will be removed and imported from pkg/sqlquery once https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/14321 is merged.
type dbClient interface {
metricRows(ctx context.Context) ([]metricRow, error)
}

type metricRow map[string]string

type dbSQLClient struct {
db *sql.DB
logger *zap.Logger
sql string
}

func newDbClient(db *sql.DB, sql string, logger *zap.Logger) dbClient {
return dbSQLClient{
db: db,
sql: sql,
logger: logger,
}
}

func (cl dbSQLClient) metricRows(ctx context.Context) ([]metricRow, error) {
sqlRows, err := cl.db.QueryContext(ctx, cl.sql)
if err != nil {
return nil, err
}
var out []metricRow
row := reusableRow{
attrs: map[string]func() string{},
}
types, err := sqlRows.ColumnTypes()
if err != nil {
return nil, err
}
for _, sqlType := range types {
colName := sqlType.Name()
var v interface{}
row.attrs[colName] = func() string {
format := "%v"
if v == nil {
return ""
}
if reflect.TypeOf(v).Kind() == reflect.Slice {
// The Postgres driver returns a []uint8 (a string) for decimal and numeric types,
// which we want to render as strings. e.g. "4.1" instead of "[52, 46, 49]".
// Other slice types get the same treatment.
format = "%s"
}
return fmt.Sprintf(format, v)
}
row.scanDest = append(row.scanDest, &v)
}
for sqlRows.Next() {
err = sqlRows.Scan(row.scanDest...)
if err != nil {
return nil, err
}
out = append(out, row.toMetricRow())
}
return out, nil
}

type reusableRow struct {
attrs map[string]func() string
scanDest []interface{}
}

func (row reusableRow) toMetricRow() metricRow {
out := metricRow{}
for k, f := range row.attrs {
out[strings.ToUpper(k)] = f()
}
return out
}
52 changes: 47 additions & 5 deletions receiver/oracledbreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ package oracledbreceiver // import "github.com/open-telemetry/opentelemetry-coll

import (
"context"
"database/sql"
"net/url"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver/scraperhelper"

"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/oracledbreceiver/internal/metadata"
)

const (
Expand All @@ -32,13 +38,49 @@ func NewFactory() component.ReceiverFactory {
return component.NewReceiverFactory(
typeStr,
createDefaultConfig,
component.WithMetricsReceiver(createMetricsReceiver, stability))
component.WithMetricsReceiver(createReceiverFunc(func(dataSourceName string) (*sql.DB, error) {
return sql.Open("oracle", dataSourceName)
}, newDbClient), stability))
}

func createMetricsReceiver(ctx context.Context, settings component.ReceiverCreateSettings, receiver component.Config, metrics consumer.Metrics) (component.MetricsReceiver, error) {
return &oracledbreceiver{}, nil
func createDefaultConfig() component.Config {
return &Config{
ReceiverSettings: config.NewReceiverSettings(component.NewID(typeStr)),
ScraperControllerSettings: scraperhelper.ScraperControllerSettings{
ReceiverSettings: config.NewReceiverSettings(component.NewID(typeStr)),
CollectionInterval: 10 * time.Second,
},
MetricsSettings: metadata.DefaultMetricsSettings(),
}
}

func createDefaultConfig() component.Config {
return &Config{ReceiverSettings: config.NewReceiverSettings(component.NewID(typeStr))}
type sqlOpenerFunc func(dataSourceName string) (*sql.DB, error)

func createReceiverFunc(sqlOpenerFunc sqlOpenerFunc, clientProviderFunc clientProviderFunc) component.CreateMetricsReceiverFunc {
return func(
ctx context.Context,
settings component.ReceiverCreateSettings,
cfg component.Config,
consumer consumer.Metrics,
) (component.MetricsReceiver, error) {
sqlCfg := cfg.(*Config)
metricsBuilder := metadata.NewMetricsBuilder(sqlCfg.MetricsSettings, settings)
datasourceURL, _ := url.Parse(sqlCfg.DataSource)
instanceName := datasourceURL.Host

mp, err := newScraper(settings.ID, metricsBuilder, sqlCfg.MetricsSettings, sqlCfg.ScraperControllerSettings, settings.TelemetrySettings.Logger, func() (*sql.DB, error) {
return sqlOpenerFunc(sqlCfg.DataSource)
}, clientProviderFunc, instanceName)
if err != nil {
return nil, err
}
opt := scraperhelper.AddScraper(mp)

return scraperhelper.NewScraperControllerReceiver(
&sqlCfg.ScraperControllerSettings,
settings,
consumer,
opt,
)
}
}
42 changes: 42 additions & 0 deletions receiver/oracledbreceiver/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oracledbreceiver

import (
"context"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
)

func TestNewFactory(t *testing.T) {
factory := NewFactory()
_, err := factory.CreateMetricsReceiver(
context.Background(),
component.ReceiverCreateSettings{
TelemetrySettings: component.TelemetrySettings{
TracerProvider: trace.NewNoopTracerProvider(),
MeterProvider: metric.NewNoopMeterProvider(),
},
},
factory.CreateDefaultConfig(),
consumertest.NewNop(),
)
require.NoError(t, err)
}
Loading