Skip to content

Commit 01eb0b0

Browse files
authored
Merge branch 'main' into add-new-oracledb-metrics
2 parents f2f766e + 9b76d08 commit 01eb0b0

34 files changed

+1772
-69
lines changed

.chloggen/sqlserver-top-query.yaml

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: sqlserverreceiver
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
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: [36462]
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+
Added top query (most CPU time consumed) collection. The query will gather the queries took most of the time during the last
20+
query interval and report related metrics. The number of queries can be configured. This will enable user to have better
21+
understanding on what is going on with the database. This enhancement empowers users to not only monitor but also actively
22+
manage and optimize their MSSQL database performance based on real usage patterns.
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]

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ receiver/solacereceiver/ @open-telemetry
298298
receiver/splunkenterprisereceiver/ @open-telemetry/collector-contrib-approvers @shalper2 @MovieStoreGuy @greatestusername
299299
receiver/splunkhecreceiver/ @open-telemetry/collector-contrib-approvers @atoulme
300300
receiver/sqlqueryreceiver/ @open-telemetry/collector-contrib-approvers @dmitryax @crobert-1
301-
receiver/sqlserverreceiver/ @open-telemetry/collector-contrib-approvers @StefanKurek
301+
receiver/sqlserverreceiver/ @open-telemetry/collector-contrib-approvers @StefanKurek @sincejune
302302
receiver/sshcheckreceiver/ @open-telemetry/collector-contrib-approvers @nslaughter
303303
receiver/statsdreceiver/ @open-telemetry/collector-contrib-approvers @jmacd @dmitryax
304304
receiver/syslogreceiver/ @open-telemetry/collector-contrib-approvers @djaglowski @andrzej-stencel

cmd/githubgen/allowlist.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
abhishek-at-cloudwerx
2-
AkhigbeEromo
32
cemdk
43
driverpt
54
dsimil

receiver/sqlserverreceiver/README.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
<!-- status autogenerated section -->
44
| Status | |
55
| ------------- |-----------|
6-
| Stability | [beta]: metrics |
6+
| Stability | [development]: logs |
7+
| | [beta]: 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%2Fsqlserver%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fsqlserver) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fsqlserver%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fsqlserver) |
9-
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@StefanKurek](https://www.github.com/StefanKurek) \| Seeking more code owners! |
10+
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@StefanKurek](https://www.github.com/StefanKurek), [@sincejune](https://www.github.com/sincejune) \| Seeking more code owners! |
1011

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

15-
The `sqlserver` receiver grabs metrics about a Microsoft SQL Server instance. The receiver works by either using the
17+
The `sqlserver` receiver grabs metrics/logs about a Microsoft SQL Server instance. The receiver works by either using the
1618
Windows Performance Counters, or by directly connecting to the instance and querying it. Windows Performance Counters
1719
are only available when running on Windows.
1820

@@ -36,6 +38,14 @@ Windows-specific options:
3638
- `computer_name` (optional): The computer name identifies the SQL Server name or IP address of the computer being monitored.
3739
If specified, `instance_name` is also required to be defined. This option is ignored in non-Windows environments.
3840

41+
Top-Query collection specific options (only useful when top-query collection are enabled):
42+
- `lookback_time` (optional, example = `60`, default = `2 * collection_interval`): The time window (in second) in which to query for top queries.
43+
- Queries that were finished execution outside the lookback window are not included in the collection. Increasing the lookback window (in seconds) will be useful for capturing long-running queries.
44+
- `max_query_sample_count` (optional, example = `5000`, default = `1000`): The maximum number of records to fetch in a single run.
45+
- `top_query_count`: (optional, example = `100`, default = `200`): The maximum number of active queries to report (to the next consumer) in a single run.
46+
- `enabled`: (optional, default = `false`): Enable collection of top queries.
47+
- e.g. `sqlserver` receiver will fetch 1000 (value: `max_query_sample_count`) queries from database and report the top 200 (value: `top_query_count`) which used the most CPU time.
48+
3949
Example:
4050

4151
```yaml
@@ -50,7 +60,7 @@ Example:
5060
port: 1433
5161
```
5262
53-
When a named instance is used on Windows, a computer name and a instance name must be specified.
63+
When a named instance is used on Windows, a computer name and an instance name must be specified.
5464
Example with named instance:
5565
5666
```yaml
@@ -68,10 +78,31 @@ Example with named instance:
6878
6979
The full list of settings exposed for this receiver are documented in [config.go](./config.go) with detailed sample configurations in [testdata/config.yaml](./testdata/config.yaml).
7080
81+
Top query collection enabled:
82+
```yaml
83+
receivers:
84+
sqlserver:
85+
collection_interval: 5s
86+
username: sa
87+
password: securepassword
88+
server: 0.0.0.0
89+
port: 1433
90+
top_query_collection:
91+
enabled: true
92+
lookback_time: 60
93+
max_query_sample_count: 1000
94+
top_query_count: 200
95+
96+
```
7197
## Metrics
7298
7399
Details about the metrics produced by this receiver can be found in [documentation.md](./documentation.md)
74100
101+
102+
## Logs
103+
104+
Details about the logs produced by this receiver can be found in [logs-documentation.md](./logs-documentation.md)
105+
75106
## Known issues
76107
SQL Server docker users may run into an issue that the collector fails to parse certificate from server due to `x509: negative serial number`. That's because we adopted Go `1.23` starting from contrib `v0.121.0`:
77108
> Before Go 1.23, ParseCertificate accepted certificates with negative serial numbers.

receiver/sqlserverreceiver/config.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,28 @@ import (
1212
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata"
1313
)
1414

15+
type TopQueryCollection struct {
16+
// Enabled enables the collection of the top queries by the execution time.
17+
// It will collect the top N queries based on totalElapsedTimeDiffs during the last collection interval.
18+
// The query statement will also be reported, hence, it is not ideal to send it as a metric. Hence
19+
// we are reporting them as logs.
20+
// The `N` is configured via `TopQueryCount`
21+
Enabled bool `mapstructure:"enabled"`
22+
LookbackTime uint `mapstructure:"lookback_time"`
23+
MaxQuerySampleCount uint `mapstructure:"max_query_sample_count"`
24+
TopQueryCount uint `mapstructure:"top_query_count"`
25+
}
26+
1527
// Config defines configuration for a sqlserver receiver.
1628
type Config struct {
1729
scraperhelper.ControllerConfig `mapstructure:",squash"`
1830
metadata.MetricsBuilderConfig `mapstructure:",squash"`
31+
// EnableTopQueryCollection enables the collection of the top queries by the execution time.
32+
// It will collect the top N queries based on totalElapsedTimeDiffs during the last collection interval.
33+
// The query statement will also be reported, hence, it is not ideal to send it as a metric. Hence
34+
// we are reporting them as logs.
35+
// The `N` is configured via `TopQueryCount`
36+
TopQueryCollection `mapstructure:"top_query_collection"`
1937

2038
InstanceName string `mapstructure:"instance_name"`
2139
ComputerName string `mapstructure:"computer_name"`
@@ -33,6 +51,14 @@ func (cfg *Config) Validate() error {
3351
return err
3452
}
3553

54+
if cfg.TopQueryCollection.MaxQuerySampleCount > 10000 {
55+
return errors.New("`max_query_sample_count` must be between 0 and 10000")
56+
}
57+
58+
if cfg.TopQueryCount > cfg.TopQueryCollection.MaxQuerySampleCount {
59+
return errors.New("`top_query_count` must be less than or equal to `max_query_sample_count`")
60+
}
61+
3662
if !directDBConnectionEnabled(cfg) {
3763
if cfg.Server != "" || cfg.Username != "" || string(cfg.Password) != "" {
3864
return errors.New("Found one or more of the following configuration options set: [server, port, username, password]. " +

receiver/sqlserverreceiver/config_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,29 @@ func TestValidate(t *testing.T) {
6464
},
6565
expectedSuccess: true,
6666
},
67+
{
68+
desc: "config with invalid MaxQuerySampleCount value",
69+
cfg: &Config{
70+
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
71+
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
72+
TopQueryCollection: TopQueryCollection{
73+
MaxQuerySampleCount: 100000,
74+
},
75+
},
76+
expectedSuccess: false,
77+
},
78+
{
79+
desc: "config with invalid TopQueryCount value",
80+
cfg: &Config{
81+
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
82+
ControllerConfig: scraperhelper.NewDefaultControllerConfig(),
83+
TopQueryCollection: TopQueryCollection{
84+
MaxQuerySampleCount: 100,
85+
TopQueryCount: 200000,
86+
},
87+
},
88+
expectedSuccess: false,
89+
},
6790
}
6891

6992
for _, tc := range testCases {
@@ -122,6 +145,10 @@ func TestLoadConfig(t *testing.T) {
122145
}
123146
expected.ComputerName = "CustomServer"
124147
expected.InstanceName = "CustomInstance"
148+
expected.Enabled = true
149+
expected.LookbackTime = 60
150+
expected.TopQueryCount = 200
151+
expected.TopQueryCollection.MaxQuerySampleCount = 1000
125152

126153
sub, err := cm.Sub("sqlserver/named")
127154
require.NoError(t, err)

receiver/sqlserverreceiver/factory.go

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,44 @@
44
package sqlserverreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver"
55

66
import (
7+
"context"
78
"database/sql"
89
"errors"
910
"fmt"
1011
"time"
1112

13+
lru "github.com/hashicorp/golang-lru/v2"
1214
"go.opentelemetry.io/collector/component"
1315
"go.opentelemetry.io/collector/receiver"
1416
"go.opentelemetry.io/collector/scraper"
1517
"go.opentelemetry.io/collector/scraper/scraperhelper"
18+
"go.uber.org/zap"
1619

1720
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/sqlquery"
1821
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata"
1922
)
2023

2124
var errConfigNotSQLServer = errors.New("config was not a sqlserver receiver config")
2225

26+
// newCache creates a new cache with the given size.
27+
// If the size is less or equal to 0, it will be set to 1.
28+
// It will never return an error.
29+
func newCache(size int) *lru.Cache[string, int64] {
30+
if size <= 0 {
31+
size = 1
32+
}
33+
// lru will only returns error when the size is less than 0
34+
cache, _ := lru.New[string, int64](size)
35+
return cache
36+
}
37+
2338
// NewFactory creates a factory for SQL Server receiver.
2439
func NewFactory() receiver.Factory {
2540
return receiver.NewFactory(
2641
metadata.Type,
2742
createDefaultConfig,
28-
receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability))
43+
receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability),
44+
receiver.WithLogs(createLogsReceiver, metadata.LogsStability))
2945
}
3046

3147
func createDefaultConfig() component.Config {
@@ -34,6 +50,12 @@ func createDefaultConfig() component.Config {
3450
return &Config{
3551
ControllerConfig: cfg,
3652
MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(),
53+
TopQueryCollection: TopQueryCollection{
54+
Enabled: false,
55+
LookbackTime: uint(2 * cfg.CollectionInterval / time.Second),
56+
MaxQuerySampleCount: 1000,
57+
TopQueryCount: 200,
58+
},
3759
}
3860
}
3961

@@ -63,6 +85,22 @@ func setupQueries(cfg *Config) []string {
6385
return queries
6486
}
6587

88+
func setupLogQueries(cfg *Config) ([]string, []error) {
89+
var queries []string
90+
var errs []error
91+
92+
if cfg.Enabled {
93+
q, err := getSQLServerQueryTextAndPlanQuery(cfg.InstanceName, cfg.MaxQuerySampleCount, cfg.LookbackTime)
94+
if err != nil {
95+
errs = append(errs, err)
96+
} else {
97+
queries = append(queries, q)
98+
}
99+
}
100+
101+
return queries, errs
102+
}
103+
66104
func directDBConnectionEnabled(config *Config) bool {
67105
return config.Server != "" &&
68106
config.Username != "" &&
@@ -97,12 +135,71 @@ func setupSQLServerScrapers(params receiver.Settings, cfg *Config) []*sqlServerS
97135
for i, query := range queries {
98136
id := component.NewIDWithName(metadata.Type, fmt.Sprintf("query-%d: %s", i, query))
99137

138+
// lru only returns error when the size is less than 0
139+
cache := newCache(1)
140+
141+
sqlServerScraper := newSQLServerScraper(id, query,
142+
sqlquery.TelemetryConfig{},
143+
dbProviderFunc,
144+
sqlquery.NewDbClient,
145+
params,
146+
cfg,
147+
cache)
148+
149+
scrapers = append(scrapers, sqlServerScraper)
150+
}
151+
152+
return scrapers
153+
}
154+
155+
// SQL Server scraper creation is split out into a separate method for the sake of testing.
156+
func setupSQLServerLogsScrapers(params receiver.Settings, cfg *Config) []*sqlServerScraperHelper {
157+
if !directDBConnectionEnabled(cfg) {
158+
params.Logger.Info("No direct connection will be made to the SQL Server: Configuration doesn't include some options.")
159+
return nil
160+
}
161+
162+
queries, errs := setupLogQueries(cfg)
163+
if len(errs) > 0 {
164+
params.Logger.Error("Failed to template queries in SQLServer receiver: Configuration might not be correct.", zap.Error(errors.Join(errs...)))
165+
return nil
166+
}
167+
168+
if len(queries) == 0 {
169+
params.Logger.Info("No direct connection will be made to the SQL Server: No logs are enabled requiring it.")
170+
return nil
171+
}
172+
173+
queryTextAndPlanQuery, err := getSQLServerQueryTextAndPlanQuery(cfg.InstanceName, cfg.MaxQuerySampleCount, cfg.LookbackTime)
174+
if err != nil {
175+
params.Logger.Error("Failed to template needed queries in SQLServer receiver: Configuration might not be correct.", zap.Error(err))
176+
return nil
177+
}
178+
179+
// TODO: Test if this needs to be re-defined for each scraper
180+
// This should be tested when there is more than one query being made.
181+
dbProviderFunc := func() (*sql.DB, error) {
182+
return sql.Open("sqlserver", getDBConnectionString(cfg))
183+
}
184+
185+
var scrapers []*sqlServerScraperHelper
186+
for i, query := range queries {
187+
id := component.NewIDWithName(metadata.Type, fmt.Sprintf("logs-query-%d: %s", i, query))
188+
189+
cache := newCache(1)
190+
191+
if query == queryTextAndPlanQuery {
192+
// we have 8 metrics in this query and multiple 2 to allow to cache more queries.
193+
cache = newCache(int(cfg.MaxQuerySampleCount * 8 * 2))
194+
}
195+
100196
sqlServerScraper := newSQLServerScraper(id, query,
101197
sqlquery.TelemetryConfig{},
102198
dbProviderFunc,
103199
sqlquery.NewDbClient,
104200
params,
105-
cfg)
201+
cfg,
202+
cache)
106203

107204
scrapers = append(scrapers, sqlServerScraper)
108205
}
@@ -132,6 +229,32 @@ func setupScrapers(params receiver.Settings, cfg *Config) ([]scraperhelper.Contr
132229
return opts, nil
133230
}
134231

232+
// Note: This method will fail silently if there is no work to do. This is an acceptable use case
233+
// as this receiver can still get information on Windows from performance counters without a direct
234+
// connection. Messages will be logged at the INFO level in such cases.
235+
func setupLogsScrapers(params receiver.Settings, cfg *Config) ([]scraperhelper.ControllerOption, error) {
236+
sqlServerScrapers := setupSQLServerLogsScrapers(params, cfg)
237+
238+
var opts []scraperhelper.ControllerOption
239+
for _, sqlScraper := range sqlServerScrapers {
240+
s, err := scraper.NewLogs(sqlScraper.ScrapeLogs,
241+
scraper.WithStart(sqlScraper.Start),
242+
scraper.WithShutdown(sqlScraper.Shutdown))
243+
if err != nil {
244+
return nil, err
245+
}
246+
247+
opt := scraperhelper.AddFactoryWithConfig(
248+
scraper.NewFactory(metadata.Type, nil,
249+
scraper.WithLogs(func(context.Context, scraper.Settings, component.Config) (scraper.Logs, error) {
250+
return s, nil
251+
}, component.StabilityLevelAlpha)), nil)
252+
opts = append(opts, opt)
253+
}
254+
255+
return opts, nil
256+
}
257+
135258
func isDatabaseIOQueryEnabled(metrics *metadata.MetricsConfig) bool {
136259
if metrics.SqlserverDatabaseLatency.Enabled ||
137260
metrics.SqlserverDatabaseOperations.Enabled ||

0 commit comments

Comments
 (0)