Skip to content

Commit 14fef66

Browse files
committed
[receiver/oracledb] events for oracledbreceiver - Adding top_query and query_sample collection
1 parent f1d7085 commit 14fef66

34 files changed

+2217
-106
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: oracledbreceiver
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Support query-level log collection, fetching top N query metrics and details of currently running queries (samples).
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [37478]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
With the introduction of query level log collection, the oracledbreceiver can now send metrics for Top N queries,
20+
filtered based on the highest CPU time consumed. The number of queries can be configured. This helps the user to have
21+
a better understanding of the database operations. In addition to this, query samples can be collected for the currently
22+
executing queries. This enhancement enables users to proactively monitor and manage the database performance.
23+
24+
# If your change doesn't affect end users or the exported elements of any package,
25+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
26+
# Optional: The change log or logs in which this entry should be included.
27+
# e.g. '[user]' or '[user, api]'
28+
# Include 'user' if the change is relevant to end users.
29+
# Include 'api' if there is a change to a library API.
30+
# Default: '[user]'
31+
change_logs: [user]

receiver/oracledbreceiver/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
<!-- status autogenerated section -->
44
| Status | |
55
| ------------- |-----------|
6-
| Stability | [alpha]: metrics |
6+
| Stability | [development]: logs |
7+
| | [alpha]: metrics |
78
| Distributions | [contrib] |
89
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Foracledb%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Foracledb) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Foracledb%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Foracledb) |
910
| Code coverage | [![codecov](https://codecov.io/github/open-telemetry/opentelemetry-collector-contrib/graph/main/badge.svg?component=receiver_oracledb)](https://app.codecov.io/gh/open-telemetry/opentelemetry-collector-contrib/tree/main/?components%5B0%5D=receiver_oracledb&displayType=list) |
1011
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@dmitryax](https://www.github.com/dmitryax), [@crobert-1](https://www.github.com/crobert-1), [@atoulme](https://www.github.com/atoulme) |
1112

13+
[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
1214
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
1315
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
1416
<!-- end autogenerated section -->

receiver/oracledbreceiver/config.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,24 @@ import (
1717
)
1818

1919
var (
20-
errBadDataSource = errors.New("datasource is invalid")
21-
errBadEndpoint = errors.New("endpoint must be specified as host:port")
22-
errBadPort = errors.New("invalid port in endpoint")
23-
errEmptyEndpoint = errors.New("endpoint must be specified")
24-
errEmptyPassword = errors.New("password must be set")
25-
errEmptyService = errors.New("service must be specified")
26-
errEmptyUsername = errors.New("username must be set")
20+
errBadDataSource = errors.New("datasource is invalid")
21+
errBadEndpoint = errors.New("endpoint must be specified as host:port")
22+
errBadPort = errors.New("invalid port in endpoint")
23+
errEmptyEndpoint = errors.New("endpoint must be specified")
24+
errEmptyPassword = errors.New("password must be set")
25+
errEmptyService = errors.New("service must be specified")
26+
errEmptyUsername = errors.New("username must be set")
27+
errMaxQuerySampleCount = errors.New("`max_query_sample_count` must be between 1 and 10000")
28+
errTopQueryCount = errors.New("`top_query_count` must be between 1 and 200 and less than or equal to `max_query_sample_count`")
29+
errQueryCacheSize = errors.New("`query_cache_size` must be strictly positive")
2730
)
2831

32+
type TopQueryCollection struct {
33+
MaxQuerySampleCount uint `mapstructure:"max_query_sample_count"`
34+
TopQueryCount uint `mapstructure:"top_query_count"`
35+
QueryCacheSize int `mapstructure:"query_cache_size"`
36+
}
37+
2938
type Config struct {
3039
DataSource string `mapstructure:"datasource"`
3140
Endpoint string `mapstructure:"endpoint"`
@@ -34,6 +43,9 @@ type Config struct {
3443
Username string `mapstructure:"username"`
3544
scraperhelper.ControllerConfig `mapstructure:",squash"`
3645
metadata.MetricsBuilderConfig `mapstructure:",squash"`
46+
metadata.LogsBuilderConfig `mapstructure:",squash"`
47+
48+
TopQueryCollection `mapstructure:"top_query_collection"`
3749
}
3850

3951
func (c Config) Validate() error {
@@ -79,5 +91,15 @@ func (c Config) Validate() error {
7991
allErrs = multierr.Append(allErrs, fmt.Errorf("%w: %s", errBadDataSource, err.Error()))
8092
}
8193
}
94+
95+
if c.MaxQuerySampleCount < 1 || c.MaxQuerySampleCount > 10000 {
96+
allErrs = multierr.Append(allErrs, errMaxQuerySampleCount)
97+
}
98+
if c.TopQueryCount < 1 || c.TopQueryCount > 200 || c.TopQueryCount > c.MaxQuerySampleCount {
99+
allErrs = multierr.Append(allErrs, errTopQueryCount)
100+
}
101+
if c.QueryCacheSize <= 0 {
102+
allErrs = multierr.Append(allErrs, errQueryCacheSize)
103+
}
82104
return allErrs
83105
}

receiver/oracledbreceiver/db_client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
)
1717

1818
type dbClient interface {
19-
metricRows(ctx context.Context) ([]metricRow, error)
19+
metricRows(ctx context.Context, args ...any) ([]metricRow, error)
2020
}
2121

2222
type metricRow map[string]string
@@ -35,8 +35,8 @@ func newDbClient(db *sql.DB, sql string, logger *zap.Logger) dbClient {
3535
}
3636
}
3737

38-
func (cl dbSQLClient) metricRows(ctx context.Context) ([]metricRow, error) {
39-
sqlRows, err := cl.db.QueryContext(ctx, cl.sql)
38+
func (cl dbSQLClient) metricRows(ctx context.Context, args ...any) ([]metricRow, error) {
39+
sqlRows, err := cl.db.QueryContext(ctx, cl.sql, args...)
4040
if err != nil {
4141
return nil, err
4242
}

receiver/oracledbreceiver/documentation.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,90 @@ Number of SELECT statements executed in parallel
369369
| ---- | ----------- | ---------- | ----------------------- | --------- |
370370
| {queries} | Sum | Int | Cumulative | true |
371371
372+
## Default Events
373+
374+
The following events are emitted by default. Each of them can be disabled by applying the following configuration:
375+
376+
```yaml
377+
events:
378+
<event_name>:
379+
enabled: false
380+
```
381+
382+
## Optional Events
383+
384+
The following events are not emitted by default. Each of them can be enabled by applying the following configuration:
385+
386+
```yaml
387+
events:
388+
<event_name>:
389+
enabled: true
390+
```
391+
392+
### db.server.query_sample
393+
394+
sample query
395+
396+
#### Attributes
397+
398+
| Name | Description | Values |
399+
| ---- | ----------- | ------ |
400+
| db.query.text | The text of the database query being executed. | Any Str |
401+
| db.system.name | The database management system (DBMS) product as identified by the client instrumentation. | Any Str |
402+
| user.name | Database user name under which a session is connected to | Any Str |
403+
| client.address | Identifies the name of the machine (host) from which the database client is connecting to the Oracle database. | Any Str |
404+
| oracledb.plan_hash_value | Binary hash value calculated on the query execution plan and used to identify similar query execution plans, reported in the HEX format. | Any Str |
405+
| oracledb.sql_id | The SQL ID of the query. | Any Str |
406+
| oracledb.child_number | The child number of the query. | Any Str |
407+
| oracledb.sid | ID of the Oracle Server session. | Any Str |
408+
| oracledb.serial | Serial number associated with a session. | Any Str |
409+
| oracledb.process | The operating system process ID (PID) associated with a session. | Any Str |
410+
| oracledb.schemaname | Oracle schema under which SQL statements are being executed | Any Str |
411+
| oracledb.program | Name of the client program or tool that initiated the Oracle database session. | Any Str |
412+
| oracledb.module | Logical module name of the client application that initiated a query or session. | Any Str |
413+
| oracledb.status | Execution state or result of a database query or session. | Any Str |
414+
| oracledb.state | Current state of the query or the session executing it. | Any Str |
415+
| oracledb.wait_class | The category of wait events a query or session is currently experiencing in Oracle Database. | Any Str |
416+
| oracledb.event | The specific wait event that a query or session is currently experiencing. | Any Str |
417+
| oracledb.object_name | Name of the database object that a query is accessing. | Any Str |
418+
| oracledb.object_type | Type of the database object that a query is accessing. | Any Str |
419+
| oracledb.osuser | Name of the operating system user that initiated or is running the Oracle database session. | Any Str |
420+
| oracledb.duration_sec | Total time taken by a database query to execute. | Any Double |
421+
422+
### db.server.top_query
423+
424+
top query
425+
426+
#### Attributes
427+
428+
| Name | Description | Values |
429+
| ---- | ----------- | ------ |
430+
| db.system.name | The database management system (DBMS) product as identified by the client instrumentation. | Any Str |
431+
| db.server.name | The name of the server hosting the database. | Any Str |
432+
| db.query.text | The text of the database query being executed. | Any Str |
433+
| oracledb.query_plan | The query execution plan used by the SQL Server. | Any Str |
434+
| oracledb.sql_id | The SQL ID of the query. | Any Str |
435+
| oracledb.child_number | The child number of the query. | Any Str |
436+
| oracledb.application_wait_time | The total time (in seconds) a query spent waiting on the application before it could proceed with execution (reporting delta). | Any Double |
437+
| oracledb.buffer_gets | Number of logical reads (i.e., buffer cache accesses) performed by a query (reporting delta). | Any Int |
438+
| oracledb.cluster_wait_time | Total time (in seconds) that a query waited due to Oracle Real Application Clusters (RAC) coordination (reporting delta). | Any Double |
439+
| oracledb.concurrency_wait_time | Total time (in seconds) a query spent waiting on concurrency-related events (reporting delta). | Any Double |
440+
| oracledb.cpu_time | Total time (in seconds) that the CPU spent actively processing a query, excluding time spent waiting (reporting delta). | Any Double |
441+
| oracledb.direct_reads | The number of direct path reads performed by a query — i.e., data blocks read directly from disk into the session’s memory (reporting delta). | Any Int |
442+
| oracledb.direct_writes | The number of direct path write operations, where data is written directly to disk from user memory (reporting delta). | Any Int |
443+
| oracledb.disk_reads | The number of physical reads a query performs — that is, the number of data blocks read from disk (reporting delta). | Any Int |
444+
| oracledb.elapsed_time | The total time (in seconds) taken by a query from start to finish, including CPU time and all types of waits (reporting delta). | Any Double |
445+
| oracledb.executions | The number of times a specific SQL query has been executed (reporting delta). | Any Int |
446+
| oracledb.physical_read_bytes | The total number of bytes read from disk by a query (reporting delta). | Any Int |
447+
| oracledb.physical_read_requests | The number of physical I/O read operations performed by a query (reporting delta). | Any Int |
448+
| oracledb.physical_write_bytes | The total number of bytes written to disk by a query (reporting delta). | Any Int |
449+
| oracledb.physical_write_requests | The number of times a query requested to write data to disk (reporting delta). | Any Int |
450+
| oracledb.rows_processed | The total number of rows that a query has read, returned, or affected during its execution (reporting delta). | Any Int |
451+
| oracledb.user_io_wait_time | The total time (in seconds) a query spent waiting for user I/O operations—such as reading or writing data to disk or network file systems (reporting delta). | Any Double |
452+
372453
## Resource Attributes
373454
374455
| Name | Description | Values | Enabled |
375456
| ---- | ----------- | ------ | ------- |
457+
| host.name | The host name of Oracle Server | Any Str | true |
376458
| oracledb.instance.name | The name of the instance that data is coming from. | Any Str | true |

receiver/oracledbreceiver/factory.go

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ import (
1111
"strconv"
1212
"time"
1313

14+
lru "github.com/hashicorp/golang-lru/v2"
1415
go_ora "github.com/sijms/go-ora/v2"
1516
"go.opentelemetry.io/collector/component"
1617
"go.opentelemetry.io/collector/consumer"
1718
"go.opentelemetry.io/collector/receiver"
19+
"go.opentelemetry.io/collector/scraper"
1820
"go.opentelemetry.io/collector/scraper/scraperhelper"
21+
"go.uber.org/zap"
1922

2023
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/oracledbreceiver/internal/metadata"
2124
)
@@ -27,7 +30,10 @@ func NewFactory() receiver.Factory {
2730
createDefaultConfig,
2831
receiver.WithMetrics(createReceiverFunc(func(dataSourceName string) (*sql.DB, error) {
2932
return sql.Open("oracle", dataSourceName)
30-
}, newDbClient), metadata.MetricsStability))
33+
}, newDbClient), metadata.MetricsStability),
34+
receiver.WithLogs(createLogsReceiverFunc(func(dataSourceName string) (*sql.DB, error) {
35+
return sql.Open("oracle", dataSourceName)
36+
}, newDbClient), metadata.LogsStability))
3137
}
3238

3339
func createDefaultConfig() component.Config {
@@ -37,6 +43,12 @@ func createDefaultConfig() component.Config {
3743
return &Config{
3844
ControllerConfig: cfg,
3945
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
46+
LogsBuilderConfig: metadata.DefaultLogsBuilderConfig(),
47+
TopQueryCollection: TopQueryCollection{
48+
MaxQuerySampleCount: 1000,
49+
TopQueryCount: 200,
50+
QueryCacheSize: 5000,
51+
},
4052
}
4153
}
4254

@@ -56,10 +68,14 @@ func createReceiverFunc(sqlOpenerFunc sqlOpenerFunc, clientProviderFunc clientPr
5668
if err != nil {
5769
return nil, err
5870
}
71+
hostName, hostNameErr := getHostName(getDataSource(*sqlCfg))
72+
if hostNameErr != nil {
73+
return nil, hostNameErr
74+
}
5975

6076
mp, err := newScraper(metricsBuilder, sqlCfg.MetricsBuilderConfig, sqlCfg.ControllerConfig, settings.Logger, func() (*sql.DB, error) {
6177
return sqlOpenerFunc(getDataSource(*sqlCfg))
62-
}, clientProviderFunc, instanceName)
78+
}, clientProviderFunc, instanceName, hostName)
6379
if err != nil {
6480
return nil, err
6581
}
@@ -74,6 +90,56 @@ func createReceiverFunc(sqlOpenerFunc sqlOpenerFunc, clientProviderFunc clientPr
7490
}
7591
}
7692

93+
func createLogsReceiverFunc(sqlOpenerFunc sqlOpenerFunc, clientProviderFunc clientProviderFunc) receiver.CreateLogsFunc {
94+
return func(
95+
_ context.Context,
96+
settings receiver.Settings,
97+
cfg component.Config,
98+
logsConsumer consumer.Logs,
99+
) (receiver.Logs, error) {
100+
sqlCfg := cfg.(*Config)
101+
102+
logsBuilder := metadata.NewLogsBuilder(sqlCfg.LogsBuilderConfig, settings)
103+
104+
instanceName, err := getInstanceName(getDataSource(*sqlCfg))
105+
if err != nil {
106+
return nil, err
107+
}
108+
109+
hostName, hostNameErr := getHostName(getDataSource(*sqlCfg))
110+
if hostNameErr != nil {
111+
return nil, hostNameErr
112+
}
113+
114+
cacheSize := sqlCfg.QueryCacheSize
115+
metricCache, err := lru.New[string, map[string]int64](cacheSize)
116+
if err != nil {
117+
settings.Logger.Error("Failed to create LRU cache, skipping the current scraper", zap.Error(err))
118+
return nil, err
119+
}
120+
121+
mp, err := newLogsScraper(logsBuilder, sqlCfg.LogsBuilderConfig, sqlCfg.ControllerConfig, settings.Logger, func() (*sql.DB, error) {
122+
return sqlOpenerFunc(getDataSource(*sqlCfg))
123+
}, clientProviderFunc, instanceName, metricCache, sqlCfg.TopQueryCollection, hostName)
124+
if err != nil {
125+
return nil, err
126+
}
127+
128+
f := scraper.NewFactory(metadata.Type, nil,
129+
scraper.WithLogs(func(context.Context, scraper.Settings, component.Config) (scraper.Logs, error) {
130+
return mp, nil
131+
}, component.StabilityLevelAlpha))
132+
opt := scraperhelper.AddFactoryWithConfig(f, nil)
133+
134+
return scraperhelper.NewLogsController(
135+
&sqlCfg.ControllerConfig,
136+
settings,
137+
logsConsumer,
138+
opt,
139+
)
140+
}
141+
}
142+
77143
func getDataSource(cfg Config) string {
78144
if cfg.DataSource != "" {
79145
return cfg.DataSource
@@ -95,3 +161,11 @@ func getInstanceName(datasource string) (string, error) {
95161
instanceName := datasourceURL.Host + datasourceURL.Path
96162
return instanceName, nil
97163
}
164+
165+
func getHostName(datasource string) (string, error) {
166+
datasourceURL, err := url.Parse(datasource)
167+
if err != nil {
168+
return "", err
169+
}
170+
return datasourceURL.Host, nil
171+
}

receiver/oracledbreceiver/factory_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ func TestNewFactory(t *testing.T) {
3030
consumertest.NewNop(),
3131
)
3232
require.NoError(t, err)
33+
34+
config := factory.CreateDefaultConfig().(*Config)
35+
_, logsErr := factory.CreateLogs(
36+
context.Background(),
37+
receiver.Settings{
38+
ID: component.NewID(metadata.Type),
39+
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
40+
},
41+
config,
42+
43+
consumertest.NewNop(),
44+
)
45+
require.NoError(t, logsErr)
3346
}
3447

3548
func TestGetInstanceName(t *testing.T) {

receiver/oracledbreceiver/fake_db_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type fakeDbClient struct {
1313
RequestCounter int
1414
}
1515

16-
func (c *fakeDbClient) metricRows(context.Context) ([]metricRow, error) {
16+
func (c *fakeDbClient) metricRows(context.Context, ...any) ([]metricRow, error) {
1717
if c.Err != nil {
1818
return nil, c.Err
1919
}

receiver/oracledbreceiver/generated_component_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)