diff --git a/.chloggen/top-query-collection-interval.yaml b/.chloggen/top-query-collection-interval.yaml new file mode 100644 index 0000000000000..e802204f2f10a --- /dev/null +++ b/.chloggen/top-query-collection-interval.yaml @@ -0,0 +1,29 @@ +# Use this changelog template to create an entry for release notes. + +# 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: sqlserverreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add configuration option `top_query_collection.collection_interval` for top query collection to make the collection less frequent. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [40002] + +# (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: | + - This change only applies to the `top_query_collection` feature. + - The default value is `60s` + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] diff --git a/receiver/sqlserverreceiver/README.md b/receiver/sqlserverreceiver/README.md index 64952648bd1fb..75cede968d779 100644 --- a/receiver/sqlserverreceiver/README.md +++ b/receiver/sqlserverreceiver/README.md @@ -47,6 +47,13 @@ Top-Query collection specific options (only useful when top-query collection are - 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. - `max_query_sample_count` (optional, example = `5000`, default = `1000`): The maximum number of records to fetch in a single run. - `top_query_count`: (optional, example = `100`, default = `200`): The maximum number of active queries to report (to the next consumer) in a single run. +- `collection_interval`: (optional, default = `60s`): The interval at which top queries should be emitted by this receiver. + - This value can only guarantee that the top queries are collected at most once in this interval. + - For instance, you have global `collection_interval` as `10s` and `top_query_collection.collection_interval` as `60s`. + - In this case, the default receiver scraper will still try to run in every 10 seconds. + - However, the top queries collection will only run after 60 seconds have passed since the last collection. + - For instance, you have global `collection_interval` as `10s` and `top_query_collection.collection_interval` as `5s`. + - In this case, `top_query_collection.collection_internal` will make no effects to the collection - `enabled`: (optional, default = `false`): Enable collection of top queries. - 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. diff --git a/receiver/sqlserverreceiver/config.go b/receiver/sqlserverreceiver/config.go index 8aabe607d6628..7bb4e02daa219 100644 --- a/receiver/sqlserverreceiver/config.go +++ b/receiver/sqlserverreceiver/config.go @@ -5,6 +5,7 @@ package sqlserverreceiver // import "github.com/open-telemetry/opentelemetry-col import ( "errors" + "time" "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/scraper/scraperhelper" @@ -23,10 +24,11 @@ type TopQueryCollection struct { // The query statement will also be reported, hence, it is not ideal to send it as a metric. Hence // we are reporting them as logs. // The `N` is configured via `TopQueryCount` - Enabled bool `mapstructure:"enabled"` - LookbackTime uint `mapstructure:"lookback_time"` - MaxQuerySampleCount uint `mapstructure:"max_query_sample_count"` - TopQueryCount uint `mapstructure:"top_query_count"` + Enabled bool `mapstructure:"enabled"` + LookbackTime uint `mapstructure:"lookback_time"` + MaxQuerySampleCount uint `mapstructure:"max_query_sample_count"` + TopQueryCount uint `mapstructure:"top_query_count"` + CollectionInterval time.Duration `mapstructure:"collection_interval"` } // Config defines configuration for a sqlserver receiver. @@ -72,6 +74,10 @@ func (cfg *Config) Validate() error { return errors.New("`top_query_count` must be less than or equal to `max_query_sample_count`") } + if cfg.TopQueryCollection.CollectionInterval < 0 { + return errors.New("`top_query_collection.collection_interval` must not be less than 0") + } + cfg.isDirectDBConnectionEnabled, err = directDBConnectionEnabled(cfg) return err diff --git a/receiver/sqlserverreceiver/factory.go b/receiver/sqlserverreceiver/factory.go index 3fff497ad4e42..d42e2dc36bea6 100644 --- a/receiver/sqlserverreceiver/factory.go +++ b/receiver/sqlserverreceiver/factory.go @@ -61,6 +61,7 @@ func createDefaultConfig() component.Config { LookbackTime: uint(2 * cfg.CollectionInterval / time.Second), MaxQuerySampleCount: 1000, TopQueryCount: 200, + CollectionInterval: time.Minute, }, } } diff --git a/receiver/sqlserverreceiver/factory_test.go b/receiver/sqlserverreceiver/factory_test.go index 53db9b0392fcd..c100b2bc91f53 100644 --- a/receiver/sqlserverreceiver/factory_test.go +++ b/receiver/sqlserverreceiver/factory_test.go @@ -48,6 +48,7 @@ func TestFactory(t *testing.T) { LookbackTime: uint(2 * 10), MaxQuerySampleCount: 1000, TopQueryCount: 200, + CollectionInterval: time.Minute, }, QuerySample: QuerySample{ Enabled: false, diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index a6325b863ac48..01b70b6b9ad17 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -37,19 +37,20 @@ const ( ) type sqlServerScraperHelper struct { - id component.ID - config *Config - sqlQuery string - instanceName string - clientProviderFunc sqlquery.ClientProviderFunc - dbProviderFunc sqlquery.DbProviderFunc - logger *zap.Logger - telemetry sqlquery.TelemetryConfig - client sqlquery.DbClient - db *sql.DB - mb *metadata.MetricsBuilder - lb *metadata.LogsBuilder - cache *lru.Cache[string, int64] + id component.ID + config *Config + sqlQuery string + instanceName string + clientProviderFunc sqlquery.ClientProviderFunc + dbProviderFunc sqlquery.DbProviderFunc + logger *zap.Logger + telemetry sqlquery.TelemetryConfig + client sqlquery.DbClient + db *sql.DB + mb *metadata.MetricsBuilder + lb *metadata.LogsBuilder + cache *lru.Cache[string, int64] + lastExecutionTimestamp time.Time } var ( @@ -67,16 +68,17 @@ func newSQLServerScraper(id component.ID, cache *lru.Cache[string, int64], ) *sqlServerScraperHelper { return &sqlServerScraperHelper{ - id: id, - config: cfg, - sqlQuery: query, - logger: params.Logger, - telemetry: telemetry, - dbProviderFunc: dbProviderFunc, - clientProviderFunc: clientProviderFunc, - mb: metadata.NewMetricsBuilder(cfg.MetricsBuilderConfig, params), - lb: metadata.NewLogsBuilder(cfg.LogsBuilderConfig, params), - cache: cache, + id: id, + config: cfg, + sqlQuery: query, + logger: params.Logger, + telemetry: telemetry, + dbProviderFunc: dbProviderFunc, + clientProviderFunc: clientProviderFunc, + mb: metadata.NewMetricsBuilder(cfg.MetricsBuilderConfig, params), + lb: metadata.NewLogsBuilder(cfg.LogsBuilderConfig, params), + cache: cache, + lastExecutionTimestamp: time.Unix(0, 0), } } @@ -121,6 +123,10 @@ func (s *sqlServerScraperHelper) ScrapeLogs(ctx context.Context) (plog.Logs, err var resources pcommon.Resource switch s.sqlQuery { case getSQLServerQueryTextAndPlanQuery(): + if s.lastExecutionTimestamp.Add(s.config.TopQueryCollection.CollectionInterval).After(time.Now()) { + s.logger.Debug("Skipping the collection of top queries because the current time has not yet exceeded the last execution time plus the specified collection interval") + return plog.NewLogs(), nil + } resources, err = s.recordDatabaseQueryTextAndPlan(ctx, s.config.TopQueryCount) case getSQLServerQuerySamplesQuery(): resources, err = s.recordDatabaseSampleQuery(ctx) @@ -596,7 +602,9 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont sort.Slice(totalElapsedTimeDiffsMicrosecond, func(i, j int) bool { return totalElapsedTimeDiffsMicrosecond[i] > totalElapsedTimeDiffsMicrosecond[j] }) resourcesAdded := false - timestamp := pcommon.NewTimestampFromTime(time.Now()) + now := time.Now() + timestamp := pcommon.NewTimestampFromTime(now) + s.lastExecutionTimestamp = now for i, row := range rows { // skipping the rest of the rows as totalElapsedTimeDiffs is sorted in descending order if totalElapsedTimeDiffsMicrosecond[i] == 0 { diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 0f34788c437f5..b9f75a1bff0cf 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -346,6 +346,7 @@ func TestQueryTextAndPlanQuery(t *testing.T) { configureAllScraperMetrics(cfg, false) cfg.TopQueryCollection.Enabled = true + cfg.TopQueryCollection.CollectionInterval = cfg.ControllerConfig.CollectionInterval scrapers := setupSQLServerLogsScrapers(receivertest.NewNopSettings(metadata.Type), cfg) assert.NotNil(t, scrapers)