From 71759f160c0e2fd58a2d31c3e05000e2fcaca781 Mon Sep 17 00:00:00 2001 From: cjk Date: Mon, 24 Nov 2025 10:09:51 +0000 Subject: [PATCH 01/49] Adding the query and tracking of stored procedures to events --- receiver/sqlserverreceiver/documentation.md | 6 ++ receiver/sqlserverreceiver/factory.go | 2 +- .../generated_package_test.go | 3 +- .../internal/metadata/generated_logs.go | 19 ++-- .../internal/metadata/generated_logs_test.go | 27 +++++- receiver/sqlserverreceiver/metadata.yaml | 18 +++- receiver/sqlserverreceiver/scraper.go | 55 +++++++---- .../templates/dbQueryAndTextQuery.tmpl | 74 ++++++++------ .../templates/sqlServerQuerySample.tmpl | 97 ++++++++++--------- 9 files changed, 193 insertions(+), 108 deletions(-) diff --git a/receiver/sqlserverreceiver/documentation.md b/receiver/sqlserverreceiver/documentation.md index a018477cf1617..75f691ead994d 100644 --- a/receiver/sqlserverreceiver/documentation.md +++ b/receiver/sqlserverreceiver/documentation.md @@ -606,6 +606,9 @@ query sample | sqlserver.wait_type | Type of wait encountered by the request. Empty if none. | Any Str | | sqlserver.writes | Number of writes performed by the query. | Any Int | | user.name | Login name associated with the SQL Server session. | Any Str | +| sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | +| sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | +| sqlserver.procedure_type | The type code of the stored procedure, if any. Typically 'P' | Any Str | ### db.server.top_query @@ -630,6 +633,9 @@ top query | server.address | The network address of the server hosting the database. | Any Str | | server.port | The port number on which the server is listening. | Any Int | | db.system.name | The database management system (DBMS) product as identified by the client instrumentation. | Any Str | +| sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | +| sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | +| sqlserver.procedure_type | The type code of the stored procedure, if any. Typically 'P' | Any Str | ## Resource Attributes diff --git a/receiver/sqlserverreceiver/factory.go b/receiver/sqlserverreceiver/factory.go index 572d45e2e2bb6..87499ad5c8864 100644 --- a/receiver/sqlserverreceiver/factory.go +++ b/receiver/sqlserverreceiver/factory.go @@ -57,7 +57,7 @@ func createDefaultConfig() component.Config { }, TopQueryCollection: TopQueryCollection{ MaxQuerySampleCount: 1000, - TopQueryCount: 200, + TopQueryCount: 250, CollectionInterval: time.Minute, }, } diff --git a/receiver/sqlserverreceiver/generated_package_test.go b/receiver/sqlserverreceiver/generated_package_test.go index ce3721511beef..86266f5f36641 100644 --- a/receiver/sqlserverreceiver/generated_package_test.go +++ b/receiver/sqlserverreceiver/generated_package_test.go @@ -3,9 +3,8 @@ package sqlserverreceiver import ( - "testing" - "go.uber.org/goleak" + "testing" ) func TestMain(m *testing.M) { diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go index 2224f758a2990..ae30196ca3b95 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go @@ -4,7 +4,6 @@ package metadata import ( "context" - "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/filter" "go.opentelemetry.io/collector/pdata/pcommon" @@ -18,7 +17,7 @@ type eventDbServerQuerySample struct { config EventConfig // event config provided by user. } -func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string) { +func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { if !e.config.Enabled { return } @@ -63,6 +62,9 @@ func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pc dp.Attributes().PutStr("sqlserver.wait_type", sqlserverWaitTypeAttributeValue) dp.Attributes().PutInt("sqlserver.writes", sqlserverWritesAttributeValue) dp.Attributes().PutStr("user.name", userNameAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_type", sqlserverProcedureTypeAttributeValue) } @@ -86,7 +88,7 @@ type eventDbServerTopQuery struct { config EventConfig // event config provided by user. } -func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string) { +func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { if !e.config.Enabled { return } @@ -113,6 +115,9 @@ func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcomm dp.Attributes().PutStr("server.address", serverAddressAttributeValue) dp.Attributes().PutInt("server.port", serverPortAttributeValue) dp.Attributes().PutStr("db.system.name", dbSystemNameAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_type", sqlserverProcedureTypeAttributeValue) } @@ -284,11 +289,11 @@ func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs { } // RecordDbServerQuerySampleEvent adds a log record of db.server.query_sample event. -func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string) { - lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue) +func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { + lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue, sqlserverProcedureTypeAttributeValue) } // RecordDbServerTopQueryEvent adds a log record of db.server.top_query event. -func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string) { - lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue) +func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { + lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue, sqlserverProcedureTypeAttributeValue) } diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go index 53531d2d36903..4b9270d807ae4 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go @@ -4,9 +4,6 @@ package metadata import ( "context" - "testing" - "time" - "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" @@ -14,6 +11,8 @@ import ( "go.opentelemetry.io/otel/trace" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" + "testing" + "time" ) type eventsTestDataSet int @@ -135,10 +134,10 @@ func TestLogsBuilder(t *testing.T) { allEventsCount := 0 allEventsCount++ - lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val") + lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val", "sqlserver.procedure_type-val") allEventsCount++ - lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val") + lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val", "sqlserver.procedure_type-val") rb := lb.NewResourceBuilder() rb.SetHostName("host.name-val") @@ -276,6 +275,15 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("user.name") assert.True(t, ok) assert.Equal(t, "user.name-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_id") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_id-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_type") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_type-val", attrVal.Str()) case "db.server.top_query": assert.False(t, validatedEvents["db.server.top_query"], "Found a duplicate in the events slice: db.server.top_query") validatedEvents["db.server.top_query"] = true @@ -328,6 +336,15 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("db.system.name") assert.True(t, ok) assert.Equal(t, "db.system.name-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_id") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_id-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_type") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_type-val", attrVal.Str()) } } }) diff --git a/receiver/sqlserverreceiver/metadata.yaml b/receiver/sqlserverreceiver/metadata.yaml index 3b1caab551324..f7935b97d873f 100644 --- a/receiver/sqlserverreceiver/metadata.yaml +++ b/receiver/sqlserverreceiver/metadata.yaml @@ -1,5 +1,5 @@ type: sqlserver - +#generated_package_name: sqlserverreceiver status: class: receiver stability: @@ -133,6 +133,16 @@ attributes: sqlserver.percent_complete: description: Percentage of work completed. type: double + # Stored Procedures + sqlserver.procedure_id: + description: The SQLServer ID of the stored procedure, if any + type: string + sqlserver.procedure_name: + description: The name of the stored procedure, if any + type: string + sqlserver.procedure_type: + description: The type code of the stored procedure, if any. Typically 'P' + type: string sqlserver.query_hash: description: Binary hash value calculated on the query and used to identify queries with similar logic, reported in the HEX format. type: string @@ -262,6 +272,9 @@ events: - sqlserver.wait_type - sqlserver.writes - user.name + - sqlserver.procedure_id + - sqlserver.procedure_name + - sqlserver.procedure_type db.server.top_query: enabled: false @@ -282,6 +295,9 @@ events: - server.address - server.port - db.system.name + - sqlserver.procedure_id + - sqlserver.procedure_name + - sqlserver.procedure_type metrics: sqlserver.batch.request.rate: diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index 861a8155bf358..ad2026f4cf3fd 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -151,7 +151,7 @@ func (s *sqlServerScraperHelper) ScrapeLogs(ctx context.Context) (plog.Logs, err 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) + resources, err = s.recordDatabaseQueryTextAndPlan(ctx, s.config.TopQueryCount, s.config.MaxQuerySampleCount) case getSQLServerQuerySamplesQuery(): resources, err = s.recordDatabaseSampleQuery(ctx) default: @@ -618,7 +618,7 @@ func (s *sqlServerScraperHelper) recordDatabaseWaitMetrics(ctx context.Context) return errors.Join(errs...) } -func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context, topQueryCount uint) (pcommon.Resource, error) { +func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context, topQueryCount, maxQuerySampleCount uint) (pcommon.Resource, error) { // Constants are the column names of the database status const ( executionCount = "execution_count" @@ -637,6 +637,11 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont totalWorkerTime = "total_worker_time" dbSystemNameVal = "microsoft.sql_server" + + // stored procedure columns + storedProcedureId = "procedure_id" + storedProcedureName = "procedure_name" + storedProcedureType = "procedure_type" ) resources := pcommon.NewResource() @@ -659,20 +664,20 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont for i, row := range rows { queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) + planId := row[storedProcedureId] elapsedTimeMicrosecond, err := strconv.ParseInt(row[totalElapsedTime], 10, 64) if err != nil { - s.logger.Info(fmt.Sprintf("sqlServerScraperHelper failed getting rows: %s", err)) + s.logger.Warn(fmt.Sprintf("sqlServerScraperHelper failed getting rows: %s", err)) errs = append(errs, err) } else { // we're trying to get the queries that used the most time. // caching the total elapsed time (in microsecond) and compare in the next scrape. - if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { + if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, planId, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { totalElapsedTimeDiffsMicrosecond[i] = diff } } } - // sort the rows based on the totalElapsedTimeDiffs in descending order, // only report first T(T=topQueryCount) rows. rows = sortRows(rows, totalElapsedTimeDiffsMicrosecond, topQueryCount) @@ -694,6 +699,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont // reporting human-readable query hash and query hash plan queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) + procId := row[storedProcedureId] queryTextVal := s.retrieveValue(row, queryText, &errs, func(row sqlquery.StringMap, columnName string) (any, error) { statement := row[columnName] @@ -707,25 +713,25 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) executionCountVal := s.retrieveValue(row, executionCount, &errs, retrieveInt) - cached, executionCountVal := s.cacheAndDiff(queryHashVal, queryPlanHashVal, executionCount, executionCountVal.(int64)) + cached, executionCountVal := s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, executionCount, executionCountVal.(int64)) if !cached { executionCountVal = int64(0) } logicalReadsVal := s.retrieveValue(row, logicalReads, &errs, retrieveInt) - cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalReads, logicalReadsVal.(int64)) + cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, logicalReads, logicalReadsVal.(int64)) if !cached { logicalReadsVal = int64(0) } logicalWritesVal := s.retrieveValue(row, logicalWrites, &errs, retrieveInt) - cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalWrites, logicalWritesVal.(int64)) + cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, logicalWrites, logicalWritesVal.(int64)) if !cached { logicalWritesVal = int64(0) } physicalReadsVal := s.retrieveValue(row, physicalReads, &errs, retrieveInt) - cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, physicalReads, physicalReadsVal.(int64)) + cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, physicalReads, physicalReadsVal.(int64)) if !cached { physicalReadsVal = int64(0) } @@ -735,19 +741,19 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) rowsReturnedVal := s.retrieveValue(row, rowsReturned, &errs, retrieveInt) - cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, rowsReturned, rowsReturnedVal.(int64)) + cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, rowsReturned, rowsReturnedVal.(int64)) if !cached { rowsReturnedVal = int64(0) } totalGrantVal := s.retrieveValue(row, totalGrant, &errs, retrieveInt) - cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalGrant, totalGrantVal.(int64)) + cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, totalGrant, totalGrantVal.(int64)) if !cached { totalGrantVal = int64(0) } totalWorkerTimeVal := s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt) - cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalWorkerTime, totalWorkerTimeVal.(int64)) + cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, totalWorkerTime, totalWorkerTimeVal.(int64)) totalWorkerTimeInSecVal := float64(0) if cached { totalWorkerTimeInSecVal = float64(totalWorkerTimeVal.(int64)) / 1_000_000 @@ -781,7 +787,11 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont totalGrantVal.(int64), s.config.Server, int64(s.config.Port), - dbSystemNameVal) + dbSystemNameVal, + row[storedProcedureId], + row[storedProcedureName], + row[storedProcedureType], + ) } return resources, errors.Join(errs...) } @@ -804,24 +814,28 @@ func (s *sqlServerScraperHelper) retrieveValue( // cacheAndDiff store row(in int) with query hash and query plan hash variables // (1) returns true if the key is cached before // (2) returns positive value if the value is larger than the cached value -func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, column string, val int64) (bool, int64) { +func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, procedureId, column string, val int64) (bool, int64) { if val < 0 { return false, 0 } key := queryHash + "-" + queryPlanHash + "-" + column - + if procedureId != "" { + key = procedureId + "-" + key + } cached, ok := s.cache.Get(key) if !ok { s.cache.Add(key, val) + // s.logger.Info("Adding key", zap.String("key", key), zap.Int64("val", val)) return false, val } if val > cached { s.cache.Add(key, val) + // s.logger.Info("Returning cached diff for ", zap.String("key", key), zap.Int64("val", val)) return true, val - cached } - + // s.logger.Info("Returning 0", zap.String("key", key), zap.Int64("val", val)) return true, 0 } @@ -929,6 +943,10 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) const waitTimeMillisecond = "wait_time" const waitType = "wait_type" const writes = "writes" + // stored procedure columns + const storedProcedureId = "procedure_id" + const storedProcedureName = "procedure_name" + const storedProcedureType = "procedure_type" rows, err := s.client.QueryRows( ctx, @@ -1022,7 +1040,9 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) } else { clientAddressVal = row[clientAddress] } - + if row[storedProcedureId] != "0" { + s.logger.Info("Stored proc data", zap.String("id", row[storedProcedureId]), zap.String("name", row[storedProcedureName]), zap.String("type", row[storedProcedureType])) + } s.lb.RecordDbServerQuerySampleEvent( contextFromQuery, timestamp, clientAddressVal, clientPortVal, @@ -1038,6 +1058,7 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) sessionIDVal, sessionStatusVal, totalElapsedTimeSecondVal, transactionIDVal, transactionIsolationLevelVal, waitResourceVal, waitTimeSecondVal, waitTypeVal, writesVal, usernameVal, + row[storedProcedureId], row[storedProcedureName], row[storedProcedureType], ) if !resourcesAdded { diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 75fed75c75fb5..9a62148a2db5a 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -1,31 +1,43 @@ -with qstats as ( - SELECT TOP(@topNValue) - REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], - HOST_NAME() AS [computer_name], - MAX(qs.plan_handle) AS query_plan_handle, - qs.query_hash AS query_hash, - qs.query_plan_hash AS query_plan_hash, - SUM(qs.execution_count) AS execution_count, - SUM(qs.total_elapsed_time) AS total_elapsed_time, - SUM(qs.total_worker_time) AS total_worker_time, - SUM(qs.total_logical_reads) AS total_logical_reads, - SUM(qs.total_physical_reads) AS total_physical_reads, - SUM(qs.total_logical_writes) AS total_logical_writes, - SUM(qs.total_rows) AS total_rows, - SUM(qs.total_grant_kb) as total_grant_kb - FROM sys.dm_exec_query_stats AS qs - WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @lookbackTime, GETDATE()) AND GETDATE() - GROUP BY - qs.query_hash, - qs.query_plan_hash -) -SELECT qs.*, - SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1, - ((CASE statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan -FROM qstats AS qs - INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle - CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; +;WITH qstats AS + ( + SELECT TOP 1000 + REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], + HOST_NAME() AS [computer_name], + max(qs.plan_handle) AS plan_handle, + ISNULL(st1.objectid, 0) AS object_id, + qs.query_hash AS query_hash, + qs.query_plan_hash AS query_plan_hash, + SUM(qs.execution_count) AS execution_count, + SUM(qs.total_elapsed_time) AS total_elapsed_time, + SUM(qs.total_worker_time) AS total_worker_time, + SUM(qs.total_logical_reads) AS total_logical_reads, + SUM(qs.total_physical_reads) AS total_physical_reads, + SUM(qs.total_logical_writes) AS total_logical_writes, + SUM(qs.total_rows) AS total_rows, + SUM(qs.total_grant_kb) AS total_grant_kb + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 + WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, -1200, GETDATE()) AND GETDATE() + GROUP BY st1.objectid, qs.query_hash, qs.query_plan_hash + ) + SELECT + qs.*, + SUBSTRING(st.text, + (stats.statement_start_offset / 2) + 1, + ((CASE stats.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE stats.statement_end_offset + END - stats.statement_start_offset) / 2) + 1) AS query_text, + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name, + ISNULL(CASE WHEN qs.object_id > 0 + THEN CONVERT(sysname, OBJECTPROPERTYEX(qs.object_id, 'BaseType')) -- 'P' (T-SQL) or 'PC' (CLR), '')) + END, '') AS procedure_type + FROM qstats AS qs + JOIN sys.dm_exec_query_stats AS stats + ON stats.plan_handle = qs.plan_handle + CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st; \ No newline at end of file diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 48cd195e4fad7..8412b25487eac 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -1,46 +1,55 @@ SELECT TOP(@top) - REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], - HOST_NAME() AS [computer_name], - DB_NAME(r.database_id) AS db_name, - ISNULL(c.client_net_address, '') as client_address, - ISNULL(c.client_tcp_port, '') AS client_port, - CONVERT(NVARCHAR, TODATETIMEOFFSET(r.start_time, DATEPART(TZOFFSET, SYSDATETIMEOFFSET())), 126) AS query_start, - s.session_id, - s.STATUS AS session_status, - r.STATUS AS request_status, - ISNULL(s.host_name, '') AS host_name, - r.command, - SUBSTRING(o.TEXT, (r.statement_start_offset / 2) + 1, ( - ( - CASE r.statement_end_offset - WHEN - 1 - THEN DATALENGTH(o.TEXT) - ELSE r.statement_end_offset - END - r.statement_start_offset - ) / 2 - ) + 1) AS statement_text, - r.blocking_session_id, - ISNULL(r.wait_type, '') AS wait_type, - r.wait_time, - r.wait_resource, - r.open_transaction_count, - r.transaction_id, - r.percent_complete, - r.estimated_completion_time, - r.cpu_time, - r.total_elapsed_time, - r.reads, - r.writes, - r.logical_reads, - r.transaction_isolation_level, - r.lock_timeout, - r.deadlock_priority, - r.row_count, - ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, - ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, - ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, - s.login_name AS username + REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], + HOST_NAME() AS [computer_name], + DB_NAME(r.database_id) AS db_name, + ISNULL(c.client_net_address, '') as client_address, + ISNULL(c.client_tcp_port, '') AS client_port, + CONVERT(NVARCHAR, TODATETIMEOFFSET(r.start_time, DATEPART(TZOFFSET, SYSDATETIMEOFFSET())), 126) AS query_start, + s.session_id, + s.STATUS AS session_status, + r.STATUS AS request_status, + ISNULL(s.host_name, '') AS host_name, + r.command, + SUBSTRING(o.TEXT, (r.statement_start_offset / 2) + 1, ( + ( + CASE r.statement_end_offset + WHEN - 1 + THEN DATALENGTH(o.TEXT) + ELSE r.statement_end_offset + END - r.statement_start_offset + ) / 2 + ) + 1) AS statement_text, + r.blocking_session_id, + ISNULL(r.wait_type, '') AS wait_type, + r.wait_time, + r.wait_resource, + r.open_transaction_count, + r.transaction_id, + r.percent_complete, + r.estimated_completion_time, + r.cpu_time, + r.total_elapsed_time, + r.reads, + r.writes, + r.logical_reads, + r.transaction_isolation_level, + r.lock_timeout, + r.deadlock_priority, + r.row_count, + ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, + ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, + ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, + s.login_name AS username, + ISNULL(CASE WHEN o.objectid > 0 THEN o.objectid END, '') AS procedure_id, + ISNULL(CASE WHEN o.objectid > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) + END, '') AS procedure_name, + ISNULL(CASE WHEN o.objectid > 0 + THEN CONVERT(sysname, OBJECTPROPERTYEX(o.objectid, 'BaseType')) + END, '') AS procedure_type FROM sys.dm_exec_requests r -INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id -INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; + From 3b5ca90e5e707c5152c66f754e37a19a3ca6a64e Mon Sep 17 00:00:00 2001 From: cjk Date: Mon, 24 Nov 2025 15:10:09 +0000 Subject: [PATCH 02/49] Fixed missing process id --- receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 9a62148a2db5a..8868d930a144c 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -29,6 +29,7 @@ ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, ISNULL(CASE WHEN qs.object_id > 0 THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) From a8ffaeff15ebdf33e54f712a33f475adee1a207a Mon Sep 17 00:00:00 2001 From: cjk Date: Tue, 25 Nov 2025 14:33:28 +0000 Subject: [PATCH 03/49] Removing stored procedure type Updating tests --- receiver/sqlserverreceiver/documentation.md | 2 - receiver/sqlserverreceiver/factory_test.go | 2 +- .../internal/metadata/generated_logs.go | 14 ++- .../internal/metadata/generated_logs_test.go | 10 +- receiver/sqlserverreceiver/metadata.yaml | 5 - receiver/sqlserverreceiver/scraper.go | 16 +--- receiver/sqlserverreceiver/scraper_test.go | 42 +++++---- .../templates/dbQueryAndTextQuery.tmpl | 5 +- .../templates/sqlServerQuerySample.tmpl | 5 +- .../databaseTopQueryWithoutInstanceName.txt | 72 ++++++++------ .../expectedQueryTextAndPlanQuery.yaml | 8 +- ...dQueryTextAndPlanQueryWithInvalidData.yaml | 6 ++ .../expectedRecordDatabaseSampleQuery.yaml | 6 ++ ...ordDatabaseSampleQueryWithInvalidData.yaml | 6 ++ .../testdata/queryTextAndPlanQueryData.txt | 4 +- .../queryTextAndPlanQueryInvalidData.txt | 4 +- .../recordDatabaseSampleQueryData.txt | 4 +- .../recordInvalidDatabaseSampleQueryData.txt | 6 +- .../testdata/testQuerySampleQuery.txt | 94 ++++++++++--------- 19 files changed, 167 insertions(+), 144 deletions(-) diff --git a/receiver/sqlserverreceiver/documentation.md b/receiver/sqlserverreceiver/documentation.md index 75f691ead994d..55fb14a46e8ae 100644 --- a/receiver/sqlserverreceiver/documentation.md +++ b/receiver/sqlserverreceiver/documentation.md @@ -608,7 +608,6 @@ query sample | user.name | Login name associated with the SQL Server session. | Any Str | | sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | | sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | -| sqlserver.procedure_type | The type code of the stored procedure, if any. Typically 'P' | Any Str | ### db.server.top_query @@ -635,7 +634,6 @@ top query | db.system.name | The database management system (DBMS) product as identified by the client instrumentation. | Any Str | | sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | | sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | -| sqlserver.procedure_type | The type code of the stored procedure, if any. Typically 'P' | Any Str | ## Resource Attributes diff --git a/receiver/sqlserverreceiver/factory_test.go b/receiver/sqlserverreceiver/factory_test.go index 27300a46fe0f3..228d9b8236430 100644 --- a/receiver/sqlserverreceiver/factory_test.go +++ b/receiver/sqlserverreceiver/factory_test.go @@ -44,7 +44,7 @@ func TestFactory(t *testing.T) { }, TopQueryCollection: TopQueryCollection{ MaxQuerySampleCount: 1000, - TopQueryCount: 200, + TopQueryCount: 250, CollectionInterval: time.Minute, }, QuerySample: QuerySample{ diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go index ae30196ca3b95..3f492b17762aa 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go @@ -17,7 +17,7 @@ type eventDbServerQuerySample struct { config EventConfig // event config provided by user. } -func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { +func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { if !e.config.Enabled { return } @@ -64,7 +64,6 @@ func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pc dp.Attributes().PutStr("user.name", userNameAttributeValue) dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) - dp.Attributes().PutStr("sqlserver.procedure_type", sqlserverProcedureTypeAttributeValue) } @@ -88,7 +87,7 @@ type eventDbServerTopQuery struct { config EventConfig // event config provided by user. } -func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { +func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { if !e.config.Enabled { return } @@ -117,7 +116,6 @@ func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcomm dp.Attributes().PutStr("db.system.name", dbSystemNameAttributeValue) dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) - dp.Attributes().PutStr("sqlserver.procedure_type", sqlserverProcedureTypeAttributeValue) } @@ -289,11 +287,11 @@ func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs { } // RecordDbServerQuerySampleEvent adds a log record of db.server.query_sample event. -func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { - lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue, sqlserverProcedureTypeAttributeValue) +func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { + lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue) } // RecordDbServerTopQueryEvent adds a log record of db.server.top_query event. -func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string, sqlserverProcedureTypeAttributeValue string) { - lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue, sqlserverProcedureTypeAttributeValue) +func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { + lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue) } diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go index 4b9270d807ae4..e5ac43e808729 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go @@ -134,10 +134,10 @@ func TestLogsBuilder(t *testing.T) { allEventsCount := 0 allEventsCount++ - lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val", "sqlserver.procedure_type-val") + lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val") allEventsCount++ - lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val", "sqlserver.procedure_type-val") + lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val") rb := lb.NewResourceBuilder() rb.SetHostName("host.name-val") @@ -281,9 +281,6 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") assert.True(t, ok) assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) - attrVal, ok = lr.Attributes().Get("sqlserver.procedure_type") - assert.True(t, ok) - assert.Equal(t, "sqlserver.procedure_type-val", attrVal.Str()) case "db.server.top_query": assert.False(t, validatedEvents["db.server.top_query"], "Found a duplicate in the events slice: db.server.top_query") validatedEvents["db.server.top_query"] = true @@ -342,9 +339,6 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") assert.True(t, ok) assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) - attrVal, ok = lr.Attributes().Get("sqlserver.procedure_type") - assert.True(t, ok) - assert.Equal(t, "sqlserver.procedure_type-val", attrVal.Str()) } } }) diff --git a/receiver/sqlserverreceiver/metadata.yaml b/receiver/sqlserverreceiver/metadata.yaml index f7935b97d873f..90131ae0f091b 100644 --- a/receiver/sqlserverreceiver/metadata.yaml +++ b/receiver/sqlserverreceiver/metadata.yaml @@ -140,9 +140,6 @@ attributes: sqlserver.procedure_name: description: The name of the stored procedure, if any type: string - sqlserver.procedure_type: - description: The type code of the stored procedure, if any. Typically 'P' - type: string sqlserver.query_hash: description: Binary hash value calculated on the query and used to identify queries with similar logic, reported in the HEX format. type: string @@ -274,7 +271,6 @@ events: - user.name - sqlserver.procedure_id - sqlserver.procedure_name - - sqlserver.procedure_type db.server.top_query: enabled: false @@ -297,7 +293,6 @@ events: - db.system.name - sqlserver.procedure_id - sqlserver.procedure_name - - sqlserver.procedure_type metrics: sqlserver.batch.request.rate: diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index f891bcb72c369..559ea762743aa 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -642,7 +642,6 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont // stored procedure columns storedProcedureId = "procedure_id" storedProcedureName = "procedure_name" - storedProcedureType = "procedure_type" ) resources := pcommon.NewResource() @@ -665,7 +664,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont for i, row := range rows { queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) - planId := row[storedProcedureId] + planId := row[storedProcedureId] // defaulted to '0' if not present elapsedTimeMicrosecond, err := strconv.ParseInt(row[totalElapsedTime], 10, 64) if err != nil { @@ -791,7 +790,6 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont dbSystemNameVal, row[storedProcedureId], row[storedProcedureName], - row[storedProcedureType], ) } return resources, errors.Join(errs...) @@ -821,22 +819,19 @@ func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, procedur } key := queryHash + "-" + queryPlanHash + "-" + column - if procedureId != "" { + if procedureId != "0" { // procedureId is '0' when not a stored procedure key = procedureId + "-" + key } cached, ok := s.cache.Get(key) if !ok { s.cache.Add(key, val) - // s.logger.Info("Adding key", zap.String("key", key), zap.Int64("val", val)) return false, val } if val > cached { s.cache.Add(key, val) - // s.logger.Info("Returning cached diff for ", zap.String("key", key), zap.Int64("val", val)) return true, val - cached } - // s.logger.Info("Returning 0", zap.String("key", key), zap.Int64("val", val)) return true, 0 } @@ -947,7 +942,6 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) // stored procedure columns const storedProcedureId = "procedure_id" const storedProcedureName = "procedure_name" - const storedProcedureType = "procedure_type" rows, err := s.client.QueryRows( ctx, @@ -1041,8 +1035,8 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) } else { clientAddressVal = row[clientAddress] } - if row[storedProcedureId] != "0" { - s.logger.Info("Stored proc data", zap.String("id", row[storedProcedureId]), zap.String("name", row[storedProcedureName]), zap.String("type", row[storedProcedureType])) + if s.logger.Level() == zap.DebugLevel && row[storedProcedureId] != "0" { + s.logger.Debug("Stored proc data", zap.String("id", row[storedProcedureId]), zap.String("name", row[storedProcedureName])) } s.lb.RecordDbServerQuerySampleEvent( contextFromQuery, @@ -1059,7 +1053,7 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) sessionIDVal, sessionStatusVal, totalElapsedTimeSecondVal, transactionIDVal, transactionIsolationLevelVal, waitResourceVal, waitTimeSecondVal, waitTypeVal, writesVal, usernameVal, - row[storedProcedureId], row[storedProcedureName], row[storedProcedureType], + row[storedProcedureId], row[storedProcedureName], ) if !resourcesAdded { diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index cbb751a447e2b..1107edd79c47b 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -232,19 +232,19 @@ func TestScrapeCacheAndDiff(t *testing.T) { assert.NotNil(t, scrapers) scraper := scrapers[0] - cached, val := scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", -1) + cached, val := scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", -1) assert.False(t, cached) assert.Equal(t, int64(0), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 1) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 1) assert.False(t, cached) assert.Equal(t, int64(1), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 1) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 1) assert.True(t, cached) assert.Equal(t, int64(0), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 3) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 3) assert.True(t, cached) assert.Equal(t, int64(2), val) } @@ -401,14 +401,15 @@ func TestQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalElapsedTime, 846) - scraper.cacheAndDiff(queryHash, queryPlanHash, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalWorkerTime, 845) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalGrant, 1) + procedureId := "0" + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalElapsedTime, 846) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalWorkerTime, 845) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalGrant, 1) scraper.client = mockClient{ instanceName: scraper.config.InstanceName, @@ -460,14 +461,15 @@ func TestInvalidQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalElapsedTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalWorkerTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalGrant, 1) + procedureId := "0" + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalElapsedTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalWorkerTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalGrant, 1) scraper.client = mockInvalidClient{ mockClient: mockClient{ diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 8868d930a144c..7dcbc0df5851f 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -33,10 +33,7 @@ ISNULL(CASE WHEN qs.object_id > 0 THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) - END, '') AS procedure_name, - ISNULL(CASE WHEN qs.object_id > 0 - THEN CONVERT(sysname, OBJECTPROPERTYEX(qs.object_id, 'BaseType')) -- 'P' (T-SQL) or 'PC' (CLR), '')) - END, '') AS procedure_type + END, '') AS procedure_name FROM qstats AS qs JOIN sys.dm_exec_query_stats AS stats ON stats.plan_handle = qs.plan_handle diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 8412b25487eac..96774ccaf08a9 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -44,10 +44,7 @@ SELECT TOP(@top) ISNULL(CASE WHEN o.objectid > 0 THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) - END, '') AS procedure_name, - ISNULL(CASE WHEN o.objectid > 0 - THEN CONVERT(sysname, OBJECTPROPERTYEX(o.objectid, 'BaseType')) - END, '') AS procedure_type + END, '') AS procedure_name FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index cc3ceef5bfc2e..7dcbc0df5851f 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -1,31 +1,41 @@ -with qstats as ( - SELECT TOP(@maxSampleCount) - REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], - HOST_NAME() AS [computer_name], - MAX(qs.plan_handle) AS query_plan_handle, - qs.query_hash AS query_hash, - qs.query_plan_hash AS query_plan_hash, - SUM(qs.execution_count) AS execution_count, - SUM(qs.total_elapsed_time) AS total_elapsed_time, - SUM(qs.total_worker_time) AS total_worker_time, - SUM(qs.total_logical_reads) AS total_logical_reads, - SUM(qs.total_physical_reads) AS total_physical_reads, - SUM(qs.total_logical_writes) AS total_logical_writes, - SUM(qs.total_rows) AS total_rows, - SUM(qs.total_grant_kb) as total_grant_kb - FROM sys.dm_exec_query_stats AS qs - WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @lookbackTime, GETDATE()) AND GETDATE() - GROUP BY - qs.query_hash, - qs.query_plan_hash -) -SELECT qs.*, - SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1, - ((CASE statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan -FROM qstats AS qs - INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle - CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; +;WITH qstats AS + ( + SELECT TOP 1000 + REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], + HOST_NAME() AS [computer_name], + max(qs.plan_handle) AS plan_handle, + ISNULL(st1.objectid, 0) AS object_id, + qs.query_hash AS query_hash, + qs.query_plan_hash AS query_plan_hash, + SUM(qs.execution_count) AS execution_count, + SUM(qs.total_elapsed_time) AS total_elapsed_time, + SUM(qs.total_worker_time) AS total_worker_time, + SUM(qs.total_logical_reads) AS total_logical_reads, + SUM(qs.total_physical_reads) AS total_physical_reads, + SUM(qs.total_logical_writes) AS total_logical_writes, + SUM(qs.total_rows) AS total_rows, + SUM(qs.total_grant_kb) AS total_grant_kb + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 + WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, -1200, GETDATE()) AND GETDATE() + GROUP BY st1.objectid, qs.query_hash, qs.query_plan_hash + ) + SELECT + qs.*, + SUBSTRING(st.text, + (stats.statement_start_offset / 2) + 1, + ((CASE stats.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE stats.statement_end_offset + END - stats.statement_start_offset) / 2) + 1) AS query_text, + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name + FROM qstats AS qs + JOIN sys.dm_exec_query_stats AS stats + ON stats.plan_handle = qs.plan_handle + CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st; \ No newline at end of file diff --git a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml index 4042e9baab083..b68c276bed762 100644 --- a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml @@ -61,8 +61,14 @@ resourceLogs: - key: db.system.name value: stringValue: microsoft.sql_server + - key: sqlserver.procedure_id + value: + stringValue: "0" + - key: sqlserver.procedure_name + value: + stringValue: "" body: {} - eventName: db.server.top_query + eventName: "db.server.top_query" spanId: "" timeUnixNano: "1749224037462260000" traceId: "" diff --git a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml index b9c8b65d2503b..5d0c6d0036769 100644 --- a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml @@ -61,6 +61,12 @@ resourceLogs: - key: db.system.name value: stringValue: microsoft.sql_server + - key: sqlserver.procedure_id + value: + stringValue: "0" + - key: sqlserver.procedure_name + value: + stringValue: "" body: {} eventName: db.server.top_query spanId: "" diff --git a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml index d0924bd35b480..b0babe6d17b09 100644 --- a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml @@ -115,6 +115,12 @@ resourceLogs: - key: user.name value: stringValue: sa + - key: sqlserver.procedure_id + value: + stringValue: "0" + - key: sqlserver.procedure_name + value: + stringValue: "" body: {} eventName: db.server.query_sample spanId: a7ad6a7169203331 diff --git a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml index 6a24c138b13c5..c91eabef757bc 100644 --- a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml @@ -115,6 +115,12 @@ resourceLogs: - key: user.name value: stringValue: sa + - key: sqlserver.procedure_id + value: + stringValue: "0" + - key: sqlserver.procedure_name + value: + stringValue: "" body: {} eventName: db.server.query_sample spanId: "" diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt index abd3e31b681f7..7f7bf747a1c71 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt @@ -14,6 +14,8 @@ "total_rows": "2", "total_grant_kb": "3096", "query_text": "with qstats as (\n SELECT TOP(@topNValue)\n REPLACE(@@SERVERNAME,'\\',':') AS [sql_instance],\n HOST_NAME() AS [computer_name],\n MAX(qs.plan_handle) AS query_plan_handle,\n qs.query_hash AS query_hash,\n qs.query_plan_hash AS query_plan_hash,\n SUM(qs.execution_count) AS execution_count,\n SUM(qs.total_elapsed_time) AS total_elapsed_time,\n SUM(qs.total_worker_time) AS total_worker_time,\n SUM(qs.total_logical_reads) AS total_logical_reads,\n SUM(qs.total_physical_reads) AS total_physical_reads,\n SUM(qs.total_logical_writes) AS total_logical_writes,\n SUM(qs.total_rows) AS total_rows,\n SUM(qs.total_grant_kb) as total_grant_kb\n FROM sys.dm_exec_query_stats AS qs\n WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @granularity, GETDATE()) AND GETDATE()\n GROUP BY\n qs.query_hash,\n qs.query_plan_hash\n)\nSELECT qs.*,\n SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1,\n ((CASE statement_end_offset\n WHEN -1 THEN DATALENGTH(st.text)\n ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS text,\n ISNULL(qp.query_plan, '') AS query_plan\nFROM qstats AS qs\n INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle\n CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp\n CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st", - "query_plan": "" + "query_plan": "", + "procedure_id": "0", + "procedure_name": "" } ] diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt index da3925707bf19..4fad7195dd197 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt @@ -14,6 +14,8 @@ "total_rows": "2A", "total_grant_kb": "3096A", "query_text": "SELECT cpu_time AS [CPU Usage (time)", - "query_plan": " 0 THEN o.objectid END, '') AS procedure_id, + ISNULL(CASE WHEN o.objectid > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) + END, '') AS procedure_name FROM sys.dm_exec_requests r -INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id -INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file + INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id + INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id + CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; + From 0e855367f49b64da1aa7e87ee814491aa67cb401 Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 26 Nov 2025 14:44:53 +0000 Subject: [PATCH 04/49] Added changelog entry --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b0fcdd44b29..8010d523d177f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,8 @@ Starting with version v0.83.0, this changelog includes only user-facing changes. If you are looking for developer-facing changes, check out [CHANGELOG-API.md](./CHANGELOG-API.md). - - +- +- `receiver/sqlserverreceiver`: For TopQuery and Samples events, added stored procedure attributes sqlserver.procedure_id and sqlserver.procedure_name ## v0.140.1 From c155be3c5823c7f795d15faa78e2ecdb35aa3191 Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 26 Nov 2025 15:03:20 +0000 Subject: [PATCH 05/49] Added changelog entry --- ...sqlserver-stored-procedure-monitoring.yaml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .chloggen/sqlserver-stored-procedure-monitoring.yaml diff --git a/.chloggen/sqlserver-stored-procedure-monitoring.yaml b/.chloggen/sqlserver-stored-procedure-monitoring.yaml new file mode 100644 index 0000000000000..14d58653587da --- /dev/null +++ b/.chloggen/sqlserver-stored-procedure-monitoring.yaml @@ -0,0 +1,27 @@ +# 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. receiver/filelog) +component: receiver/sqlserverreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: adding sqlserver.procedure_id and sqlserver.procedure_name attributes to TopQuery and Sample Events + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [1] + +# (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: + +# 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] From c1d747cf420a7a29301f830d6deeac3c0431ffe7 Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 26 Nov 2025 15:25:29 +0000 Subject: [PATCH 06/49] reverted changelog entry --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8010d523d177f..24b0fcdd44b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,8 @@ Starting with version v0.83.0, this changelog includes only user-facing changes. If you are looking for developer-facing changes, check out [CHANGELOG-API.md](./CHANGELOG-API.md). -- -- `receiver/sqlserverreceiver`: For TopQuery and Samples events, added stored procedure attributes sqlserver.procedure_id and sqlserver.procedure_name + + ## v0.140.1 From f5a90a205848e5e4b24ad8e40bd56f6dd9af0d34 Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 26 Nov 2025 15:40:15 +0000 Subject: [PATCH 07/49] fix changelog entry --- .chloggen/sqlserver-stored-procedure-monitoring.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/sqlserver-stored-procedure-monitoring.yaml b/.chloggen/sqlserver-stored-procedure-monitoring.yaml index 14d58653587da..73ea8f7213c26 100644 --- a/.chloggen/sqlserver-stored-procedure-monitoring.yaml +++ b/.chloggen/sqlserver-stored-procedure-monitoring.yaml @@ -4,7 +4,7 @@ change_type: enhancement # The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog) -component: receiver/sqlserverreceiver +component: receiver/sqlserver # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: adding sqlserver.procedure_id and sqlserver.procedure_name attributes to TopQuery and Sample Events From 0bc478534b7c82e6cd0f7c9012d27802ad9eba3b Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 26 Nov 2025 16:36:18 +0000 Subject: [PATCH 08/49] updates to variable naming and usage --- receiver/sqlserverreceiver/scraper.go | 24 +++++++-------- receiver/sqlserverreceiver/scraper_test.go | 36 +++++++++++----------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index 559ea762743aa..52d71bdc0df68 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -640,7 +640,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont dbSystemNameVal = "microsoft.sql_server" // stored procedure columns - storedProcedureId = "procedure_id" + storedProcedureID = "procedure_id" storedProcedureName = "procedure_name" ) @@ -649,7 +649,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont rows, err := s.client.QueryRows( ctx, sql.Named("lookbackTime", -int(s.config.EffectiveLookbackTime().Seconds())), - sql.Named("maxSampleCount", s.config.MaxQuerySampleCount), + sql.Named("maxSampleCount", maxQuerySampleCount), ) if err != nil { if !errors.Is(err, sqlquery.ErrNullValueWarning) { @@ -664,7 +664,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont for i, row := range rows { queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) - planId := row[storedProcedureId] // defaulted to '0' if not present + planId := row[storedProcedureID] // defaulted to '0' if not present elapsedTimeMicrosecond, err := strconv.ParseInt(row[totalElapsedTime], 10, 64) if err != nil { @@ -699,7 +699,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont // reporting human-readable query hash and query hash plan queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) - procId := row[storedProcedureId] + procId := row[storedProcedureID] queryTextVal := s.retrieveValue(row, queryText, &errs, func(row sqlquery.StringMap, columnName string) (any, error) { statement := row[columnName] @@ -788,7 +788,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont s.config.Server, int64(s.config.Port), dbSystemNameVal, - row[storedProcedureId], + row[storedProcedureID], row[storedProcedureName], ) } @@ -813,14 +813,14 @@ func (s *sqlServerScraperHelper) retrieveValue( // cacheAndDiff store row(in int) with query hash and query plan hash variables // (1) returns true if the key is cached before // (2) returns positive value if the value is larger than the cached value -func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, procedureId, column string, val int64) (bool, int64) { +func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, procedureID, column string, val int64) (bool, int64) { if val < 0 { return false, 0 } key := queryHash + "-" + queryPlanHash + "-" + column - if procedureId != "0" { // procedureId is '0' when not a stored procedure - key = procedureId + "-" + key + if procedureID != "0" { // procedureID is '0' when not a stored procedure + key = procedureID + "-" + key } cached, ok := s.cache.Get(key) if !ok { @@ -940,7 +940,7 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) const waitType = "wait_type" const writes = "writes" // stored procedure columns - const storedProcedureId = "procedure_id" + const storedProcedureID = "procedure_id" const storedProcedureName = "procedure_name" rows, err := s.client.QueryRows( @@ -1035,8 +1035,8 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) } else { clientAddressVal = row[clientAddress] } - if s.logger.Level() == zap.DebugLevel && row[storedProcedureId] != "0" { - s.logger.Debug("Stored proc data", zap.String("id", row[storedProcedureId]), zap.String("name", row[storedProcedureName])) + if s.logger.Level() == zap.DebugLevel && row[storedProcedureID] != "0" { + s.logger.Debug("Stored proc data", zap.String("id", row[storedProcedureID]), zap.String("name", row[storedProcedureName])) } s.lb.RecordDbServerQuerySampleEvent( contextFromQuery, @@ -1053,7 +1053,7 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) sessionIDVal, sessionStatusVal, totalElapsedTimeSecondVal, transactionIDVal, transactionIsolationLevelVal, waitResourceVal, waitTimeSecondVal, waitTypeVal, writesVal, usernameVal, - row[storedProcedureId], row[storedProcedureName], + row[storedProcedureID], row[storedProcedureName], ) if !resourcesAdded { diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 1107edd79c47b..66ceff39043b3 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -401,15 +401,15 @@ func TestQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - procedureId := "0" - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalElapsedTime, 846) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalWorkerTime, 845) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalGrant, 1) + procedureID := "0" + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalElapsedTime, 846) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalWorkerTime, 845) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalGrant, 1) scraper.client = mockClient{ instanceName: scraper.config.InstanceName, @@ -461,15 +461,15 @@ func TestInvalidQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - procedureId := "0" - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalElapsedTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalWorkerTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureId, totalGrant, 1) + procedureID := "0" + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalElapsedTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalWorkerTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalGrant, 1) scraper.client = mockInvalidClient{ mockClient: mockClient{ From f4eea598a95f211d12dd738ac707528850465b3c Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 26 Nov 2025 16:47:27 +0000 Subject: [PATCH 09/49] updates to variable naming and usage --- receiver/sqlserverreceiver/scraper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index 52d71bdc0df68..66f61ab59b722 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -664,7 +664,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont for i, row := range rows { queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) - planId := row[storedProcedureID] // defaulted to '0' if not present + procID := row[storedProcedureID] // defaulted to '0' if not present elapsedTimeMicrosecond, err := strconv.ParseInt(row[totalElapsedTime], 10, 64) if err != nil { @@ -673,7 +673,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont } else { // we're trying to get the queries that used the most time. // caching the total elapsed time (in microsecond) and compare in the next scrape. - if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, planId, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { + if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { totalElapsedTimeDiffsMicrosecond[i] = diff } } From 81d191fe2823ea0ae32d2c6f88eacd67a452d7e8 Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 26 Nov 2025 16:56:36 +0000 Subject: [PATCH 10/49] updates to variable naming and usage --- receiver/sqlserverreceiver/scraper.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index 66f61ab59b722..77bfb54b9f7f1 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -699,7 +699,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont // reporting human-readable query hash and query hash plan queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) - procId := row[storedProcedureID] + procID := row[storedProcedureID] queryTextVal := s.retrieveValue(row, queryText, &errs, func(row sqlquery.StringMap, columnName string) (any, error) { statement := row[columnName] @@ -713,25 +713,25 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) executionCountVal := s.retrieveValue(row, executionCount, &errs, retrieveInt) - cached, executionCountVal := s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, executionCount, executionCountVal.(int64)) + cached, executionCountVal := s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, executionCount, executionCountVal.(int64)) if !cached { executionCountVal = int64(0) } logicalReadsVal := s.retrieveValue(row, logicalReads, &errs, retrieveInt) - cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, logicalReads, logicalReadsVal.(int64)) + cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, logicalReads, logicalReadsVal.(int64)) if !cached { logicalReadsVal = int64(0) } logicalWritesVal := s.retrieveValue(row, logicalWrites, &errs, retrieveInt) - cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, logicalWrites, logicalWritesVal.(int64)) + cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, logicalWrites, logicalWritesVal.(int64)) if !cached { logicalWritesVal = int64(0) } physicalReadsVal := s.retrieveValue(row, physicalReads, &errs, retrieveInt) - cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, physicalReads, physicalReadsVal.(int64)) + cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, physicalReads, physicalReadsVal.(int64)) if !cached { physicalReadsVal = int64(0) } @@ -741,19 +741,19 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) rowsReturnedVal := s.retrieveValue(row, rowsReturned, &errs, retrieveInt) - cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, rowsReturned, rowsReturnedVal.(int64)) + cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, rowsReturned, rowsReturnedVal.(int64)) if !cached { rowsReturnedVal = int64(0) } totalGrantVal := s.retrieveValue(row, totalGrant, &errs, retrieveInt) - cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, totalGrant, totalGrantVal.(int64)) + cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalGrant, totalGrantVal.(int64)) if !cached { totalGrantVal = int64(0) } totalWorkerTimeVal := s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt) - cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procId, totalWorkerTime, totalWorkerTimeVal.(int64)) + cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalWorkerTime, totalWorkerTimeVal.(int64)) totalWorkerTimeInSecVal := float64(0) if cached { totalWorkerTimeInSecVal = float64(totalWorkerTimeVal.(int64)) / 1_000_000 From 8a2b5edb3af607701f38eab750f1a19595c08f5a Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 27 Nov 2025 10:01:57 +0000 Subject: [PATCH 11/49] ran make generate --- receiver/sqlserverreceiver/generated_package_test.go | 3 ++- .../sqlserverreceiver/internal/metadata/generated_logs.go | 1 + .../internal/metadata/generated_logs_test.go | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/receiver/sqlserverreceiver/generated_package_test.go b/receiver/sqlserverreceiver/generated_package_test.go index 86266f5f36641..ce3721511beef 100644 --- a/receiver/sqlserverreceiver/generated_package_test.go +++ b/receiver/sqlserverreceiver/generated_package_test.go @@ -3,8 +3,9 @@ package sqlserverreceiver import ( - "go.uber.org/goleak" "testing" + + "go.uber.org/goleak" ) func TestMain(m *testing.M) { diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go index 3f492b17762aa..0d7489892146a 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go @@ -4,6 +4,7 @@ package metadata import ( "context" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/filter" "go.opentelemetry.io/collector/pdata/pcommon" diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go index e5ac43e808729..7038dc4bf960c 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go @@ -4,6 +4,9 @@ package metadata import ( "context" + "testing" + "time" + "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" @@ -11,8 +14,6 @@ import ( "go.opentelemetry.io/otel/trace" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" - "testing" - "time" ) type eventsTestDataSet int From 8024074db3666117ff74fcb1261e548a4f882dc2 Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 27 Nov 2025 15:07:02 +0000 Subject: [PATCH 12/49] Moved QuerySampleCount and TopQueryCount ot of params and reference config obj directly --- receiver/sqlserverreceiver/scraper.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index 77bfb54b9f7f1..677e22a739965 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -152,7 +152,7 @@ func (s *sqlServerScraperHelper) ScrapeLogs(ctx context.Context) (plog.Logs, err 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, s.config.MaxQuerySampleCount) + resources, err = s.recordDatabaseQueryTextAndPlan(ctx) case getSQLServerQuerySamplesQuery(): resources, err = s.recordDatabaseSampleQuery(ctx) default: @@ -619,7 +619,7 @@ func (s *sqlServerScraperHelper) recordDatabaseWaitMetrics(ctx context.Context) return errors.Join(errs...) } -func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context, topQueryCount, maxQuerySampleCount uint) (pcommon.Resource, error) { +func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context) (pcommon.Resource, error) { // Constants are the column names of the database status const ( executionCount = "execution_count" @@ -649,7 +649,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont rows, err := s.client.QueryRows( ctx, sql.Named("lookbackTime", -int(s.config.EffectiveLookbackTime().Seconds())), - sql.Named("maxSampleCount", maxQuerySampleCount), + sql.Named("maxSampleCount", s.config.MaxQuerySampleCount), ) if err != nil { if !errors.Is(err, sqlquery.ErrNullValueWarning) { @@ -680,7 +680,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont } // sort the rows based on the totalElapsedTimeDiffs in descending order, // only report first T(T=topQueryCount) rows. - rows = sortRows(rows, totalElapsedTimeDiffsMicrosecond, topQueryCount) + rows = sortRows(rows, totalElapsedTimeDiffsMicrosecond, s.config.TopQueryCount) // sort the totalElapsedTimeDiffs in descending order as well sort.Slice(totalElapsedTimeDiffsMicrosecond, func(i, j int) bool { return totalElapsedTimeDiffsMicrosecond[i] > totalElapsedTimeDiffsMicrosecond[j] }) From 08900201d6e619d0150593c3322b4568dd4c0275 Mon Sep 17 00:00:00 2001 From: cjk Date: Mon, 1 Dec 2025 10:29:15 +0000 Subject: [PATCH 13/49] Added issue to changelog --- .chloggen/sqlserver-stored-procedure-monitoring.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/sqlserver-stored-procedure-monitoring.yaml b/.chloggen/sqlserver-stored-procedure-monitoring.yaml index 73ea8f7213c26..7b2f6e03d325a 100644 --- a/.chloggen/sqlserver-stored-procedure-monitoring.yaml +++ b/.chloggen/sqlserver-stored-procedure-monitoring.yaml @@ -10,7 +10,7 @@ component: receiver/sqlserver note: adding sqlserver.procedure_id and sqlserver.procedure_name attributes to TopQuery and Sample Events # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. -issues: [1] +issues: [44656] # (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. From fb96860cbdbe0b10828f7c195b259fb1ab2ea63c Mon Sep 17 00:00:00 2001 From: cjk Date: Tue, 2 Dec 2025 10:33:27 +0000 Subject: [PATCH 14/49] Replaced values with vars --- receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 7dcbc0df5851f..20da91dea8a26 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -1,6 +1,6 @@ ;WITH qstats AS ( - SELECT TOP 1000 + SELECT TOP (@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], max(qs.plan_handle) AS plan_handle, @@ -17,7 +17,7 @@ SUM(qs.total_grant_kb) AS total_grant_kb FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 - WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, -1200, GETDATE()) AND GETDATE() + WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @lookbackTime, GETDATE()) AND GETDATE() GROUP BY st1.objectid, qs.query_hash, qs.query_plan_hash ) SELECT From c5d313a9479ae361bb09731b8478c30a82f948cb Mon Sep 17 00:00:00 2001 From: cjk Date: Tue, 2 Dec 2025 13:12:27 +0000 Subject: [PATCH 15/49] Integrating stored procedure values for samples --- receiver/postgresqlreceiver/documentation.md | 2 ++ .../internal/metadata/generated_logs.go | 8 +++++--- .../internal/metadata/generated_logs_test.go | 8 +++++++- receiver/postgresqlreceiver/metadata.yaml | 8 ++++++++ receiver/postgresqlreceiver/scraper.go | 2 ++ 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/receiver/postgresqlreceiver/documentation.md b/receiver/postgresqlreceiver/documentation.md index a5376dd9388cf..38316fb9f4f20 100644 --- a/receiver/postgresqlreceiver/documentation.md +++ b/receiver/postgresqlreceiver/documentation.md @@ -422,6 +422,8 @@ query sample | postgresql.wait_event_type | The type of event for which the backend is waiting, if any; otherwise NULL. | Any Str | | postgresql.query_id | Identifier of this backend's most recent query. If state is active this field shows the identifier of the currently executing query. In all other states, it shows the identifier of last query that was executed. | Any Str | | postgresql.total_exec_time | Total time spent executing the statement, in delta milliseconds. | Any Double | +| postgresql.procedure_id | Identifier of the stored procedure. | Any Str | +| postgresql.procedure_name | Name of the stored procedure. | Any Str | ### db.server.top_query diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_logs.go b/receiver/postgresqlreceiver/internal/metadata/generated_logs.go index 2c7e326e7817b..467a6edb2a1f0 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_logs.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_logs.go @@ -18,7 +18,7 @@ type eventDbServerQuerySample struct { config EventConfig // event config provided by user. } -func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue string, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64) { +func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue string, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64, postgresqlProcedureIDAttributeValue string, postgresqlProcedureNameAttributeValue string) { if !e.config.Enabled { return } @@ -45,6 +45,8 @@ func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pc dp.Attributes().PutStr("postgresql.wait_event_type", postgresqlWaitEventTypeAttributeValue) dp.Attributes().PutStr("postgresql.query_id", postgresqlQueryIDAttributeValue) dp.Attributes().PutDouble("postgresql.total_exec_time", postgresqlTotalExecTimeAttributeValue) + dp.Attributes().PutStr("postgresql.procedure_id", postgresqlProcedureIDAttributeValue) + dp.Attributes().PutStr("postgresql.procedure_name", postgresqlProcedureNameAttributeValue) } @@ -249,8 +251,8 @@ func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs { } // RecordDbServerQuerySampleEvent adds a log record of db.server.query_sample event. -func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue AttributeDbSystemName, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64) { - lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, dbSystemNameAttributeValue.String(), dbNamespaceAttributeValue, dbQueryTextAttributeValue, userNameAttributeValue, postgresqlStateAttributeValue, postgresqlPidAttributeValue, postgresqlApplicationNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, postgresqlClientHostnameAttributeValue, postgresqlQueryStartAttributeValue, postgresqlWaitEventAttributeValue, postgresqlWaitEventTypeAttributeValue, postgresqlQueryIDAttributeValue, postgresqlTotalExecTimeAttributeValue) +func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue AttributeDbSystemName, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64, postgresqlProcedureIDAttributeValue string, postgresqlProcedureNameAttributeValue string) { + lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, dbSystemNameAttributeValue.String(), dbNamespaceAttributeValue, dbQueryTextAttributeValue, userNameAttributeValue, postgresqlStateAttributeValue, postgresqlPidAttributeValue, postgresqlApplicationNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, postgresqlClientHostnameAttributeValue, postgresqlQueryStartAttributeValue, postgresqlWaitEventAttributeValue, postgresqlWaitEventTypeAttributeValue, postgresqlQueryIDAttributeValue, postgresqlTotalExecTimeAttributeValue, postgresqlProcedureIDAttributeValue, postgresqlProcedureNameAttributeValue) } // RecordDbServerTopQueryEvent adds a log record of db.server.top_query event. diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go b/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go index 3eec76a985dd8..5f3d26d9bb1d9 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go @@ -132,7 +132,7 @@ func TestLogsBuilder(t *testing.T) { allEventsCount := 0 defaultEventsCount++ allEventsCount++ - lb.RecordDbServerQuerySampleEvent(ctx, timestamp, AttributeDbSystemNamePostgresql, "db.namespace-val", "db.query.text-val", "user.name-val", "postgresql.state-val", 14, "postgresql.application_name-val", "network.peer.address-val", 17, "postgresql.client_hostname-val", "postgresql.query_start-val", "postgresql.wait_event-val", "postgresql.wait_event_type-val", "postgresql.query_id-val", 26.100000) + lb.RecordDbServerQuerySampleEvent(ctx, timestamp, AttributeDbSystemNamePostgresql, "db.namespace-val", "db.query.text-val", "user.name-val", "postgresql.state-val", 14, "postgresql.application_name-val", "network.peer.address-val", 17, "postgresql.client_hostname-val", "postgresql.query_start-val", "postgresql.wait_event-val", "postgresql.wait_event_type-val", "postgresql.query_id-val", 26.100000, "postgresql.procedure_id-val", "postgresql.procedure_name-val") defaultEventsCount++ allEventsCount++ lb.RecordDbServerTopQueryEvent(ctx, timestamp, AttributeDbSystemNamePostgresql, "db.namespace-val", "db.query.text-val", 16, 15, 30, 26, 27, 30, 25, 28, "postgresql.queryid-val", "postgresql.rolname-val", 26.100000, 26.100000, "postgresql.query_plan-val") @@ -216,6 +216,12 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("postgresql.total_exec_time") assert.True(t, ok) assert.Equal(t, 26.100000, attrVal.Double()) + attrVal, ok = lr.Attributes().Get("postgresql.procedure_id") + assert.True(t, ok) + assert.Equal(t, "postgresql.procedure_id-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("postgresql.procedure_name") + assert.True(t, ok) + assert.Equal(t, "postgresql.procedure_name-val", attrVal.Str()) case "db.server.top_query": assert.False(t, validatedEvents["db.server.top_query"], "Found a duplicate in the events slice: db.server.top_query") validatedEvents["db.server.top_query"] = true diff --git a/receiver/postgresqlreceiver/metadata.yaml b/receiver/postgresqlreceiver/metadata.yaml index 4275c1dd70bf0..e4ef5e6fb962f 100644 --- a/receiver/postgresqlreceiver/metadata.yaml +++ b/receiver/postgresqlreceiver/metadata.yaml @@ -94,6 +94,12 @@ attributes: postgresql.pid: description: Process ID of this backend. type: int + postgresql.procedure_id: + description: Identifier of the stored procedure. + type: string + postgresql.procedure_name: + description: Name of the stored procedure. + type: string postgresql.query_id: description: Identifier of this backend's most recent query. If state is active this field shows the identifier of the currently executing query. In all other states, it shows the identifier of last query that was executed. type: string @@ -196,6 +202,8 @@ events: - postgresql.wait_event_type - postgresql.query_id - postgresql.total_exec_time + - postgresql.procedure_id + - postgresql.procedure_name db.server.top_query: enabled: true description: top query diff --git a/receiver/postgresqlreceiver/scraper.go b/receiver/postgresqlreceiver/scraper.go index b06a4856e0e35..5b8d2164e2f1f 100644 --- a/receiver/postgresqlreceiver/scraper.go +++ b/receiver/postgresqlreceiver/scraper.go @@ -227,6 +227,8 @@ func (p *postgreSQLScraper) collectQuerySamples(ctx context.Context, dbClient cl atts[dbAttributePrefix+"wait_event_type"].(string), atts[dbAttributePrefix+"query_id"].(string), atts["duration"].(float64), + atts[dbAttributePrefix+"procedure_id"].(string), + atts[dbAttributePrefix+"procedure_name"].(string), ) } } From 28fa28f3b3e7b02c5373658403b5e7d4a8622ddb Mon Sep 17 00:00:00 2001 From: cjk Date: Tue, 2 Dec 2025 13:34:27 +0000 Subject: [PATCH 16/49] updates to test data --- .../databaseTopQueryWithInstanceName.txt | 72 +++++++++++-------- .../databaseTopQueryWithoutInstanceName.txt | 4 +- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt index 29228f5aba2e4..6e223d1ae7dc0 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt @@ -1,31 +1,41 @@ -with qstats as ( - SELECT TOP(@topNValue) - REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], - HOST_NAME() AS [computer_name], - MAX(qs.plan_handle) AS query_plan_handle, - qs.query_hash AS query_hash, - qs.query_plan_hash AS query_plan_hash, - SUM(qs.execution_count) AS execution_count, - SUM(qs.total_elapsed_time) AS total_elapsed_time, - SUM(qs.total_worker_time) AS total_worker_time, - SUM(qs.total_logical_reads) AS total_logical_reads, - SUM(qs.total_physical_reads) AS total_physical_reads, - SUM(qs.total_logical_writes) AS total_logical_writes, - SUM(qs.total_rows) AS total_rows, - SUM(qs.total_grant_kb) as total_grant_kb - FROM sys.dm_exec_query_stats AS qs - WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @lookbackTime, GETDATE()) AND GETDATE() AND (@instanceName = '' OR @@SERVERNAME = @instanceName) - GROUP BY - qs.query_hash, - qs.query_plan_hash -) -SELECT qs.*, - SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1, - ((CASE statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan -FROM qstats AS qs - INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle - CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; +;WITH qstats AS + ( + SELECT TOP 1000 + REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], + HOST_NAME() AS [computer_name], + max(qs.plan_handle) AS plan_handle, + ISNULL(st1.objectid, 0) AS object_id, + qs.query_hash AS query_hash, + qs.query_plan_hash AS query_plan_hash, + SUM(qs.execution_count) AS execution_count, + SUM(qs.total_elapsed_time) AS total_elapsed_time, + SUM(qs.total_worker_time) AS total_worker_time, + SUM(qs.total_logical_reads) AS total_logical_reads, + SUM(qs.total_physical_reads) AS total_physical_reads, + SUM(qs.total_logical_writes) AS total_logical_writes, + SUM(qs.total_rows) AS total_rows, + SUM(qs.total_grant_kb) AS total_grant_kb + FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 + WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, -60, GETDATE()) AND GETDATE() + GROUP BY st1.objectid, qs.query_hash, qs.query_plan_hash + ) + SELECT + qs.*, + SUBSTRING(st.text, + (stats.statement_start_offset / 2) + 1, + ((CASE stats.statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE stats.statement_end_offset + END - stats.statement_start_offset) / 2) + 1) AS query_text, + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name + FROM qstats AS qs + JOIN sys.dm_exec_query_stats AS stats + ON stats.plan_handle = qs.plan_handle + CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st; \ No newline at end of file diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index 7dcbc0df5851f..20da91dea8a26 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -1,6 +1,6 @@ ;WITH qstats AS ( - SELECT TOP 1000 + SELECT TOP (@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], max(qs.plan_handle) AS plan_handle, @@ -17,7 +17,7 @@ SUM(qs.total_grant_kb) AS total_grant_kb FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 - WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, -1200, GETDATE()) AND GETDATE() + WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @lookbackTime, GETDATE()) AND GETDATE() GROUP BY st1.objectid, qs.query_hash, qs.query_plan_hash ) SELECT From c8efaec35a8df964a7bef461098889265b4b0f67 Mon Sep 17 00:00:00 2001 From: cjk Date: Tue, 2 Dec 2025 13:39:01 +0000 Subject: [PATCH 17/49] Backout unintended merge --- receiver/postgresqlreceiver/documentation.md | 2 -- .../internal/metadata/generated_logs.go | 8 +++----- .../internal/metadata/generated_logs_test.go | 8 +------- receiver/postgresqlreceiver/metadata.yaml | 8 -------- receiver/postgresqlreceiver/scraper.go | 2 -- 5 files changed, 4 insertions(+), 24 deletions(-) diff --git a/receiver/postgresqlreceiver/documentation.md b/receiver/postgresqlreceiver/documentation.md index 38316fb9f4f20..a5376dd9388cf 100644 --- a/receiver/postgresqlreceiver/documentation.md +++ b/receiver/postgresqlreceiver/documentation.md @@ -422,8 +422,6 @@ query sample | postgresql.wait_event_type | The type of event for which the backend is waiting, if any; otherwise NULL. | Any Str | | postgresql.query_id | Identifier of this backend's most recent query. If state is active this field shows the identifier of the currently executing query. In all other states, it shows the identifier of last query that was executed. | Any Str | | postgresql.total_exec_time | Total time spent executing the statement, in delta milliseconds. | Any Double | -| postgresql.procedure_id | Identifier of the stored procedure. | Any Str | -| postgresql.procedure_name | Name of the stored procedure. | Any Str | ### db.server.top_query diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_logs.go b/receiver/postgresqlreceiver/internal/metadata/generated_logs.go index 467a6edb2a1f0..2c7e326e7817b 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_logs.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_logs.go @@ -18,7 +18,7 @@ type eventDbServerQuerySample struct { config EventConfig // event config provided by user. } -func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue string, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64, postgresqlProcedureIDAttributeValue string, postgresqlProcedureNameAttributeValue string) { +func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue string, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64) { if !e.config.Enabled { return } @@ -45,8 +45,6 @@ func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pc dp.Attributes().PutStr("postgresql.wait_event_type", postgresqlWaitEventTypeAttributeValue) dp.Attributes().PutStr("postgresql.query_id", postgresqlQueryIDAttributeValue) dp.Attributes().PutDouble("postgresql.total_exec_time", postgresqlTotalExecTimeAttributeValue) - dp.Attributes().PutStr("postgresql.procedure_id", postgresqlProcedureIDAttributeValue) - dp.Attributes().PutStr("postgresql.procedure_name", postgresqlProcedureNameAttributeValue) } @@ -251,8 +249,8 @@ func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs { } // RecordDbServerQuerySampleEvent adds a log record of db.server.query_sample event. -func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue AttributeDbSystemName, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64, postgresqlProcedureIDAttributeValue string, postgresqlProcedureNameAttributeValue string) { - lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, dbSystemNameAttributeValue.String(), dbNamespaceAttributeValue, dbQueryTextAttributeValue, userNameAttributeValue, postgresqlStateAttributeValue, postgresqlPidAttributeValue, postgresqlApplicationNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, postgresqlClientHostnameAttributeValue, postgresqlQueryStartAttributeValue, postgresqlWaitEventAttributeValue, postgresqlWaitEventTypeAttributeValue, postgresqlQueryIDAttributeValue, postgresqlTotalExecTimeAttributeValue, postgresqlProcedureIDAttributeValue, postgresqlProcedureNameAttributeValue) +func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, dbSystemNameAttributeValue AttributeDbSystemName, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, userNameAttributeValue string, postgresqlStateAttributeValue string, postgresqlPidAttributeValue int64, postgresqlApplicationNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, postgresqlClientHostnameAttributeValue string, postgresqlQueryStartAttributeValue string, postgresqlWaitEventAttributeValue string, postgresqlWaitEventTypeAttributeValue string, postgresqlQueryIDAttributeValue string, postgresqlTotalExecTimeAttributeValue float64) { + lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, dbSystemNameAttributeValue.String(), dbNamespaceAttributeValue, dbQueryTextAttributeValue, userNameAttributeValue, postgresqlStateAttributeValue, postgresqlPidAttributeValue, postgresqlApplicationNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, postgresqlClientHostnameAttributeValue, postgresqlQueryStartAttributeValue, postgresqlWaitEventAttributeValue, postgresqlWaitEventTypeAttributeValue, postgresqlQueryIDAttributeValue, postgresqlTotalExecTimeAttributeValue) } // RecordDbServerTopQueryEvent adds a log record of db.server.top_query event. diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go b/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go index 5f3d26d9bb1d9..3eec76a985dd8 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_logs_test.go @@ -132,7 +132,7 @@ func TestLogsBuilder(t *testing.T) { allEventsCount := 0 defaultEventsCount++ allEventsCount++ - lb.RecordDbServerQuerySampleEvent(ctx, timestamp, AttributeDbSystemNamePostgresql, "db.namespace-val", "db.query.text-val", "user.name-val", "postgresql.state-val", 14, "postgresql.application_name-val", "network.peer.address-val", 17, "postgresql.client_hostname-val", "postgresql.query_start-val", "postgresql.wait_event-val", "postgresql.wait_event_type-val", "postgresql.query_id-val", 26.100000, "postgresql.procedure_id-val", "postgresql.procedure_name-val") + lb.RecordDbServerQuerySampleEvent(ctx, timestamp, AttributeDbSystemNamePostgresql, "db.namespace-val", "db.query.text-val", "user.name-val", "postgresql.state-val", 14, "postgresql.application_name-val", "network.peer.address-val", 17, "postgresql.client_hostname-val", "postgresql.query_start-val", "postgresql.wait_event-val", "postgresql.wait_event_type-val", "postgresql.query_id-val", 26.100000) defaultEventsCount++ allEventsCount++ lb.RecordDbServerTopQueryEvent(ctx, timestamp, AttributeDbSystemNamePostgresql, "db.namespace-val", "db.query.text-val", 16, 15, 30, 26, 27, 30, 25, 28, "postgresql.queryid-val", "postgresql.rolname-val", 26.100000, 26.100000, "postgresql.query_plan-val") @@ -216,12 +216,6 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("postgresql.total_exec_time") assert.True(t, ok) assert.Equal(t, 26.100000, attrVal.Double()) - attrVal, ok = lr.Attributes().Get("postgresql.procedure_id") - assert.True(t, ok) - assert.Equal(t, "postgresql.procedure_id-val", attrVal.Str()) - attrVal, ok = lr.Attributes().Get("postgresql.procedure_name") - assert.True(t, ok) - assert.Equal(t, "postgresql.procedure_name-val", attrVal.Str()) case "db.server.top_query": assert.False(t, validatedEvents["db.server.top_query"], "Found a duplicate in the events slice: db.server.top_query") validatedEvents["db.server.top_query"] = true diff --git a/receiver/postgresqlreceiver/metadata.yaml b/receiver/postgresqlreceiver/metadata.yaml index e4ef5e6fb962f..4275c1dd70bf0 100644 --- a/receiver/postgresqlreceiver/metadata.yaml +++ b/receiver/postgresqlreceiver/metadata.yaml @@ -94,12 +94,6 @@ attributes: postgresql.pid: description: Process ID of this backend. type: int - postgresql.procedure_id: - description: Identifier of the stored procedure. - type: string - postgresql.procedure_name: - description: Name of the stored procedure. - type: string postgresql.query_id: description: Identifier of this backend's most recent query. If state is active this field shows the identifier of the currently executing query. In all other states, it shows the identifier of last query that was executed. type: string @@ -202,8 +196,6 @@ events: - postgresql.wait_event_type - postgresql.query_id - postgresql.total_exec_time - - postgresql.procedure_id - - postgresql.procedure_name db.server.top_query: enabled: true description: top query diff --git a/receiver/postgresqlreceiver/scraper.go b/receiver/postgresqlreceiver/scraper.go index 5b8d2164e2f1f..b06a4856e0e35 100644 --- a/receiver/postgresqlreceiver/scraper.go +++ b/receiver/postgresqlreceiver/scraper.go @@ -227,8 +227,6 @@ func (p *postgreSQLScraper) collectQuerySamples(ctx context.Context, dbClient cl atts[dbAttributePrefix+"wait_event_type"].(string), atts[dbAttributePrefix+"query_id"].(string), atts["duration"].(float64), - atts[dbAttributePrefix+"procedure_id"].(string), - atts[dbAttributePrefix+"procedure_name"].(string), ) } } From 7ba3c74aeab51b127f1953cd69af466f1d9d627c Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 17 Dec 2025 10:08:52 +0000 Subject: [PATCH 18/49] updating change log --- ...sqlserver-stored-procedure-monitoring.yaml | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 .chloggen/sqlserver-stored-procedure-monitoring.yaml diff --git a/.chloggen/sqlserver-stored-procedure-monitoring.yaml b/.chloggen/sqlserver-stored-procedure-monitoring.yaml deleted file mode 100644 index 7b2f6e03d325a..0000000000000 --- a/.chloggen/sqlserver-stored-procedure-monitoring.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# 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. receiver/filelog) -component: receiver/sqlserver - -# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). -note: adding sqlserver.procedure_id and sqlserver.procedure_name attributes to TopQuery and Sample Events - -# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. -issues: [44656] - -# (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: - -# 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] From f9531c39b36145e778a6431633ccda92e83a6ba8 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Mon, 5 Jan 2026 15:57:08 +0000 Subject: [PATCH 19/49] Fix for inaccurate query count being reported in postgresql query metrics collection --- receiver/sqlserverreceiver/scraper.go | 66 ++++++++++++------- receiver/sqlserverreceiver/scraper_test.go | 62 +++++++++++++++++ .../templates/dbQueryAndTextQuery.tmpl | 4 +- .../databaseTopQueryWithoutInstanceName.txt | 4 +- .../expectedQueryTextAndPlanQuery.yaml | 59 +++++++++++++++-- .../testdata/queryTextAndPlanQueryData.txt | 19 +++++- 6 files changed, 179 insertions(+), 35 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index a04218f62ca4a..7f52155afe69f 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -686,11 +686,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont 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 { - break - } - totalElapsedTimeVal := float64(totalElapsedTimeDiffsMicrosecond[i]) / 1_000_000 + totalElapsedTimeValDiff := float64(totalElapsedTimeDiffsMicrosecond[i]) / 1_000_000 // reporting human-readable query hash and query hash plan queryHashVal := hex.EncodeToString([]byte(row[queryHash])) @@ -707,28 +703,30 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont return obfuscated, nil }) + var cached bool + executionCountVal := s.retrieveValue(row, executionCount, &errs, retrieveInt) - cached, executionCountVal := s.cacheAndDiff(queryHashVal, queryPlanHashVal, executionCount, executionCountVal.(int64)) + cached, executionCountValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, executionCount, executionCountVal.(int64)) if !cached { - executionCountVal = int64(0) + executionCountValDiff = int64(0) } logicalReadsVal := s.retrieveValue(row, logicalReads, &errs, retrieveInt) - cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalReads, logicalReadsVal.(int64)) + cached, logicalReadsValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalReads, logicalReadsVal.(int64)) if !cached { - logicalReadsVal = int64(0) + logicalReadsValDiff = int64(0) } logicalWritesVal := s.retrieveValue(row, logicalWrites, &errs, retrieveInt) - cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalWrites, logicalWritesVal.(int64)) + cached, logicalWritesValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalWrites, logicalWritesVal.(int64)) if !cached { - logicalWritesVal = int64(0) + logicalWritesValDiff = int64(0) } physicalReadsVal := s.retrieveValue(row, physicalReads, &errs, retrieveInt) - cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, physicalReads, physicalReadsVal.(int64)) + cached, physicalReadsValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, physicalReads, physicalReadsVal.(int64)) if !cached { - physicalReadsVal = int64(0) + physicalReadsValDiff = int64(0) } queryPlanVal := s.retrieveValue(row, queryPlan, &errs, func(row sqlquery.StringMap, columnName string) (any, error) { @@ -736,22 +734,43 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) rowsReturnedVal := s.retrieveValue(row, rowsReturned, &errs, retrieveInt) - cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, rowsReturned, rowsReturnedVal.(int64)) + cached, rowsReturnedValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, rowsReturned, rowsReturnedVal.(int64)) if !cached { - rowsReturnedVal = int64(0) + rowsReturnedValDiff = int64(0) } totalGrantVal := s.retrieveValue(row, totalGrant, &errs, retrieveInt) - cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalGrant, totalGrantVal.(int64)) + cached, totalGrantValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalGrant, totalGrantVal.(int64)) if !cached { - totalGrantVal = int64(0) + totalGrantValDiff = int64(0) } - totalWorkerTimeVal := s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt) - cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalWorkerTime, totalWorkerTimeVal.(int64)) - totalWorkerTimeInSecVal := float64(0) + totalWorkerTimeVal := float64(s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt).(int64)) + cached, totalWorkerTimeValCached := s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalWorkerTime, int64(totalWorkerTimeVal)) + totalWorkerTimeValDiff := float64(0) if cached { - totalWorkerTimeInSecVal = float64(totalWorkerTimeVal.(int64)) / 1_000_000 + totalWorkerTimeValDiff = float64(totalWorkerTimeValCached) / 1_000_000 + } + + totalElapsedTimeVal := s.retrieveValue(row, totalElapsedTime, &errs, retrieveFloat).(float64) / 1_000_000 + + // If execution count in the db is 1 that would mean there is no past records to compare with. + // We just send down the metrics corresponding to that single execution as it is. + // If execution count is not 1 then we send down the delta values. + execCount := executionCountVal.(int64) + if execCount != 1 { + executionCountVal = executionCountValDiff + logicalReadsVal = logicalReadsValDiff + logicalWritesVal = logicalWritesValDiff + physicalReadsVal = physicalReadsValDiff + rowsReturnedVal = rowsReturnedValDiff + totalGrantVal = totalGrantValDiff + totalWorkerTimeVal = totalWorkerTimeValDiff + totalElapsedTimeVal = totalElapsedTimeValDiff + } + + if totalElapsedTimeVal == 0 { + continue } s.logger.Debug(fmt.Sprintf("QueryHash: %v, PlanHash: %v, DataRow: %v", queryHashVal, queryPlanHashVal, row)) @@ -768,7 +787,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont s.lb.RecordDbServerTopQueryEvent( context.Background(), timestamp, - totalWorkerTimeInSecVal, + totalWorkerTimeVal, queryTextVal.(string), executionCountVal.(int64), logicalReadsVal.(int64), @@ -813,13 +832,12 @@ func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, column s key := queryHash + "-" + queryPlanHash + "-" + column cached, ok := s.cache.Get(key) + s.cache.Add(key, val) if !ok { - s.cache.Add(key, val) return false, val } if val > cached { - s.cache.Add(key, val) return true, val - cached } diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index cbb751a447e2b..92bddbfaa245f 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -370,6 +370,68 @@ func (mc mockInvalidClient) QueryRows(context.Context, ...any) ([]sqlquery.Strin return queryResults, nil } +func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.Username = "sa" + cfg.Password = "password" + cfg.Port = 1433 + cfg.Server = "0.0.0.0" + cfg.MetricsBuilderConfig.ResourceAttributes.SqlserverInstanceName.Enabled = true + cfg.Events.DbServerTopQuery.Enabled = true + assert.NoError(t, cfg.Validate()) + + configureAllScraperMetricsAndEvents(cfg, false) + cfg.Events.DbServerTopQuery.Enabled = true + cfg.TopQueryCollection.CollectionInterval = cfg.ControllerConfig.CollectionInterval + + scrapers := setupSQLServerLogsScrapers(receivertest.NewNopSettings(metadata.Type), cfg) + assert.NotNil(t, scrapers) + + scraper := scrapers[0] + assert.NotNil(t, scraper.cache) + + const totalElapsedTime = "total_elapsed_time" + const rowsReturned = "total_rows" + const totalWorkerTime = "total_worker_time" + const logicalReads = "total_logical_reads" + const logicalWrites = "total_logical_writes" + const physicalReads = "total_physical_reads" + const executionCount = "execution_count" + const totalGrant = "total_grant_kb" + + scraper.client = mockClient{ + instanceName: scraper.config.InstanceName, + SQL: scraper.sqlQuery, + maxQuerySampleCount: 1000, + lookbackTime: 20, + topQueryCount: 200, + } + + actualLogs, err := scraper.ScrapeLogs(t.Context()) + assert.NoError(t, err) + + expectedFile := filepath.Join("testdata", "expectedQueryTextAndPlanQuery.yaml") + expectedLogs, _ := golden.ReadLogs(expectedFile) + + queryHash, _ := expectedLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get("sqlserver.query_hash") + planHash, _ := expectedLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get("sqlserver.query_plan_hash") + keyPrefix := queryHash.Str() + "-" + planHash.Str() + + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+totalElapsedTime)) + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+rowsReturned)) + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+totalWorkerTime)) + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+logicalReads)) + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+logicalWrites)) + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+physicalReads)) + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+executionCount)) + assert.True(t, scraper.cache.Contains(keyPrefix+"-"+totalGrant)) + + assert.True(t, actualLogs.LogRecordCount() == 1) + collectQueryHash, _ := actualLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get("sqlserver.query_hash") + assert.Equal(t, hex.EncodeToString([]byte("0x37849E874171E3F4")), collectQueryHash.Str(), "Metrics for the record with 1 execution_count in db should be reported always regardless of cache record presence") + +} + func TestQueryTextAndPlanQuery(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.Username = "sa" diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 7dddd011ac9fe..d59a29ac3888b 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -16,7 +16,6 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs - WHERE DATEADD(ms, last_elapsed_time / 1000, last_execution_time) > DATEADD(SECOND, @lookbackTime, GETDATE()) GROUP BY qs.query_hash, qs.query_plan_hash @@ -30,4 +29,5 @@ SELECT qs.*, FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st +WHERE DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time) > DATEADD(SECOND, @lookbackTime, GETDATE()); diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index 7dddd011ac9fe..d59a29ac3888b 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -16,7 +16,6 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs - WHERE DATEADD(ms, last_elapsed_time / 1000, last_execution_time) > DATEADD(SECOND, @lookbackTime, GETDATE()) GROUP BY qs.query_hash, qs.query_plan_hash @@ -30,4 +29,5 @@ SELECT qs.*, FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st +WHERE DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time) > DATEADD(SECOND, @lookbackTime, GETDATE()); diff --git a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml index 4042e9baab083..690af0a54bb32 100644 --- a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml @@ -4,15 +4,15 @@ resourceLogs: - key: host.name value: stringValue: 0.0.0.0 - - key: service.instance.id - value: - stringValue: 0.0.0.0:1433 - key: sqlserver.computer.name value: stringValue: DESKTOP-GHAEGRD - key: sqlserver.instance.name value: stringValue: sqlserver + - key: service.instance.id + value: + stringValue: 0.0.0.0:1433 scopeLogs: - logRecords: - attributes: @@ -63,9 +63,56 @@ resourceLogs: stringValue: microsoft.sql_server body: {} eventName: db.server.top_query - spanId: "" - timeUnixNano: "1749224037462260000" - traceId: "" + timeUnixNano: "1767627037097600000" + - attributes: + - key: sqlserver.total_worker_time + value: + doubleValue: 3845 + - key: db.query.text + value: + stringValue: with qstats SELECT TOP ( @topNValue ) REPLACE ( @@SERVERNAME, ? ), HOST_NAME ( ), MAX ( qs.plan_handle ), qs.query_hash, qs.query_plan_hash, SUM ( qs.execution_count ), SUM ( qs.total_elapsed_time ), SUM ( qs.total_worker_time ), SUM ( qs.total_logical_reads ), SUM ( qs.total_physical_reads ), SUM ( qs.total_logical_writes ), SUM ( qs.total_rows ), SUM ( qs.total_grant_kb ) FROM sys.dm_exec_query_stats WHERE qs.last_execution_time BETWEEN DATEADD ( SECOND, @granularity, GETDATE ( ) ) AND GETDATE ( ) GROUP BY qs.query_hash, qs.query_plan_hash ) SELECT qs.*, SUBSTRING ( st.text, ( stats.statement_start_offset / ? ) + ? ( ( CASE statement_end_offset WHEN ? THEN DATALENGTH ( st.text ) ELSE stats.statement_end_offset END - stats.statement_start_offset ) / ? ) + ? ), ISNULL ( qp.query_plan, ? ) FROM qstats INNER JOIN sys.dm_exec_query_stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan ( qs.query_plan_handle ) CROSS APPLY sys.dm_exec_sql_text ( qs.query_plan_handle ) + - key: sqlserver.execution_count + value: + intValue: "1" + - key: sqlserver.total_logical_reads + value: + intValue: "3" + - key: sqlserver.total_logical_writes + value: + intValue: "4" + - key: sqlserver.total_physical_reads + value: + intValue: "5" + - key: sqlserver.query_hash + value: + stringValue: "307833373834394538373431373145334634" + - key: sqlserver.query_plan + value: + stringValue: + - key: sqlserver.query_plan_hash + value: + stringValue: "307844333131323930393432394131423534" + - key: sqlserver.total_rows + value: + intValue: "2" + - key: sqlserver.total_elapsed_time + value: + doubleValue: 0.003846 + - key: sqlserver.total_grant_kb + value: + intValue: "3096" + - key: server.address + value: + stringValue: 0.0.0.0 + - key: server.port + value: + intValue: "1433" + - key: db.system.name + value: + stringValue: microsoft.sql_server + body: {} + eventName: db.server.top_query + timeUnixNano: "1767627037097600000" scope: name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver version: latest diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt index abd3e31b681f7..69dc183f9fb6b 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt @@ -15,5 +15,22 @@ "total_grant_kb": "3096", "query_text": "with qstats as (\n SELECT TOP(@topNValue)\n REPLACE(@@SERVERNAME,'\\',':') AS [sql_instance],\n HOST_NAME() AS [computer_name],\n MAX(qs.plan_handle) AS query_plan_handle,\n qs.query_hash AS query_hash,\n qs.query_plan_hash AS query_plan_hash,\n SUM(qs.execution_count) AS execution_count,\n SUM(qs.total_elapsed_time) AS total_elapsed_time,\n SUM(qs.total_worker_time) AS total_worker_time,\n SUM(qs.total_logical_reads) AS total_logical_reads,\n SUM(qs.total_physical_reads) AS total_physical_reads,\n SUM(qs.total_logical_writes) AS total_logical_writes,\n SUM(qs.total_rows) AS total_rows,\n SUM(qs.total_grant_kb) as total_grant_kb\n FROM sys.dm_exec_query_stats AS qs\n WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @granularity, GETDATE()) AND GETDATE()\n GROUP BY\n qs.query_hash,\n qs.query_plan_hash\n)\nSELECT qs.*,\n SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1,\n ((CASE statement_end_offset\n WHEN -1 THEN DATALENGTH(st.text)\n ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS text,\n ISNULL(qp.query_plan, '') AS query_plan\nFROM qstats AS qs\n INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle\n CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp\n CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st", "query_plan": "" - } + }, + { + "sql_instance": "sqlserver", + "computer_name": "DESKTOP-GHAEGRD", + "query_plan_handle": "0x06000100E2B3C02AA042A7001000000001000000000000000000000000000000000000000000000000000000", + "query_hash": "0x37849E874171E3F4", + "query_plan_hash": "0xD3112909429A1B54", + "execution_count": "1", + "total_elapsed_time": "3846", + "total_worker_time": "3845", + "total_logical_reads": "3", + "total_physical_reads": "5", + "total_logical_writes": "4", + "total_rows": "2", + "total_grant_kb": "3096", + "query_text": "with qstats as (\n SELECT TOP(@topNValue)\n REPLACE(@@SERVERNAME,'\\',':') AS [sql_instance],\n HOST_NAME() AS [computer_name],\n MAX(qs.plan_handle) AS query_plan_handle,\n qs.query_hash AS query_hash,\n qs.query_plan_hash AS query_plan_hash,\n SUM(qs.execution_count) AS execution_count,\n SUM(qs.total_elapsed_time) AS total_elapsed_time,\n SUM(qs.total_worker_time) AS total_worker_time,\n SUM(qs.total_logical_reads) AS total_logical_reads,\n SUM(qs.total_physical_reads) AS total_physical_reads,\n SUM(qs.total_logical_writes) AS total_logical_writes,\n SUM(qs.total_rows) AS total_rows,\n SUM(qs.total_grant_kb) as total_grant_kb\n FROM sys.dm_exec_query_stats AS qs\n WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @granularity, GETDATE()) AND GETDATE()\n GROUP BY\n qs.query_hash,\n qs.query_plan_hash\n)\nSELECT qs.*,\n SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1,\n ((CASE statement_end_offset\n WHEN -1 THEN DATALENGTH(st.text)\n ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS text,\n ISNULL(qp.query_plan, '') AS query_plan\nFROM qstats AS qs\n INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle\n CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp\n CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st", + "query_plan": "" + } ] From 42432a7a16031caf7c8a65a80418b56e23e77205 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Mon, 5 Jan 2026 16:31:21 +0000 Subject: [PATCH 20/49] change log --- ...postgresql_fix_for_inaccurate_metrics.yaml | 29 +++++++++++++++++++ receiver/sqlserverreceiver/scraper.go | 8 ++--- 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 .chloggen/postgresql_fix_for_inaccurate_metrics.yaml diff --git a/.chloggen/postgresql_fix_for_inaccurate_metrics.yaml b/.chloggen/postgresql_fix_for_inaccurate_metrics.yaml new file mode 100644 index 0000000000000..0fe003919275f --- /dev/null +++ b/.chloggen/postgresql_fix_for_inaccurate_metrics.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: bug_fix + +# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog) +component: receiver/postgresql + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Accuracy improvements for top-query metrics + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [] + +# (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: | + PostgreSQL metrics reporting is improved by reducing the warm-up delay and providing accurate insights sooner. + +# 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/scraper.go b/receiver/sqlserverreceiver/scraper.go index 7f52155afe69f..c7e515c4656b0 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -754,11 +754,11 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont totalElapsedTimeVal := s.retrieveValue(row, totalElapsedTime, &errs, retrieveFloat).(float64) / 1_000_000 - // If execution count in the db is 1 that would mean there is no past records to compare with. + // If execution count in the DB is 1 that would mean there is no past records to compare with to find a delta. // We just send down the metrics corresponding to that single execution as it is. - // If execution count is not 1 then we send down the delta values. - execCount := executionCountVal.(int64) - if execCount != 1 { + // If execution count in DB is not 1 then we send down the delta values. + execCountInDB := executionCountVal.(int64) + if execCountInDB != 1 { executionCountVal = executionCountValDiff logicalReadsVal = logicalReadsValDiff logicalWritesVal = logicalWritesValDiff From a06028f92499c0859254edb53b2fff24878e3780 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Mon, 5 Jan 2026 16:53:50 +0000 Subject: [PATCH 21/49] this fix is for sqlserver --- ...metrics.yaml => sqlserver_fix_for_inaccurate_metrics.yaml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .chloggen/{postgresql_fix_for_inaccurate_metrics.yaml => sqlserver_fix_for_inaccurate_metrics.yaml} (89%) diff --git a/.chloggen/postgresql_fix_for_inaccurate_metrics.yaml b/.chloggen/sqlserver_fix_for_inaccurate_metrics.yaml similarity index 89% rename from .chloggen/postgresql_fix_for_inaccurate_metrics.yaml rename to .chloggen/sqlserver_fix_for_inaccurate_metrics.yaml index 0fe003919275f..6fbbd4e573f38 100644 --- a/.chloggen/postgresql_fix_for_inaccurate_metrics.yaml +++ b/.chloggen/sqlserver_fix_for_inaccurate_metrics.yaml @@ -4,7 +4,7 @@ change_type: bug_fix # The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog) -component: receiver/postgresql +component: receiver/sqlserver # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: Accuracy improvements for top-query metrics @@ -16,7 +16,7 @@ issues: [] # These lines will be padded with 2 spaces and then inserted directly into the document. # Use pipe (|) for multiline entries. subtext: | - PostgreSQL metrics reporting is improved by reducing the warm-up delay and providing accurate insights sooner. + SQLServer metrics reporting is improved by reducing the warm-up delay and providing accurate insights sooner. # 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. From 98e2e15e75322326566c2706cea4f85f61ae7a84 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Mon, 5 Jan 2026 16:54:44 +0000 Subject: [PATCH 22/49] fill issue details --- .chloggen/sqlserver_fix_for_inaccurate_metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/sqlserver_fix_for_inaccurate_metrics.yaml b/.chloggen/sqlserver_fix_for_inaccurate_metrics.yaml index 6fbbd4e573f38..7fc9b3a3901ee 100644 --- a/.chloggen/sqlserver_fix_for_inaccurate_metrics.yaml +++ b/.chloggen/sqlserver_fix_for_inaccurate_metrics.yaml @@ -10,7 +10,7 @@ component: receiver/sqlserver note: Accuracy improvements for top-query metrics # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. -issues: [] +issues: [45228] # (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. From ca3a16a89763e8ecb3f2414a46a34f37b986f01e Mon Sep 17 00:00:00 2001 From: sreenathv Date: Tue, 6 Jan 2026 09:19:59 +0000 Subject: [PATCH 23/49] go generate --- receiver/sqlserverreceiver/documentation.md | 166 +++++++++--------- .../generated_package_test.go | 3 +- .../metadata/generated_config_test.go | 1 + .../internal/metadata/generated_logs.go | 1 - .../internal/metadata/generated_logs_test.go | 5 +- receiver/sqlserverreceiver/scraper_test.go | 1 - 6 files changed, 87 insertions(+), 90 deletions(-) diff --git a/receiver/sqlserverreceiver/documentation.md b/receiver/sqlserverreceiver/documentation.md index a018477cf1617..fcd433289df41 100644 --- a/receiver/sqlserverreceiver/documentation.md +++ b/receiver/sqlserverreceiver/documentation.md @@ -18,7 +18,7 @@ Number of batch requests received by SQL Server. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {requests}/s | Gauge | Double | Development | +| {requests}/s | Gauge | Double | development | ### sqlserver.batch.sql_compilation.rate @@ -26,7 +26,7 @@ Number of SQL compilations needed. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {compilations}/s | Gauge | Double | Development | +| {compilations}/s | Gauge | Double | development | ### sqlserver.batch.sql_recompilation.rate @@ -34,7 +34,7 @@ Number of SQL recompilations needed. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {compilations}/s | Gauge | Double | Development | +| {compilations}/s | Gauge | Double | development | ### sqlserver.lock.wait.rate @@ -42,7 +42,7 @@ Number of lock requests resulting in a wait. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {requests}/s | Gauge | Double | Development | +| {requests}/s | Gauge | Double | development | ### sqlserver.lock.wait_time.avg @@ -52,7 +52,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| ms | Gauge | Double | Development | +| ms | Gauge | Double | development | ### sqlserver.page.buffer_cache.hit_ratio @@ -60,7 +60,7 @@ Pages found in the buffer pool without having to read from disk. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| % | Gauge | Double | Development | +| % | Gauge | Double | development | ### sqlserver.page.checkpoint.flush.rate @@ -70,7 +70,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {pages}/s | Gauge | Double | Development | +| {pages}/s | Gauge | Double | development | ### sqlserver.page.lazy_write.rate @@ -80,7 +80,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {writes}/s | Gauge | Double | Development | +| {writes}/s | Gauge | Double | development | ### sqlserver.page.life_expectancy @@ -88,13 +88,13 @@ Time a page will stay in the buffer pool. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| s | Gauge | Int | Development | +| s | Gauge | Int | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| performance_counter.object_name | Category to which this counter belongs | Any Str | Recommended | +| performance_counter.object_name | Category to which this counter belongs | Any Str | false | ### sqlserver.page.operation.rate @@ -104,13 +104,13 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {operations}/s | Gauge | Double | Development | +| {operations}/s | Gauge | Double | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| type | The page operation types. | Str: ``read``, ``write`` | Recommended | +| type | The page operation types. | Str: ``read``, ``write`` | false | ### sqlserver.page.split.rate @@ -120,7 +120,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {pages}/s | Gauge | Double | Development | +| {pages}/s | Gauge | Double | development | ### sqlserver.transaction.rate @@ -130,7 +130,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {transactions}/s | Gauge | Double | Development | +| {transactions}/s | Gauge | Double | development | ### sqlserver.transaction.write.rate @@ -140,7 +140,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {transactions}/s | Gauge | Double | Development | +| {transactions}/s | Gauge | Double | development | ### sqlserver.transaction_log.flush.data.rate @@ -150,7 +150,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| By/s | Gauge | Double | Development | +| By/s | Gauge | Double | development | ### sqlserver.transaction_log.flush.rate @@ -160,7 +160,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {flushes}/s | Gauge | Double | Development | +| {flushes}/s | Gauge | Double | development | ### sqlserver.transaction_log.flush.wait.rate @@ -170,7 +170,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {commits}/s | Gauge | Double | Development | +| {commits}/s | Gauge | Double | development | ### sqlserver.transaction_log.growth.count @@ -180,7 +180,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {growths} | Sum | Int | Cumulative | true | Development | +| {growths} | Sum | Int | Cumulative | true | development | ### sqlserver.transaction_log.shrink.count @@ -190,7 +190,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {shrinks} | Sum | Int | Cumulative | true | Development | +| {shrinks} | Sum | Int | Cumulative | true | development | ### sqlserver.transaction_log.usage @@ -200,7 +200,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| % | Gauge | Int | Development | +| % | Gauge | Int | development | ### sqlserver.user.connection.count @@ -208,7 +208,7 @@ Number of users connected to the SQL Server. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {connections} | Gauge | Int | Development | +| {connections} | Gauge | Int | development | ## Optional Metrics @@ -226,7 +226,7 @@ Computer uptime. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {seconds} | Gauge | Int | Development | +| {seconds} | Gauge | Int | development | ### sqlserver.cpu.count @@ -234,7 +234,7 @@ Number of CPUs. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {CPUs} | Gauge | Int | Development | +| {CPUs} | Gauge | Int | development | ### sqlserver.database.backup_or_restore.rate @@ -242,7 +242,7 @@ Total number of backups/restores. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{backups_or_restores}/s” | Gauge | Double | Development | +| “{backups_or_restores}/s” | Gauge | Double | development | ### sqlserver.database.count @@ -252,13 +252,13 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {databases} | Gauge | Int | Development | +| {databases} | Gauge | Int | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| database.status | The current status of a database | Str: ``online``, ``restoring``, ``recovering``, ``pending_recovery``, ``suspect``, ``offline`` | Recommended | +| database.status | The current status of a database | Str: ``online``, ``restoring``, ``recovering``, ``pending_recovery``, ``suspect``, ``offline`` | false | ### sqlserver.database.execution.errors @@ -266,7 +266,7 @@ Number of execution errors. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{errors}” | Gauge | Int | Development | +| “{errors}” | Gauge | Int | development | ### sqlserver.database.full_scan.rate @@ -274,7 +274,7 @@ The number of unrestricted full table or index scans. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {scans}/s | Gauge | Double | Development | +| {scans}/s | Gauge | Double | development | ### sqlserver.database.io @@ -284,16 +284,16 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| By | Sum | Int | Cumulative | true | Development | +| By | Sum | Int | Cumulative | true | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| physical_filename | The physical filename of the file being monitored. | Any Str | Recommended | -| logical_filename | The logical filename of the file being monitored. | Any Str | Recommended | -| file_type | The type of file being monitored. | Any Str | Recommended | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | +| physical_filename | The physical filename of the file being monitored. | Any Str | false | +| logical_filename | The logical filename of the file being monitored. | Any Str | false | +| file_type | The type of file being monitored. | Any Str | false | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | ### sqlserver.database.latency @@ -303,16 +303,16 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| s | Sum | Double | Cumulative | true | Development | +| s | Sum | Double | Cumulative | true | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| physical_filename | The physical filename of the file being monitored. | Any Str | Recommended | -| logical_filename | The logical filename of the file being monitored. | Any Str | Recommended | -| file_type | The type of file being monitored. | Any Str | Recommended | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | +| physical_filename | The physical filename of the file being monitored. | Any Str | false | +| logical_filename | The logical filename of the file being monitored. | Any Str | false | +| file_type | The type of file being monitored. | Any Str | false | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | ### sqlserver.database.operations @@ -322,16 +322,16 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {operations} | Sum | Int | Cumulative | true | Development | +| {operations} | Sum | Int | Cumulative | true | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| physical_filename | The physical filename of the file being monitored. | Any Str | Recommended | -| logical_filename | The logical filename of the file being monitored. | Any Str | Recommended | -| file_type | The type of file being monitored. | Any Str | Recommended | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | +| physical_filename | The physical filename of the file being monitored. | Any Str | false | +| logical_filename | The logical filename of the file being monitored. | Any Str | false | +| file_type | The type of file being monitored. | Any Str | false | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | ### sqlserver.database.tempdb.space @@ -339,13 +339,13 @@ Total free space in temporary DB. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “KB” | Sum | Int | Cumulative | false | Development | +| “KB” | Sum | Int | Cumulative | false | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| tempdb.state | The status of the tempdb space usage. | Str: ``free``, ``used`` | Recommended | +| tempdb.state | The status of the tempdb space usage. | Str: ``free``, ``used`` | false | ### sqlserver.database.tempdb.version_store.size @@ -353,7 +353,7 @@ TempDB version store size. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “KB” | Gauge | Double | Development | +| “KB” | Gauge | Double | development | ### sqlserver.deadlock.rate @@ -361,7 +361,7 @@ Total number of deadlocks. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{deadlocks}/s” | Gauge | Double | Development | +| “{deadlocks}/s” | Gauge | Double | development | ### sqlserver.index.search.rate @@ -369,7 +369,7 @@ Total number of index searches. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{searches}/s” | Gauge | Double | Development | +| “{searches}/s” | Gauge | Double | development | ### sqlserver.lock.timeout.rate @@ -377,7 +377,7 @@ Total number of lock timeouts. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{timeouts}/s” | Gauge | Double | Development | +| “{timeouts}/s” | Gauge | Double | development | ### sqlserver.lock.wait.count @@ -387,7 +387,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {wait} | Sum | Int | Cumulative | true | Development | +| {wait} | Sum | Int | Cumulative | true | development | ### sqlserver.login.rate @@ -395,7 +395,7 @@ Total number of logins. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{logins}/s” | Gauge | Double | Development | +| “{logins}/s” | Gauge | Double | development | ### sqlserver.logout.rate @@ -403,7 +403,7 @@ Total number of logouts. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{logouts}/s” | Gauge | Double | Development | +| “{logouts}/s” | Gauge | Double | development | ### sqlserver.memory.grants.pending.count @@ -411,7 +411,7 @@ Total number of memory grants pending. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “{grants}” | Sum | Int | Cumulative | false | Development | +| “{grants}” | Sum | Int | Cumulative | false | development | ### sqlserver.memory.usage @@ -419,7 +419,7 @@ Total memory in use. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “KB” | Sum | Double | Cumulative | false | Development | +| “KB” | Sum | Double | Cumulative | false | development | ### sqlserver.os.wait.duration @@ -429,14 +429,14 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| s | Sum | Double | Cumulative | true | Development | +| s | Sum | Double | Cumulative | true | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| wait.category | Category of the reason for a wait. | Any Str | Recommended | -| wait.type | Type of the wait, view [WaitTypes documentation](https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql?view=sql-server-ver16#WaitTypes) for more information. | Any Str | Recommended | +| wait.category | Category of the reason for a wait. | Any Str | false | +| wait.type | Type of the wait, view [WaitTypes documentation](https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql?view=sql-server-ver16#WaitTypes) for more information. | Any Str | false | ### sqlserver.page.buffer_cache.free_list.stalls.rate @@ -444,7 +444,7 @@ Number of free list stalls. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{stalls}/s” | Gauge | Int | Development | +| “{stalls}/s” | Gauge | Int | development | ### sqlserver.page.lookup.rate @@ -452,7 +452,7 @@ Total number of page lookups. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{lookups}/s” | Gauge | Double | Development | +| “{lookups}/s” | Gauge | Double | development | ### sqlserver.processes.blocked @@ -462,7 +462,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {processes} | Gauge | Int | Development | +| {processes} | Gauge | Int | development | ### sqlserver.replica.data.rate @@ -470,13 +470,13 @@ Throughput rate of replica data. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| By/s | Gauge | Double | Development | +| By/s | Gauge | Double | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| replica.direction | The direction of flow of bytes for replica. | Str: ``transmit``, ``receive`` | Recommended | +| replica.direction | The direction of flow of bytes for replica. | Str: ``transmit``, ``receive`` | false | ### sqlserver.resource_pool.disk.operations @@ -486,13 +486,13 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {operations}/s | Gauge | Double | Development | +| {operations}/s | Gauge | Double | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | ### sqlserver.resource_pool.disk.throttled.read.rate @@ -502,7 +502,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {reads}/s | Gauge | Int | Development | +| {reads}/s | Gauge | Int | development | ### sqlserver.resource_pool.disk.throttled.write.rate @@ -512,7 +512,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {writes}/s | Gauge | Double | Development | +| {writes}/s | Gauge | Double | development | ### sqlserver.table.count @@ -520,14 +520,14 @@ The number of tables. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “{tables}” | Sum | Int | Cumulative | false | Development | +| “{tables}” | Sum | Int | Cumulative | false | development | #### Attributes -| Name | Description | Values | Requirement Level | +| Name | Description | Values | Optional | | ---- | ----------- | ------ | -------- | -| table.state | The state of the table. | Str: ``active``, ``inactive`` | Recommended | -| table.status | The status of the table. | Str: ``temporary``, ``permanent`` | Recommended | +| table.state | The state of the table. | Str: ``active``, ``inactive`` | false | +| table.status | The status of the table. | Str: ``temporary``, ``permanent`` | false | ### sqlserver.transaction.delay @@ -535,7 +535,7 @@ Time consumed in transaction delays. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| ms | Sum | Double | Cumulative | false | Development | +| ms | Sum | Double | Cumulative | false | development | ### sqlserver.transaction.mirror_write.rate @@ -543,7 +543,7 @@ Total number of mirror write transactions. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{transactions}/s” | Gauge | Double | Development | +| “{transactions}/s” | Gauge | Double | development | ## Default Events diff --git a/receiver/sqlserverreceiver/generated_package_test.go b/receiver/sqlserverreceiver/generated_package_test.go index ce3721511beef..86266f5f36641 100644 --- a/receiver/sqlserverreceiver/generated_package_test.go +++ b/receiver/sqlserverreceiver/generated_package_test.go @@ -3,9 +3,8 @@ package sqlserverreceiver import ( - "testing" - "go.uber.org/goleak" + "testing" ) func TestMain(m *testing.M) { diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go index 6f28022a39a7a..94073cc60b8c3 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go @@ -9,6 +9,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" ) diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go index 2224f758a2990..18d6d211b3788 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go @@ -4,7 +4,6 @@ package metadata import ( "context" - "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/filter" "go.opentelemetry.io/collector/pdata/pcommon" diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go index 53531d2d36903..fb265c194d02c 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go @@ -4,9 +4,6 @@ package metadata import ( "context" - "testing" - "time" - "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" @@ -14,6 +11,8 @@ import ( "go.opentelemetry.io/otel/trace" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" + "testing" + "time" ) type eventsTestDataSet int diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 92bddbfaa245f..1ae3a9f6b0556 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -429,7 +429,6 @@ func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testi assert.True(t, actualLogs.LogRecordCount() == 1) collectQueryHash, _ := actualLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get("sqlserver.query_hash") assert.Equal(t, hex.EncodeToString([]byte("0x37849E874171E3F4")), collectQueryHash.Str(), "Metrics for the record with 1 execution_count in db should be reported always regardless of cache record presence") - } func TestQueryTextAndPlanQuery(t *testing.T) { From e521c6fbb5114f7a59d870897af914c3c76c19e0 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Tue, 6 Jan 2026 09:39:38 +0000 Subject: [PATCH 24/49] fix lint --- receiver/sqlserverreceiver/scraper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 1ae3a9f6b0556..3fb6e410cd530 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -426,7 +426,7 @@ func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testi assert.True(t, scraper.cache.Contains(keyPrefix+"-"+executionCount)) assert.True(t, scraper.cache.Contains(keyPrefix+"-"+totalGrant)) - assert.True(t, actualLogs.LogRecordCount() == 1) + assert.Equal(t, 1, actualLogs.LogRecordCount()) collectQueryHash, _ := actualLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get("sqlserver.query_hash") assert.Equal(t, hex.EncodeToString([]byte("0x37849E874171E3F4")), collectQueryHash.Str(), "Metrics for the record with 1 execution_count in db should be reported always regardless of cache record presence") } From 3cf3d01b28ac7a6c4f40dc305696cbc99e8f4f94 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Tue, 6 Jan 2026 14:20:23 +0000 Subject: [PATCH 25/49] make gogci --- receiver/sqlserverreceiver/documentation.md | 166 +++++++++--------- .../generated_package_test.go | 3 +- .../metadata/generated_config_test.go | 1 - .../internal/metadata/generated_logs.go | 1 + .../internal/metadata/generated_logs_test.go | 5 +- 5 files changed, 89 insertions(+), 87 deletions(-) diff --git a/receiver/sqlserverreceiver/documentation.md b/receiver/sqlserverreceiver/documentation.md index fcd433289df41..a018477cf1617 100644 --- a/receiver/sqlserverreceiver/documentation.md +++ b/receiver/sqlserverreceiver/documentation.md @@ -18,7 +18,7 @@ Number of batch requests received by SQL Server. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {requests}/s | Gauge | Double | development | +| {requests}/s | Gauge | Double | Development | ### sqlserver.batch.sql_compilation.rate @@ -26,7 +26,7 @@ Number of SQL compilations needed. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {compilations}/s | Gauge | Double | development | +| {compilations}/s | Gauge | Double | Development | ### sqlserver.batch.sql_recompilation.rate @@ -34,7 +34,7 @@ Number of SQL recompilations needed. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {compilations}/s | Gauge | Double | development | +| {compilations}/s | Gauge | Double | Development | ### sqlserver.lock.wait.rate @@ -42,7 +42,7 @@ Number of lock requests resulting in a wait. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {requests}/s | Gauge | Double | development | +| {requests}/s | Gauge | Double | Development | ### sqlserver.lock.wait_time.avg @@ -52,7 +52,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| ms | Gauge | Double | development | +| ms | Gauge | Double | Development | ### sqlserver.page.buffer_cache.hit_ratio @@ -60,7 +60,7 @@ Pages found in the buffer pool without having to read from disk. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| % | Gauge | Double | development | +| % | Gauge | Double | Development | ### sqlserver.page.checkpoint.flush.rate @@ -70,7 +70,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {pages}/s | Gauge | Double | development | +| {pages}/s | Gauge | Double | Development | ### sqlserver.page.lazy_write.rate @@ -80,7 +80,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {writes}/s | Gauge | Double | development | +| {writes}/s | Gauge | Double | Development | ### sqlserver.page.life_expectancy @@ -88,13 +88,13 @@ Time a page will stay in the buffer pool. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| s | Gauge | Int | development | +| s | Gauge | Int | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| performance_counter.object_name | Category to which this counter belongs | Any Str | false | +| performance_counter.object_name | Category to which this counter belongs | Any Str | Recommended | ### sqlserver.page.operation.rate @@ -104,13 +104,13 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {operations}/s | Gauge | Double | development | +| {operations}/s | Gauge | Double | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| type | The page operation types. | Str: ``read``, ``write`` | false | +| type | The page operation types. | Str: ``read``, ``write`` | Recommended | ### sqlserver.page.split.rate @@ -120,7 +120,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {pages}/s | Gauge | Double | development | +| {pages}/s | Gauge | Double | Development | ### sqlserver.transaction.rate @@ -130,7 +130,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {transactions}/s | Gauge | Double | development | +| {transactions}/s | Gauge | Double | Development | ### sqlserver.transaction.write.rate @@ -140,7 +140,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {transactions}/s | Gauge | Double | development | +| {transactions}/s | Gauge | Double | Development | ### sqlserver.transaction_log.flush.data.rate @@ -150,7 +150,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| By/s | Gauge | Double | development | +| By/s | Gauge | Double | Development | ### sqlserver.transaction_log.flush.rate @@ -160,7 +160,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {flushes}/s | Gauge | Double | development | +| {flushes}/s | Gauge | Double | Development | ### sqlserver.transaction_log.flush.wait.rate @@ -170,7 +170,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {commits}/s | Gauge | Double | development | +| {commits}/s | Gauge | Double | Development | ### sqlserver.transaction_log.growth.count @@ -180,7 +180,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {growths} | Sum | Int | Cumulative | true | development | +| {growths} | Sum | Int | Cumulative | true | Development | ### sqlserver.transaction_log.shrink.count @@ -190,7 +190,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {shrinks} | Sum | Int | Cumulative | true | development | +| {shrinks} | Sum | Int | Cumulative | true | Development | ### sqlserver.transaction_log.usage @@ -200,7 +200,7 @@ This metric is only available when running on Windows. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| % | Gauge | Int | development | +| % | Gauge | Int | Development | ### sqlserver.user.connection.count @@ -208,7 +208,7 @@ Number of users connected to the SQL Server. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {connections} | Gauge | Int | development | +| {connections} | Gauge | Int | Development | ## Optional Metrics @@ -226,7 +226,7 @@ Computer uptime. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {seconds} | Gauge | Int | development | +| {seconds} | Gauge | Int | Development | ### sqlserver.cpu.count @@ -234,7 +234,7 @@ Number of CPUs. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {CPUs} | Gauge | Int | development | +| {CPUs} | Gauge | Int | Development | ### sqlserver.database.backup_or_restore.rate @@ -242,7 +242,7 @@ Total number of backups/restores. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{backups_or_restores}/s” | Gauge | Double | development | +| “{backups_or_restores}/s” | Gauge | Double | Development | ### sqlserver.database.count @@ -252,13 +252,13 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {databases} | Gauge | Int | development | +| {databases} | Gauge | Int | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| database.status | The current status of a database | Str: ``online``, ``restoring``, ``recovering``, ``pending_recovery``, ``suspect``, ``offline`` | false | +| database.status | The current status of a database | Str: ``online``, ``restoring``, ``recovering``, ``pending_recovery``, ``suspect``, ``offline`` | Recommended | ### sqlserver.database.execution.errors @@ -266,7 +266,7 @@ Number of execution errors. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{errors}” | Gauge | Int | development | +| “{errors}” | Gauge | Int | Development | ### sqlserver.database.full_scan.rate @@ -274,7 +274,7 @@ The number of unrestricted full table or index scans. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {scans}/s | Gauge | Double | development | +| {scans}/s | Gauge | Double | Development | ### sqlserver.database.io @@ -284,16 +284,16 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| By | Sum | Int | Cumulative | true | development | +| By | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| physical_filename | The physical filename of the file being monitored. | Any Str | false | -| logical_filename | The logical filename of the file being monitored. | Any Str | false | -| file_type | The type of file being monitored. | Any Str | false | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | +| physical_filename | The physical filename of the file being monitored. | Any Str | Recommended | +| logical_filename | The logical filename of the file being monitored. | Any Str | Recommended | +| file_type | The type of file being monitored. | Any Str | Recommended | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | ### sqlserver.database.latency @@ -303,16 +303,16 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| s | Sum | Double | Cumulative | true | development | +| s | Sum | Double | Cumulative | true | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| physical_filename | The physical filename of the file being monitored. | Any Str | false | -| logical_filename | The logical filename of the file being monitored. | Any Str | false | -| file_type | The type of file being monitored. | Any Str | false | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | +| physical_filename | The physical filename of the file being monitored. | Any Str | Recommended | +| logical_filename | The logical filename of the file being monitored. | Any Str | Recommended | +| file_type | The type of file being monitored. | Any Str | Recommended | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | ### sqlserver.database.operations @@ -322,16 +322,16 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {operations} | Sum | Int | Cumulative | true | development | +| {operations} | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| physical_filename | The physical filename of the file being monitored. | Any Str | false | -| logical_filename | The logical filename of the file being monitored. | Any Str | false | -| file_type | The type of file being monitored. | Any Str | false | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | +| physical_filename | The physical filename of the file being monitored. | Any Str | Recommended | +| logical_filename | The logical filename of the file being monitored. | Any Str | Recommended | +| file_type | The type of file being monitored. | Any Str | Recommended | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | ### sqlserver.database.tempdb.space @@ -339,13 +339,13 @@ Total free space in temporary DB. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “KB” | Sum | Int | Cumulative | false | development | +| “KB” | Sum | Int | Cumulative | false | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| tempdb.state | The status of the tempdb space usage. | Str: ``free``, ``used`` | false | +| tempdb.state | The status of the tempdb space usage. | Str: ``free``, ``used`` | Recommended | ### sqlserver.database.tempdb.version_store.size @@ -353,7 +353,7 @@ TempDB version store size. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “KB” | Gauge | Double | development | +| “KB” | Gauge | Double | Development | ### sqlserver.deadlock.rate @@ -361,7 +361,7 @@ Total number of deadlocks. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{deadlocks}/s” | Gauge | Double | development | +| “{deadlocks}/s” | Gauge | Double | Development | ### sqlserver.index.search.rate @@ -369,7 +369,7 @@ Total number of index searches. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{searches}/s” | Gauge | Double | development | +| “{searches}/s” | Gauge | Double | Development | ### sqlserver.lock.timeout.rate @@ -377,7 +377,7 @@ Total number of lock timeouts. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{timeouts}/s” | Gauge | Double | development | +| “{timeouts}/s” | Gauge | Double | Development | ### sqlserver.lock.wait.count @@ -387,7 +387,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| {wait} | Sum | Int | Cumulative | true | development | +| {wait} | Sum | Int | Cumulative | true | Development | ### sqlserver.login.rate @@ -395,7 +395,7 @@ Total number of logins. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{logins}/s” | Gauge | Double | development | +| “{logins}/s” | Gauge | Double | Development | ### sqlserver.logout.rate @@ -403,7 +403,7 @@ Total number of logouts. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{logouts}/s” | Gauge | Double | development | +| “{logouts}/s” | Gauge | Double | Development | ### sqlserver.memory.grants.pending.count @@ -411,7 +411,7 @@ Total number of memory grants pending. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “{grants}” | Sum | Int | Cumulative | false | development | +| “{grants}” | Sum | Int | Cumulative | false | Development | ### sqlserver.memory.usage @@ -419,7 +419,7 @@ Total memory in use. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “KB” | Sum | Double | Cumulative | false | development | +| “KB” | Sum | Double | Cumulative | false | Development | ### sqlserver.os.wait.duration @@ -429,14 +429,14 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| s | Sum | Double | Cumulative | true | development | +| s | Sum | Double | Cumulative | true | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| wait.category | Category of the reason for a wait. | Any Str | false | -| wait.type | Type of the wait, view [WaitTypes documentation](https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql?view=sql-server-ver16#WaitTypes) for more information. | Any Str | false | +| wait.category | Category of the reason for a wait. | Any Str | Recommended | +| wait.type | Type of the wait, view [WaitTypes documentation](https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql?view=sql-server-ver16#WaitTypes) for more information. | Any Str | Recommended | ### sqlserver.page.buffer_cache.free_list.stalls.rate @@ -444,7 +444,7 @@ Number of free list stalls. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{stalls}/s” | Gauge | Int | development | +| “{stalls}/s” | Gauge | Int | Development | ### sqlserver.page.lookup.rate @@ -452,7 +452,7 @@ Total number of page lookups. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{lookups}/s” | Gauge | Double | development | +| “{lookups}/s” | Gauge | Double | Development | ### sqlserver.processes.blocked @@ -462,7 +462,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {processes} | Gauge | Int | development | +| {processes} | Gauge | Int | Development | ### sqlserver.replica.data.rate @@ -470,13 +470,13 @@ Throughput rate of replica data. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| By/s | Gauge | Double | development | +| By/s | Gauge | Double | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| replica.direction | The direction of flow of bytes for replica. | Str: ``transmit``, ``receive`` | false | +| replica.direction | The direction of flow of bytes for replica. | Str: ``transmit``, ``receive`` | Recommended | ### sqlserver.resource_pool.disk.operations @@ -486,13 +486,13 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {operations}/s | Gauge | Double | development | +| {operations}/s | Gauge | Double | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | false | +| direction | The direction of flow of bytes or operations. | Str: ``read``, ``write`` | Recommended | ### sqlserver.resource_pool.disk.throttled.read.rate @@ -502,7 +502,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {reads}/s | Gauge | Int | development | +| {reads}/s | Gauge | Int | Development | ### sqlserver.resource_pool.disk.throttled.write.rate @@ -512,7 +512,7 @@ This metric is only available when the receiver is configured to directly connec | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| {writes}/s | Gauge | Double | development | +| {writes}/s | Gauge | Double | Development | ### sqlserver.table.count @@ -520,14 +520,14 @@ The number of tables. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| “{tables}” | Sum | Int | Cumulative | false | development | +| “{tables}” | Sum | Int | Cumulative | false | Development | #### Attributes -| Name | Description | Values | Optional | +| Name | Description | Values | Requirement Level | | ---- | ----------- | ------ | -------- | -| table.state | The state of the table. | Str: ``active``, ``inactive`` | false | -| table.status | The status of the table. | Str: ``temporary``, ``permanent`` | false | +| table.state | The state of the table. | Str: ``active``, ``inactive`` | Recommended | +| table.status | The status of the table. | Str: ``temporary``, ``permanent`` | Recommended | ### sqlserver.transaction.delay @@ -535,7 +535,7 @@ Time consumed in transaction delays. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | | ---- | ----------- | ---------- | ----------------------- | --------- | --------- | -| ms | Sum | Double | Cumulative | false | development | +| ms | Sum | Double | Cumulative | false | Development | ### sqlserver.transaction.mirror_write.rate @@ -543,7 +543,7 @@ Total number of mirror write transactions. | Unit | Metric Type | Value Type | Stability | | ---- | ----------- | ---------- | --------- | -| “{transactions}/s” | Gauge | Double | development | +| “{transactions}/s” | Gauge | Double | Development | ## Default Events diff --git a/receiver/sqlserverreceiver/generated_package_test.go b/receiver/sqlserverreceiver/generated_package_test.go index 86266f5f36641..ce3721511beef 100644 --- a/receiver/sqlserverreceiver/generated_package_test.go +++ b/receiver/sqlserverreceiver/generated_package_test.go @@ -3,8 +3,9 @@ package sqlserverreceiver import ( - "go.uber.org/goleak" "testing" + + "go.uber.org/goleak" ) func TestMain(m *testing.M) { diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go index 94073cc60b8c3..6f28022a39a7a 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_config_test.go @@ -9,7 +9,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" ) diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go index 18d6d211b3788..2224f758a2990 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go @@ -4,6 +4,7 @@ package metadata import ( "context" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/filter" "go.opentelemetry.io/collector/pdata/pcommon" diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go index fb265c194d02c..53531d2d36903 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go @@ -4,6 +4,9 @@ package metadata import ( "context" + "testing" + "time" + "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" @@ -11,8 +14,6 @@ import ( "go.opentelemetry.io/otel/trace" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" - "testing" - "time" ) type eventsTestDataSet int From 87935cd28596509ac01a40b922fee84820ac11af Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 7 Jan 2026 14:41:20 +0000 Subject: [PATCH 26/49] tweak to samples sql --- .../templates/sqlServerQuerySample.tmpl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 96774ccaf08a9..c4cd34f97f892 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -11,14 +11,14 @@ SELECT TOP(@top) ISNULL(s.host_name, '') AS host_name, r.command, SUBSTRING(o.TEXT, (r.statement_start_offset / 2) + 1, ( - ( - CASE r.statement_end_offset - WHEN - 1 - THEN DATALENGTH(o.TEXT) - ELSE r.statement_end_offset - END - r.statement_start_offset - ) / 2 - ) + 1) AS statement_text, + ( + CASE r.statement_end_offset + WHEN - 1 + THEN DATALENGTH(o.TEXT) + ELSE r.statement_end_offset + END - r.statement_start_offset + ) / 2 + ) + 1) AS statement_text, r.blocking_session_id, ISNULL(r.wait_type, '') AS wait_type, r.wait_time, From 9433fdf6c6c64d948a7b75b2712ab0b4e5d4741d Mon Sep 17 00:00:00 2001 From: sreenathv Date: Wed, 7 Jan 2026 17:39:18 +0000 Subject: [PATCH 27/49] Revert checks around sending metrics for countInDb=1, since it's addressing a negligable edge case. --- receiver/sqlserverreceiver/scraper.go | 56 ++++++------------ receiver/sqlserverreceiver/scraper_test.go | 42 ++++++++----- .../expectedQueryTextAndPlanQuery.yaml | 59 ++----------------- .../testdata/queryTextAndPlanQueryData.txt | 19 +----- 4 files changed, 54 insertions(+), 122 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index c7e515c4656b0..ffa8bb11a9507 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -686,8 +686,6 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont timestamp := pcommon.NewTimestampFromTime(now) s.lastExecutionTimestamp = now for i, row := range rows { - totalElapsedTimeValDiff := float64(totalElapsedTimeDiffsMicrosecond[i]) / 1_000_000 - // reporting human-readable query hash and query hash plan queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) @@ -706,27 +704,27 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont var cached bool executionCountVal := s.retrieveValue(row, executionCount, &errs, retrieveInt) - cached, executionCountValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, executionCount, executionCountVal.(int64)) + cached, executionCountVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, executionCount, executionCountVal.(int64)) if !cached { - executionCountValDiff = int64(0) + executionCountVal = int64(0) } logicalReadsVal := s.retrieveValue(row, logicalReads, &errs, retrieveInt) - cached, logicalReadsValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalReads, logicalReadsVal.(int64)) + cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalReads, logicalReadsVal.(int64)) if !cached { - logicalReadsValDiff = int64(0) + logicalReadsVal = int64(0) } logicalWritesVal := s.retrieveValue(row, logicalWrites, &errs, retrieveInt) - cached, logicalWritesValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalWrites, logicalWritesVal.(int64)) + cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalWrites, logicalWritesVal.(int64)) if !cached { - logicalWritesValDiff = int64(0) + logicalWritesVal = int64(0) } physicalReadsVal := s.retrieveValue(row, physicalReads, &errs, retrieveInt) - cached, physicalReadsValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, physicalReads, physicalReadsVal.(int64)) + cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, physicalReads, physicalReadsVal.(int64)) if !cached { - physicalReadsValDiff = int64(0) + physicalReadsVal = int64(0) } queryPlanVal := s.retrieveValue(row, queryPlan, &errs, func(row sqlquery.StringMap, columnName string) (any, error) { @@ -734,42 +732,26 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) rowsReturnedVal := s.retrieveValue(row, rowsReturned, &errs, retrieveInt) - cached, rowsReturnedValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, rowsReturned, rowsReturnedVal.(int64)) + cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, rowsReturned, rowsReturnedVal.(int64)) if !cached { - rowsReturnedValDiff = int64(0) + rowsReturnedVal = int64(0) } totalGrantVal := s.retrieveValue(row, totalGrant, &errs, retrieveInt) - cached, totalGrantValDiff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalGrant, totalGrantVal.(int64)) + cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalGrant, totalGrantVal.(int64)) if !cached { - totalGrantValDiff = int64(0) + totalGrantVal = int64(0) } - totalWorkerTimeVal := float64(s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt).(int64)) - cached, totalWorkerTimeValCached := s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalWorkerTime, int64(totalWorkerTimeVal)) - totalWorkerTimeValDiff := float64(0) + totalWorkerTimeVal := s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt) + cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalWorkerTime, totalWorkerTimeVal.(int64)) + totalWorkerTimeInSecVal := float64(0) if cached { - totalWorkerTimeValDiff = float64(totalWorkerTimeValCached) / 1_000_000 - } - - totalElapsedTimeVal := s.retrieveValue(row, totalElapsedTime, &errs, retrieveFloat).(float64) / 1_000_000 - - // If execution count in the DB is 1 that would mean there is no past records to compare with to find a delta. - // We just send down the metrics corresponding to that single execution as it is. - // If execution count in DB is not 1 then we send down the delta values. - execCountInDB := executionCountVal.(int64) - if execCountInDB != 1 { - executionCountVal = executionCountValDiff - logicalReadsVal = logicalReadsValDiff - logicalWritesVal = logicalWritesValDiff - physicalReadsVal = physicalReadsValDiff - rowsReturnedVal = rowsReturnedValDiff - totalGrantVal = totalGrantValDiff - totalWorkerTimeVal = totalWorkerTimeValDiff - totalElapsedTimeVal = totalElapsedTimeValDiff + totalWorkerTimeInSecVal = float64(totalWorkerTimeVal.(int64)) / 1_000_000 } - if totalElapsedTimeVal == 0 { + totalElapsedTimeVal := float64(totalElapsedTimeDiffsMicrosecond[i]) / 1_000_000 + if totalElapsedTimeVal == 0 || executionCountVal == 0 { continue } @@ -787,7 +769,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont s.lb.RecordDbServerTopQueryEvent( context.Background(), timestamp, - totalWorkerTimeVal, + totalWorkerTimeInSecVal, queryTextVal.(string), executionCountVal.(int64), logicalReadsVal.(int64), diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 3fb6e410cd530..b84775492cae7 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -407,8 +407,7 @@ func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testi topQueryCount: 200, } - actualLogs, err := scraper.ScrapeLogs(t.Context()) - assert.NoError(t, err) + scraper.ScrapeLogs(t.Context()) expectedFile := filepath.Join("testdata", "expectedQueryTextAndPlanQuery.yaml") expectedLogs, _ := golden.ReadLogs(expectedFile) @@ -417,18 +416,33 @@ func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testi planHash, _ := expectedLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get("sqlserver.query_plan_hash") keyPrefix := queryHash.Str() + "-" + planHash.Str() - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+totalElapsedTime)) - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+rowsReturned)) - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+totalWorkerTime)) - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+logicalReads)) - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+logicalWrites)) - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+physicalReads)) - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+executionCount)) - assert.True(t, scraper.cache.Contains(keyPrefix+"-"+totalGrant)) - - assert.Equal(t, 1, actualLogs.LogRecordCount()) - collectQueryHash, _ := actualLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Attributes().Get("sqlserver.query_hash") - assert.Equal(t, hex.EncodeToString([]byte("0x37849E874171E3F4")), collectQueryHash.Str(), "Metrics for the record with 1 execution_count in db should be reported always regardless of cache record presence") + tetValue, ok := scraper.cache.Get(keyPrefix + "-" + totalElapsedTime) + assert.True(t, ok, "Expected to find elapsed time in cache right after the first collection") + assert.Equal(t, 3846, int(tetValue)) + + rtValue, ok := scraper.cache.Get(keyPrefix + "-" + rowsReturned) + assert.True(t, ok, "Expected to find rowsReturned time in cache right after the first collection") + assert.Equal(t, 2, int(rtValue)) + + twtValue, ok := scraper.cache.Get(keyPrefix + "-" + totalWorkerTime) + assert.True(t, ok, "Expected to find totalWorkerTime time in cache right after the first collection") + assert.Equal(t, 3845, int(twtValue)) + + lrValue, ok := scraper.cache.Get(keyPrefix + "-" + logicalReads) + assert.True(t, ok, "Expected to find logicalReads time in cache right after the first collection") + assert.Equal(t, 3, int(lrValue)) + + prValue, ok := scraper.cache.Get(keyPrefix + "-" + physicalReads) + assert.True(t, ok, "Expected to find physicalReads time in cache right after the first collection") + assert.Equal(t, 5, int(prValue)) + + ecValue, ok := scraper.cache.Get(keyPrefix + "-" + executionCount) + assert.True(t, ok, "Expected to find executionCount time in cache right after the first collection") + assert.Equal(t, 6, int(ecValue)) + + tgValue, ok := scraper.cache.Get(keyPrefix + "-" + totalGrant) + assert.True(t, ok, "Expected to find totalGrant time in cache right after the first collection") + assert.Equal(t, 3096, int(tgValue)) } func TestQueryTextAndPlanQuery(t *testing.T) { diff --git a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml index 690af0a54bb32..4042e9baab083 100644 --- a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml @@ -4,15 +4,15 @@ resourceLogs: - key: host.name value: stringValue: 0.0.0.0 + - key: service.instance.id + value: + stringValue: 0.0.0.0:1433 - key: sqlserver.computer.name value: stringValue: DESKTOP-GHAEGRD - key: sqlserver.instance.name value: stringValue: sqlserver - - key: service.instance.id - value: - stringValue: 0.0.0.0:1433 scopeLogs: - logRecords: - attributes: @@ -63,56 +63,9 @@ resourceLogs: stringValue: microsoft.sql_server body: {} eventName: db.server.top_query - timeUnixNano: "1767627037097600000" - - attributes: - - key: sqlserver.total_worker_time - value: - doubleValue: 3845 - - key: db.query.text - value: - stringValue: with qstats SELECT TOP ( @topNValue ) REPLACE ( @@SERVERNAME, ? ), HOST_NAME ( ), MAX ( qs.plan_handle ), qs.query_hash, qs.query_plan_hash, SUM ( qs.execution_count ), SUM ( qs.total_elapsed_time ), SUM ( qs.total_worker_time ), SUM ( qs.total_logical_reads ), SUM ( qs.total_physical_reads ), SUM ( qs.total_logical_writes ), SUM ( qs.total_rows ), SUM ( qs.total_grant_kb ) FROM sys.dm_exec_query_stats WHERE qs.last_execution_time BETWEEN DATEADD ( SECOND, @granularity, GETDATE ( ) ) AND GETDATE ( ) GROUP BY qs.query_hash, qs.query_plan_hash ) SELECT qs.*, SUBSTRING ( st.text, ( stats.statement_start_offset / ? ) + ? ( ( CASE statement_end_offset WHEN ? THEN DATALENGTH ( st.text ) ELSE stats.statement_end_offset END - stats.statement_start_offset ) / ? ) + ? ), ISNULL ( qp.query_plan, ? ) FROM qstats INNER JOIN sys.dm_exec_query_stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan ( qs.query_plan_handle ) CROSS APPLY sys.dm_exec_sql_text ( qs.query_plan_handle ) - - key: sqlserver.execution_count - value: - intValue: "1" - - key: sqlserver.total_logical_reads - value: - intValue: "3" - - key: sqlserver.total_logical_writes - value: - intValue: "4" - - key: sqlserver.total_physical_reads - value: - intValue: "5" - - key: sqlserver.query_hash - value: - stringValue: "307833373834394538373431373145334634" - - key: sqlserver.query_plan - value: - stringValue: - - key: sqlserver.query_plan_hash - value: - stringValue: "307844333131323930393432394131423534" - - key: sqlserver.total_rows - value: - intValue: "2" - - key: sqlserver.total_elapsed_time - value: - doubleValue: 0.003846 - - key: sqlserver.total_grant_kb - value: - intValue: "3096" - - key: server.address - value: - stringValue: 0.0.0.0 - - key: server.port - value: - intValue: "1433" - - key: db.system.name - value: - stringValue: microsoft.sql_server - body: {} - eventName: db.server.top_query - timeUnixNano: "1767627037097600000" + spanId: "" + timeUnixNano: "1749224037462260000" + traceId: "" scope: name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver version: latest diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt index 69dc183f9fb6b..abd3e31b681f7 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt @@ -15,22 +15,5 @@ "total_grant_kb": "3096", "query_text": "with qstats as (\n SELECT TOP(@topNValue)\n REPLACE(@@SERVERNAME,'\\',':') AS [sql_instance],\n HOST_NAME() AS [computer_name],\n MAX(qs.plan_handle) AS query_plan_handle,\n qs.query_hash AS query_hash,\n qs.query_plan_hash AS query_plan_hash,\n SUM(qs.execution_count) AS execution_count,\n SUM(qs.total_elapsed_time) AS total_elapsed_time,\n SUM(qs.total_worker_time) AS total_worker_time,\n SUM(qs.total_logical_reads) AS total_logical_reads,\n SUM(qs.total_physical_reads) AS total_physical_reads,\n SUM(qs.total_logical_writes) AS total_logical_writes,\n SUM(qs.total_rows) AS total_rows,\n SUM(qs.total_grant_kb) as total_grant_kb\n FROM sys.dm_exec_query_stats AS qs\n WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @granularity, GETDATE()) AND GETDATE()\n GROUP BY\n qs.query_hash,\n qs.query_plan_hash\n)\nSELECT qs.*,\n SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1,\n ((CASE statement_end_offset\n WHEN -1 THEN DATALENGTH(st.text)\n ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS text,\n ISNULL(qp.query_plan, '') AS query_plan\nFROM qstats AS qs\n INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle\n CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp\n CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st", "query_plan": "" - }, - { - "sql_instance": "sqlserver", - "computer_name": "DESKTOP-GHAEGRD", - "query_plan_handle": "0x06000100E2B3C02AA042A7001000000001000000000000000000000000000000000000000000000000000000", - "query_hash": "0x37849E874171E3F4", - "query_plan_hash": "0xD3112909429A1B54", - "execution_count": "1", - "total_elapsed_time": "3846", - "total_worker_time": "3845", - "total_logical_reads": "3", - "total_physical_reads": "5", - "total_logical_writes": "4", - "total_rows": "2", - "total_grant_kb": "3096", - "query_text": "with qstats as (\n SELECT TOP(@topNValue)\n REPLACE(@@SERVERNAME,'\\',':') AS [sql_instance],\n HOST_NAME() AS [computer_name],\n MAX(qs.plan_handle) AS query_plan_handle,\n qs.query_hash AS query_hash,\n qs.query_plan_hash AS query_plan_hash,\n SUM(qs.execution_count) AS execution_count,\n SUM(qs.total_elapsed_time) AS total_elapsed_time,\n SUM(qs.total_worker_time) AS total_worker_time,\n SUM(qs.total_logical_reads) AS total_logical_reads,\n SUM(qs.total_physical_reads) AS total_physical_reads,\n SUM(qs.total_logical_writes) AS total_logical_writes,\n SUM(qs.total_rows) AS total_rows,\n SUM(qs.total_grant_kb) as total_grant_kb\n FROM sys.dm_exec_query_stats AS qs\n WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @granularity, GETDATE()) AND GETDATE()\n GROUP BY\n qs.query_hash,\n qs.query_plan_hash\n)\nSELECT qs.*,\n SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1,\n ((CASE statement_end_offset\n WHEN -1 THEN DATALENGTH(st.text)\n ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS text,\n ISNULL(qp.query_plan, '') AS query_plan\nFROM qstats AS qs\n INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle\n CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp\n CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st", - "query_plan": "" - } + } ] From 4db323b681e5def573c7c370a2b92e6f759d3282 Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 8 Jan 2026 11:49:55 +0000 Subject: [PATCH 28/49] Updates in prep for testing --- .../templates/dbQueryAndTextQuery.tmpl | 79 +++++++-------- .../templates/sqlServerQuerySample.tmpl | 99 +++++++++---------- 2 files changed, 87 insertions(+), 91 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 20da91dea8a26..d971262d91bf0 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -1,41 +1,38 @@ -;WITH qstats AS - ( - SELECT TOP (@maxSampleCount) - REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], - HOST_NAME() AS [computer_name], - max(qs.plan_handle) AS plan_handle, - ISNULL(st1.objectid, 0) AS object_id, - qs.query_hash AS query_hash, - qs.query_plan_hash AS query_plan_hash, - SUM(qs.execution_count) AS execution_count, - SUM(qs.total_elapsed_time) AS total_elapsed_time, - SUM(qs.total_worker_time) AS total_worker_time, - SUM(qs.total_logical_reads) AS total_logical_reads, - SUM(qs.total_physical_reads) AS total_physical_reads, - SUM(qs.total_logical_writes) AS total_logical_writes, - SUM(qs.total_rows) AS total_rows, - SUM(qs.total_grant_kb) AS total_grant_kb - FROM sys.dm_exec_query_stats AS qs - CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 - WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @lookbackTime, GETDATE()) AND GETDATE() - GROUP BY st1.objectid, qs.query_hash, qs.query_plan_hash - ) - SELECT - qs.*, - SUBSTRING(st.text, - (stats.statement_start_offset / 2) + 1, - ((CASE stats.statement_end_offset - WHEN -1 THEN DATALENGTH(st.text) - ELSE stats.statement_end_offset - END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan, - ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, - ISNULL(CASE WHEN qs.object_id > 0 - THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) - + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) - END, '') AS procedure_name - FROM qstats AS qs - JOIN sys.dm_exec_query_stats AS stats - ON stats.plan_handle = qs.plan_handle - CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st; \ No newline at end of file +with qstats as ( + SELECT TOP(@maxSampleCount) + REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], + HOST_NAME() AS [computer_name], + MAX(qs.plan_handle) AS query_plan_handle, + MAX(qs.last_elapsed_time) AS last_elapsed_time, + MAX(qs.last_execution_time) AS last_execution_time, + qs.query_hash AS query_hash, + qs.query_plan_hash AS query_plan_hash, + SUM(qs.execution_count) AS execution_count, + SUM(qs.total_elapsed_time) AS total_elapsed_time, + SUM(qs.total_worker_time) AS total_worker_time, + SUM(qs.total_logical_reads) AS total_logical_reads, + SUM(qs.total_physical_reads) AS total_physical_reads, + SUM(qs.total_logical_writes) AS total_logical_writes, + SUM(qs.total_rows) AS total_rows, + SUM(qs.total_grant_kb) as total_grant_kb + FROM sys.dm_exec_query_stats AS qs + GROUP BY + qs.query_hash, + qs.query_plan_hash +) +SELECT qs.*, + SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1, + ((CASE statement_end_offset + WHEN -1 THEN DATALENGTH(st.text) + ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name +FROM qstats AS qs + INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle + CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st +WHERE DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time) > DATEADD(SECOND, @lookbackTime, GETDATE()); \ No newline at end of file diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index c4cd34f97f892..5ff57fcee6d99 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -1,52 +1,51 @@ SELECT TOP(@top) - REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], - HOST_NAME() AS [computer_name], - DB_NAME(r.database_id) AS db_name, - ISNULL(c.client_net_address, '') as client_address, - ISNULL(c.client_tcp_port, '') AS client_port, - CONVERT(NVARCHAR, TODATETIMEOFFSET(r.start_time, DATEPART(TZOFFSET, SYSDATETIMEOFFSET())), 126) AS query_start, - s.session_id, - s.STATUS AS session_status, - r.STATUS AS request_status, - ISNULL(s.host_name, '') AS host_name, - r.command, - SUBSTRING(o.TEXT, (r.statement_start_offset / 2) + 1, ( - ( - CASE r.statement_end_offset - WHEN - 1 - THEN DATALENGTH(o.TEXT) - ELSE r.statement_end_offset - END - r.statement_start_offset - ) / 2 - ) + 1) AS statement_text, - r.blocking_session_id, - ISNULL(r.wait_type, '') AS wait_type, - r.wait_time, - r.wait_resource, - r.open_transaction_count, - r.transaction_id, - r.percent_complete, - r.estimated_completion_time, - r.cpu_time, - r.total_elapsed_time, - r.reads, - r.writes, - r.logical_reads, - r.transaction_isolation_level, - r.lock_timeout, - r.deadlock_priority, - r.row_count, - ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, - ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, - ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, - s.login_name AS username, - ISNULL(CASE WHEN o.objectid > 0 THEN o.objectid END, '') AS procedure_id, - ISNULL(CASE WHEN o.objectid > 0 - THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) - + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) - END, '') AS procedure_name + REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], + HOST_NAME() AS [computer_name], + DB_NAME(r.database_id) AS db_name, + ISNULL(c.client_net_address, '') as client_address, + ISNULL(c.client_tcp_port, '') AS client_port, + CONVERT(NVARCHAR, TODATETIMEOFFSET(r.start_time, DATEPART(TZOFFSET, SYSDATETIMEOFFSET())), 126) AS query_start, + s.session_id, + s.STATUS AS session_status, + r.STATUS AS request_status, + ISNULL(s.host_name, '') AS host_name, + r.command, + SUBSTRING(o.TEXT, (r.statement_start_offset / 2) + 1, ( + ( + CASE r.statement_end_offset + WHEN - 1 + THEN DATALENGTH(o.TEXT) + ELSE r.statement_end_offset + END - r.statement_start_offset + ) / 2 + ) + 1) AS statement_text, + r.blocking_session_id, + ISNULL(r.wait_type, '') AS wait_type, + r.wait_time, + r.wait_resource, + r.open_transaction_count, + r.transaction_id, + r.percent_complete, + r.estimated_completion_time, + r.cpu_time, + r.total_elapsed_time, + r.reads, + r.writes, + r.logical_reads, + r.transaction_isolation_level, + r.lock_timeout, + r.deadlock_priority, + r.row_count, + ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, + ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, + ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, + s.login_name AS username, + ISNULL(CASE WHEN o.objectid > 0 THEN o.objectid END, '') AS procedure_id, + ISNULL(CASE WHEN o.objectid > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) + END, '') AS procedure_name FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; - +INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id +INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file From bb78240db51f2a0dc1d32dbadfe26b69767651d5 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Thu, 8 Jan 2026 13:07:51 +0000 Subject: [PATCH 29/49] fix lint --- receiver/sqlserverreceiver/scraper_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index b84775492cae7..0ee9a899791f7 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -394,7 +394,6 @@ func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testi const rowsReturned = "total_rows" const totalWorkerTime = "total_worker_time" const logicalReads = "total_logical_reads" - const logicalWrites = "total_logical_writes" const physicalReads = "total_physical_reads" const executionCount = "execution_count" const totalGrant = "total_grant_kb" @@ -407,7 +406,8 @@ func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testi topQueryCount: 200, } - scraper.ScrapeLogs(t.Context()) + _, err := scraper.ScrapeLogs(t.Context()) + assert.NoError(t, err) expectedFile := filepath.Join("testdata", "expectedQueryTextAndPlanQuery.yaml") expectedLogs, _ := golden.ReadLogs(expectedFile) From b47bd3e85df55432273f5388352716df0ffb7336 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Thu, 8 Jan 2026 15:33:12 +0000 Subject: [PATCH 30/49] cleanup --- receiver/sqlserverreceiver/scraper_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 0ee9a899791f7..a149bfffc5e66 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -421,27 +421,27 @@ func TestQueryTextAndPlanQueryMetricsShouldBeCachedSinceFirstCollection(t *testi assert.Equal(t, 3846, int(tetValue)) rtValue, ok := scraper.cache.Get(keyPrefix + "-" + rowsReturned) - assert.True(t, ok, "Expected to find rowsReturned time in cache right after the first collection") + assert.True(t, ok, "Expected to find rowsReturned in cache right after the first collection") assert.Equal(t, 2, int(rtValue)) twtValue, ok := scraper.cache.Get(keyPrefix + "-" + totalWorkerTime) - assert.True(t, ok, "Expected to find totalWorkerTime time in cache right after the first collection") + assert.True(t, ok, "Expected to find totalWorkerTime in cache right after the first collection") assert.Equal(t, 3845, int(twtValue)) lrValue, ok := scraper.cache.Get(keyPrefix + "-" + logicalReads) - assert.True(t, ok, "Expected to find logicalReads time in cache right after the first collection") + assert.True(t, ok, "Expected to find logicalReads in cache right after the first collection") assert.Equal(t, 3, int(lrValue)) prValue, ok := scraper.cache.Get(keyPrefix + "-" + physicalReads) - assert.True(t, ok, "Expected to find physicalReads time in cache right after the first collection") + assert.True(t, ok, "Expected to find physicalReads in cache right after the first collection") assert.Equal(t, 5, int(prValue)) ecValue, ok := scraper.cache.Get(keyPrefix + "-" + executionCount) - assert.True(t, ok, "Expected to find executionCount time in cache right after the first collection") + assert.True(t, ok, "Expected to find executionCount in cache right after the first collection") assert.Equal(t, 6, int(ecValue)) tgValue, ok := scraper.cache.Get(keyPrefix + "-" + totalGrant) - assert.True(t, ok, "Expected to find totalGrant time in cache right after the first collection") + assert.True(t, ok, "Expected to find totalGrant in cache right after the first collection") assert.Equal(t, 3096, int(tgValue)) } From d0514119cea971026c0206987fab189110f949d2 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Fri, 9 Jan 2026 15:25:26 +0000 Subject: [PATCH 31/49] Fix invalid casting and update old test --- receiver/sqlserverreceiver/scraper.go | 2 +- receiver/sqlserverreceiver/scraper_test.go | 12 +--- ...dQueryTextAndPlanQueryWithInvalidData.yaml | 71 ------------------- 3 files changed, 2 insertions(+), 83 deletions(-) delete mode 100644 receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index ffa8bb11a9507..c40b554d480ff 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -751,7 +751,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont } totalElapsedTimeVal := float64(totalElapsedTimeDiffsMicrosecond[i]) / 1_000_000 - if totalElapsedTimeVal == 0 || executionCountVal == 0 { + if count, ok := executionCountVal.(int64); !ok || count == 0 || totalElapsedTimeVal == 0 { continue } diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index a149bfffc5e66..1ec36f87e77fc 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -556,17 +556,7 @@ func TestInvalidQueryTextAndPlanQuery(t *testing.T) { actualLogs, err := scraper.ScrapeLogs(t.Context()) assert.Error(t, err) - expectedFile := "expectedQueryTextAndPlanQueryWithInvalidData.yaml" - - // Uncomment line below to re-generate expected logs. - // golden.WriteLogs(t, filepath.Join("testdata", expectedFile), actualLogs) - - expectedLogs, err := golden.ReadLogs(filepath.Join("testdata", expectedFile)) - assert.NoError(t, err) - - errs := plogtest.CompareLogs(expectedLogs, actualLogs, plogtest.IgnoreTimestamp()) - assert.Equal(t, "db.server.top_query", actualLogs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).EventName()) - assert.NoError(t, errs) + assert.Zero(t, actualLogs.LogRecordCount(), "If the metrics does not hold meaningful values then those records need not be exported by the receiver") } func TestRecordDatabaseSampleQuery(t *testing.T) { diff --git a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml deleted file mode 100644 index b9c8b65d2503b..0000000000000 --- a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQueryWithInvalidData.yaml +++ /dev/null @@ -1,71 +0,0 @@ -resourceLogs: - - resource: - attributes: - - key: host.name - value: - stringValue: 0.0.0.0 - - key: service.instance.id - value: - stringValue: 0.0.0.0:1433 - - key: sqlserver.computer.name - value: - stringValue: DESKTOP-GHAEGRD - - key: sqlserver.instance.name - value: - stringValue: sqlserver - scopeLogs: - - logRecords: - - attributes: - - key: sqlserver.total_worker_time - value: - doubleValue: 0 - - key: db.query.text - value: - stringValue: "" - - key: sqlserver.execution_count - value: - intValue: "0" - - key: sqlserver.total_logical_reads - value: - intValue: "0" - - key: sqlserver.total_logical_writes - value: - intValue: "0" - - key: sqlserver.total_physical_reads - value: - intValue: "0" - - key: sqlserver.query_hash - value: - stringValue: "307833373834394538373431373145334633" - - key: sqlserver.query_plan - value: - stringValue: "" - - key: sqlserver.query_plan_hash - value: - stringValue: "307844333131323930393432394131423530" - - key: sqlserver.total_rows - value: - intValue: "0" - - key: sqlserver.total_elapsed_time - value: - doubleValue: 0.003845 - - key: sqlserver.total_grant_kb - value: - intValue: "0" - - key: server.address - value: - stringValue: 0.0.0.0 - - key: server.port - value: - intValue: "1433" - - key: db.system.name - value: - stringValue: microsoft.sql_server - body: {} - eventName: db.server.top_query - spanId: "" - timeUnixNano: "1748501969782683000" - traceId: "" - scope: - name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver - version: latest From e32d34ceda53391fd2eb2aeffdab07ced2e142f8 Mon Sep 17 00:00:00 2001 From: sreenathv Date: Fri, 9 Jan 2026 15:26:59 +0000 Subject: [PATCH 32/49] Updating topN query to use HAVING instead of external WHERE clause --- receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl | 4 ++-- .../testdata/databaseTopQueryWithInstanceName.txt | 2 +- .../testdata/databaseTopQueryWithoutInstanceName.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index d59a29ac3888b..933f1cc40caf3 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -19,6 +19,7 @@ with qstats as ( GROUP BY qs.query_hash, qs.query_plan_hash + HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) ) SELECT qs.*, SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1, @@ -29,5 +30,4 @@ SELECT qs.*, FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st -WHERE DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time) > DATEADD(SECOND, @lookbackTime, GETDATE()); + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt index 29228f5aba2e4..5d36da836b57b 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt @@ -14,10 +14,10 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs - WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @lookbackTime, GETDATE()) AND GETDATE() AND (@instanceName = '' OR @@SERVERNAME = @instanceName) GROUP BY qs.query_hash, qs.query_plan_hash + HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) ) SELECT qs.*, SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1, diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index d59a29ac3888b..933f1cc40caf3 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -19,6 +19,7 @@ with qstats as ( GROUP BY qs.query_hash, qs.query_plan_hash + HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) ) SELECT qs.*, SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1, @@ -29,5 +30,4 @@ SELECT qs.*, FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st -WHERE DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time) > DATEADD(SECOND, @lookbackTime, GETDATE()); + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; From 53e83a29d48bdb02ce6a13df28b11bc738f0c4d9 Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 15 Jan 2026 10:20:27 +0000 Subject: [PATCH 33/49] Query Tuning --- .../templates/dbQueryAndTextQuery.tmpl | 10 +- .../databaseTopQueryWithoutInstanceName.txt | 10 +- .../testdata/testQuerySampleQuery.txt | 99 +++++++++---------- 3 files changed, 67 insertions(+), 52 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 933f1cc40caf3..20883d5360b61 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -2,6 +2,7 @@ with qstats as ( SELECT TOP(@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], + ISNULL(st1.objectid, 0) AS object_id, MAX(qs.plan_handle) AS query_plan_handle, MAX(qs.last_elapsed_time) AS last_elapsed_time, MAX(qs.last_execution_time) AS last_execution_time, @@ -16,7 +17,9 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 GROUP BY + st1.objectid, qs.query_hash, qs.query_plan_hash HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) @@ -26,7 +29,12 @@ SELECT qs.*, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index 933f1cc40caf3..20883d5360b61 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -2,6 +2,7 @@ with qstats as ( SELECT TOP(@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], + ISNULL(st1.objectid, 0) AS object_id, MAX(qs.plan_handle) AS query_plan_handle, MAX(qs.last_elapsed_time) AS last_elapsed_time, MAX(qs.last_execution_time) AS last_execution_time, @@ -16,7 +17,9 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 GROUP BY + st1.objectid, qs.query_hash, qs.query_plan_hash HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) @@ -26,7 +29,12 @@ SELECT qs.*, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp diff --git a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt index 96774ccaf08a9..5ff57fcee6d99 100644 --- a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt +++ b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt @@ -1,52 +1,51 @@ SELECT TOP(@top) - REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], - HOST_NAME() AS [computer_name], - DB_NAME(r.database_id) AS db_name, - ISNULL(c.client_net_address, '') as client_address, - ISNULL(c.client_tcp_port, '') AS client_port, - CONVERT(NVARCHAR, TODATETIMEOFFSET(r.start_time, DATEPART(TZOFFSET, SYSDATETIMEOFFSET())), 126) AS query_start, - s.session_id, - s.STATUS AS session_status, - r.STATUS AS request_status, - ISNULL(s.host_name, '') AS host_name, - r.command, - SUBSTRING(o.TEXT, (r.statement_start_offset / 2) + 1, ( - ( - CASE r.statement_end_offset - WHEN - 1 - THEN DATALENGTH(o.TEXT) - ELSE r.statement_end_offset - END - r.statement_start_offset - ) / 2 - ) + 1) AS statement_text, - r.blocking_session_id, - ISNULL(r.wait_type, '') AS wait_type, - r.wait_time, - r.wait_resource, - r.open_transaction_count, - r.transaction_id, - r.percent_complete, - r.estimated_completion_time, - r.cpu_time, - r.total_elapsed_time, - r.reads, - r.writes, - r.logical_reads, - r.transaction_isolation_level, - r.lock_timeout, - r.deadlock_priority, - r.row_count, - ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, - ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, - ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, - s.login_name AS username, - ISNULL(CASE WHEN o.objectid > 0 THEN o.objectid END, '') AS procedure_id, - ISNULL(CASE WHEN o.objectid > 0 - THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) - + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) - END, '') AS procedure_name + REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], + HOST_NAME() AS [computer_name], + DB_NAME(r.database_id) AS db_name, + ISNULL(c.client_net_address, '') as client_address, + ISNULL(c.client_tcp_port, '') AS client_port, + CONVERT(NVARCHAR, TODATETIMEOFFSET(r.start_time, DATEPART(TZOFFSET, SYSDATETIMEOFFSET())), 126) AS query_start, + s.session_id, + s.STATUS AS session_status, + r.STATUS AS request_status, + ISNULL(s.host_name, '') AS host_name, + r.command, + SUBSTRING(o.TEXT, (r.statement_start_offset / 2) + 1, ( + ( + CASE r.statement_end_offset + WHEN - 1 + THEN DATALENGTH(o.TEXT) + ELSE r.statement_end_offset + END - r.statement_start_offset + ) / 2 + ) + 1) AS statement_text, + r.blocking_session_id, + ISNULL(r.wait_type, '') AS wait_type, + r.wait_time, + r.wait_resource, + r.open_transaction_count, + r.transaction_id, + r.percent_complete, + r.estimated_completion_time, + r.cpu_time, + r.total_elapsed_time, + r.reads, + r.writes, + r.logical_reads, + r.transaction_isolation_level, + r.lock_timeout, + r.deadlock_priority, + r.row_count, + ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, + ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, + ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, + s.login_name AS username, + ISNULL(CASE WHEN o.objectid > 0 THEN o.objectid END, '') AS procedure_id, + ISNULL(CASE WHEN o.objectid > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) + END, '') AS procedure_name FROM sys.dm_exec_requests r - INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id - INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id - CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; - +INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id +INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file From 45b217331132b4a4071614a52627d3c4b36c7f77 Mon Sep 17 00:00:00 2001 From: cjk Date: Tue, 20 Jan 2026 10:25:09 +0000 Subject: [PATCH 34/49] Adding '''WHERE st.text IS NOT NULL; ''' to topx and samples queries --- receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl | 3 ++- receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl | 3 ++- .../testdata/databaseTopQueryWithInstanceName.txt | 3 ++- .../testdata/databaseTopQueryWithoutInstanceName.txt | 3 ++- receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 20883d5360b61..7c0c129284a48 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -38,4 +38,5 @@ SELECT qs.*, FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st + WHERE st.text IS NOT NULL; diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 5ff57fcee6d99..2fbc90607539c 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -48,4 +48,5 @@ SELECT TOP(@top) FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o +WHERE st.text IS NOT NULL; \ No newline at end of file diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt index 5d36da836b57b..aa97f3b60f60f 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithInstanceName.txt @@ -28,4 +28,5 @@ SELECT qs.*, FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st + WHERE st.text IS NOT NULL; diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index 20883d5360b61..7c0c129284a48 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -38,4 +38,5 @@ SELECT qs.*, FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp - CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st; + CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st + WHERE st.text IS NOT NULL; diff --git a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt index 5ff57fcee6d99..2fbc90607539c 100644 --- a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt +++ b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt @@ -48,4 +48,5 @@ SELECT TOP(@top) FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o +WHERE st.text IS NOT NULL; \ No newline at end of file From e52062a0ce5991669f2992dfea1c5c29b172054b Mon Sep 17 00:00:00 2001 From: cjk Date: Wed, 21 Jan 2026 16:05:13 +0000 Subject: [PATCH 35/49] Backed out changes to samples query --- receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl | 3 +-- receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 2fbc90607539c..5ff57fcee6d99 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -48,5 +48,4 @@ SELECT TOP(@top) FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o -WHERE st.text IS NOT NULL; \ No newline at end of file +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file diff --git a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt index 2fbc90607539c..5ff57fcee6d99 100644 --- a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt +++ b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt @@ -48,5 +48,4 @@ SELECT TOP(@top) FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o -WHERE st.text IS NOT NULL; \ No newline at end of file +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file From 088a342f1ef26978462c1c72c4ca22ce8ab25239 Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 22 Jan 2026 10:30:26 +0000 Subject: [PATCH 36/49] Adding documentation for default samples count --- ...dd-stored-procedure-columns-to-events.yaml | 28 +++++++++++++++++++ receiver/sqlserverreceiver/README.md | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .chloggen/add-stored-procedure-columns-to-events.yaml diff --git a/.chloggen/add-stored-procedure-columns-to-events.yaml b/.chloggen/add-stored-procedure-columns-to-events.yaml new file mode 100644 index 0000000000000..b24371d6c0c98 --- /dev/null +++ b/.chloggen/add-stored-procedure-columns-to-events.yaml @@ -0,0 +1,28 @@ +# 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. receiver/filelog) +component: receiver/sqlserver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: adding sqlserver.procedure_id and sqlserver.procedure_name attributes to TopQuery and Sample Events + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [44656] + +# (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: Refined query and reported events to include stored procedure information when applicable. + Expanded default samples count from 200 to 250 to account for teh addition of stored procedure events. + +# 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 2caa7458dfc4c..791308fc5d1cf 100644 --- a/receiver/sqlserverreceiver/README.md +++ b/receiver/sqlserverreceiver/README.md @@ -90,7 +90,7 @@ Top-Query collection specific options (only useful when top-query collection are - `lookback_time` (optional, example = `60s`, default = `2 * collection_interval`): The time window (in second) in which to query for top queries. - 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. +- `top_query_count`: (optional, example = `100`, default = `250`): 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`. From 135c98803642651e35fb9e5b75bcc6b0474c6012 Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 22 Jan 2026 10:38:06 +0000 Subject: [PATCH 37/49] Revert "Add stored procedure columns to events (#1)" This reverts commit 75b17b23bf89526233620c4da9390acb51e2cf65. --- receiver/sqlserverreceiver/documentation.md | 4 -- receiver/sqlserverreceiver/factory.go | 2 +- receiver/sqlserverreceiver/factory_test.go | 2 +- .../internal/metadata/generated_logs.go | 16 +++--- .../internal/metadata/generated_logs_test.go | 16 +----- receiver/sqlserverreceiver/metadata.yaml | 13 +---- receiver/sqlserverreceiver/scraper.go | 53 +++++++------------ receiver/sqlserverreceiver/scraper_test.go | 42 +++++++-------- .../templates/dbQueryAndTextQuery.tmpl | 10 +--- .../templates/sqlServerQuerySample.tmpl | 7 +-- .../databaseTopQueryWithoutInstanceName.txt | 10 +--- .../expectedQueryTextAndPlanQuery.yaml | 8 +-- .../expectedRecordDatabaseSampleQuery.yaml | 6 --- ...ordDatabaseSampleQueryWithInvalidData.yaml | 6 --- .../testdata/queryTextAndPlanQueryData.txt | 4 +- .../queryTextAndPlanQueryInvalidData.txt | 4 +- .../recordDatabaseSampleQueryData.txt | 4 +- .../recordInvalidDatabaseSampleQueryData.txt | 6 +-- .../testdata/testQuerySampleQuery.txt | 7 +-- 19 files changed, 59 insertions(+), 161 deletions(-) diff --git a/receiver/sqlserverreceiver/documentation.md b/receiver/sqlserverreceiver/documentation.md index 55fb14a46e8ae..a018477cf1617 100644 --- a/receiver/sqlserverreceiver/documentation.md +++ b/receiver/sqlserverreceiver/documentation.md @@ -606,8 +606,6 @@ query sample | sqlserver.wait_type | Type of wait encountered by the request. Empty if none. | Any Str | | sqlserver.writes | Number of writes performed by the query. | Any Int | | user.name | Login name associated with the SQL Server session. | Any Str | -| sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | -| sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | ### db.server.top_query @@ -632,8 +630,6 @@ top query | server.address | The network address of the server hosting the database. | Any Str | | server.port | The port number on which the server is listening. | Any Int | | db.system.name | The database management system (DBMS) product as identified by the client instrumentation. | Any Str | -| sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | -| sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | ## Resource Attributes diff --git a/receiver/sqlserverreceiver/factory.go b/receiver/sqlserverreceiver/factory.go index faa73392b8cff..e2079934fc7a9 100644 --- a/receiver/sqlserverreceiver/factory.go +++ b/receiver/sqlserverreceiver/factory.go @@ -57,7 +57,7 @@ func createDefaultConfig() component.Config { }, TopQueryCollection: TopQueryCollection{ MaxQuerySampleCount: 1000, - TopQueryCount: 250, + TopQueryCount: 200, CollectionInterval: time.Minute, }, } diff --git a/receiver/sqlserverreceiver/factory_test.go b/receiver/sqlserverreceiver/factory_test.go index 228d9b8236430..27300a46fe0f3 100644 --- a/receiver/sqlserverreceiver/factory_test.go +++ b/receiver/sqlserverreceiver/factory_test.go @@ -44,7 +44,7 @@ func TestFactory(t *testing.T) { }, TopQueryCollection: TopQueryCollection{ MaxQuerySampleCount: 1000, - TopQueryCount: 250, + TopQueryCount: 200, CollectionInterval: time.Minute, }, QuerySample: QuerySample{ diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go index 0d7489892146a..2224f758a2990 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go @@ -18,7 +18,7 @@ type eventDbServerQuerySample struct { config EventConfig // event config provided by user. } -func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { +func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string) { if !e.config.Enabled { return } @@ -63,8 +63,6 @@ func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pc dp.Attributes().PutStr("sqlserver.wait_type", sqlserverWaitTypeAttributeValue) dp.Attributes().PutInt("sqlserver.writes", sqlserverWritesAttributeValue) dp.Attributes().PutStr("user.name", userNameAttributeValue) - dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) - dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) } @@ -88,7 +86,7 @@ type eventDbServerTopQuery struct { config EventConfig // event config provided by user. } -func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { +func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string) { if !e.config.Enabled { return } @@ -115,8 +113,6 @@ func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcomm dp.Attributes().PutStr("server.address", serverAddressAttributeValue) dp.Attributes().PutInt("server.port", serverPortAttributeValue) dp.Attributes().PutStr("db.system.name", dbSystemNameAttributeValue) - dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) - dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) } @@ -288,11 +284,11 @@ func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs { } // RecordDbServerQuerySampleEvent adds a log record of db.server.query_sample event. -func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { - lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue) +func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string) { + lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue) } // RecordDbServerTopQueryEvent adds a log record of db.server.top_query event. -func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { - lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue) +func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string) { + lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue) } diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go index 7038dc4bf960c..53531d2d36903 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go @@ -135,10 +135,10 @@ func TestLogsBuilder(t *testing.T) { allEventsCount := 0 allEventsCount++ - lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val") + lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val") allEventsCount++ - lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val") + lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val") rb := lb.NewResourceBuilder() rb.SetHostName("host.name-val") @@ -276,12 +276,6 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("user.name") assert.True(t, ok) assert.Equal(t, "user.name-val", attrVal.Str()) - attrVal, ok = lr.Attributes().Get("sqlserver.procedure_id") - assert.True(t, ok) - assert.Equal(t, "sqlserver.procedure_id-val", attrVal.Str()) - attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") - assert.True(t, ok) - assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) case "db.server.top_query": assert.False(t, validatedEvents["db.server.top_query"], "Found a duplicate in the events slice: db.server.top_query") validatedEvents["db.server.top_query"] = true @@ -334,12 +328,6 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("db.system.name") assert.True(t, ok) assert.Equal(t, "db.system.name-val", attrVal.Str()) - attrVal, ok = lr.Attributes().Get("sqlserver.procedure_id") - assert.True(t, ok) - assert.Equal(t, "sqlserver.procedure_id-val", attrVal.Str()) - attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") - assert.True(t, ok) - assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) } } }) diff --git a/receiver/sqlserverreceiver/metadata.yaml b/receiver/sqlserverreceiver/metadata.yaml index 90131ae0f091b..3b1caab551324 100644 --- a/receiver/sqlserverreceiver/metadata.yaml +++ b/receiver/sqlserverreceiver/metadata.yaml @@ -1,5 +1,5 @@ type: sqlserver -#generated_package_name: sqlserverreceiver + status: class: receiver stability: @@ -133,13 +133,6 @@ attributes: sqlserver.percent_complete: description: Percentage of work completed. type: double - # Stored Procedures - sqlserver.procedure_id: - description: The SQLServer ID of the stored procedure, if any - type: string - sqlserver.procedure_name: - description: The name of the stored procedure, if any - type: string sqlserver.query_hash: description: Binary hash value calculated on the query and used to identify queries with similar logic, reported in the HEX format. type: string @@ -269,8 +262,6 @@ events: - sqlserver.wait_type - sqlserver.writes - user.name - - sqlserver.procedure_id - - sqlserver.procedure_name db.server.top_query: enabled: false @@ -291,8 +282,6 @@ events: - server.address - server.port - db.system.name - - sqlserver.procedure_id - - sqlserver.procedure_name metrics: sqlserver.batch.request.rate: diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index c37320f1c9ec6..ea3d689138bbe 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -152,7 +152,7 @@ func (s *sqlServerScraperHelper) ScrapeLogs(ctx context.Context) (plog.Logs, err 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) + resources, err = s.recordDatabaseQueryTextAndPlan(ctx, s.config.TopQueryCount) case getSQLServerQuerySamplesQuery(): resources, err = s.recordDatabaseSampleQuery(ctx) default: @@ -619,7 +619,7 @@ func (s *sqlServerScraperHelper) recordDatabaseWaitMetrics(ctx context.Context) return errors.Join(errs...) } -func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context) (pcommon.Resource, error) { +func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context, topQueryCount uint) (pcommon.Resource, error) { // Constants are the column names of the database status const ( executionCount = "execution_count" @@ -638,10 +638,6 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont totalWorkerTime = "total_worker_time" dbSystemNameVal = "microsoft.sql_server" - - // stored procedure columns - storedProcedureID = "procedure_id" - storedProcedureName = "procedure_name" ) resources := pcommon.NewResource() @@ -664,23 +660,23 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont for i, row := range rows { queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) - procID := row[storedProcedureID] // defaulted to '0' if not present elapsedTimeMicrosecond, err := strconv.ParseInt(row[totalElapsedTime], 10, 64) if err != nil { - s.logger.Warn(fmt.Sprintf("sqlServerScraperHelper failed getting rows: %s", err)) + s.logger.Info(fmt.Sprintf("sqlServerScraperHelper failed getting rows: %s", err)) errs = append(errs, err) } else { // we're trying to get the queries that used the most time. // caching the total elapsed time (in microsecond) and compare in the next scrape. - if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { + if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { totalElapsedTimeDiffsMicrosecond[i] = diff } } } + // sort the rows based on the totalElapsedTimeDiffs in descending order, // only report first T(T=topQueryCount) rows. - rows = sortRows(rows, totalElapsedTimeDiffsMicrosecond, s.config.TopQueryCount) + rows = sortRows(rows, totalElapsedTimeDiffsMicrosecond, topQueryCount) // sort the totalElapsedTimeDiffs in descending order as well sort.Slice(totalElapsedTimeDiffsMicrosecond, func(i, j int) bool { return totalElapsedTimeDiffsMicrosecond[i] > totalElapsedTimeDiffsMicrosecond[j] }) @@ -693,7 +689,6 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont // reporting human-readable query hash and query hash plan queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) - procID := row[storedProcedureID] queryTextVal := s.retrieveValue(row, queryText, &errs, func(row sqlquery.StringMap, columnName string) (any, error) { statement := row[columnName] @@ -706,28 +701,26 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont return obfuscated, nil }) - var cached bool - executionCountVal := s.retrieveValue(row, executionCount, &errs, retrieveInt) - cached, executionCountVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, executionCount, executionCountVal.(int64)) + cached, executionCountVal := s.cacheAndDiff(queryHashVal, queryPlanHashVal, executionCount, executionCountVal.(int64)) if !cached { executionCountVal = int64(0) } logicalReadsVal := s.retrieveValue(row, logicalReads, &errs, retrieveInt) - cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, logicalReads, logicalReadsVal.(int64)) + cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalReads, logicalReadsVal.(int64)) if !cached { logicalReadsVal = int64(0) } logicalWritesVal := s.retrieveValue(row, logicalWrites, &errs, retrieveInt) - cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, logicalWrites, logicalWritesVal.(int64)) + cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalWrites, logicalWritesVal.(int64)) if !cached { logicalWritesVal = int64(0) } physicalReadsVal := s.retrieveValue(row, physicalReads, &errs, retrieveInt) - cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, physicalReads, physicalReadsVal.(int64)) + cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, physicalReads, physicalReadsVal.(int64)) if !cached { physicalReadsVal = int64(0) } @@ -737,19 +730,19 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) rowsReturnedVal := s.retrieveValue(row, rowsReturned, &errs, retrieveInt) - cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, rowsReturned, rowsReturnedVal.(int64)) + cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, rowsReturned, rowsReturnedVal.(int64)) if !cached { rowsReturnedVal = int64(0) } totalGrantVal := s.retrieveValue(row, totalGrant, &errs, retrieveInt) - cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalGrant, totalGrantVal.(int64)) + cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalGrant, totalGrantVal.(int64)) if !cached { totalGrantVal = int64(0) } totalWorkerTimeVal := s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt) - cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalWorkerTime, totalWorkerTimeVal.(int64)) + cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalWorkerTime, totalWorkerTimeVal.(int64)) totalWorkerTimeInSecVal := float64(0) if cached { totalWorkerTimeInSecVal = float64(totalWorkerTimeVal.(int64)) / 1_000_000 @@ -788,10 +781,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont totalGrantVal.(int64), s.config.Server, int64(s.config.Port), - dbSystemNameVal, - row[storedProcedureID], - row[storedProcedureName], - ) + dbSystemNameVal) } return resources, errors.Join(errs...) } @@ -814,15 +804,13 @@ func (s *sqlServerScraperHelper) retrieveValue( // cacheAndDiff store row(in int) with query hash and query plan hash variables // (1) returns true if the key is cached before // (2) returns positive value if the value is larger than the cached value -func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, procedureID, column string, val int64) (bool, int64) { +func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, column string, val int64) (bool, int64) { if val < 0 { return false, 0 } key := queryHash + "-" + queryPlanHash + "-" + column - if procedureID != "0" { // procedureID is '0' when not a stored procedure - key = procedureID + "-" + key - } + cached, ok := s.cache.Get(key) s.cache.Add(key, val) if !ok { @@ -832,6 +820,7 @@ func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, procedur if val > cached { return true, val - cached } + return true, 0 } @@ -939,9 +928,6 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) const waitTimeMillisecond = "wait_time" const waitType = "wait_type" const writes = "writes" - // stored procedure columns - const storedProcedureID = "procedure_id" - const storedProcedureName = "procedure_name" rows, err := s.client.QueryRows( ctx, @@ -1035,9 +1021,7 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) } else { clientAddressVal = row[clientAddress] } - if s.logger.Level() == zap.DebugLevel && row[storedProcedureID] != "0" { - s.logger.Debug("Stored proc data", zap.String("id", row[storedProcedureID]), zap.String("name", row[storedProcedureName])) - } + s.lb.RecordDbServerQuerySampleEvent( contextFromQuery, timestamp, clientAddressVal, clientPortVal, @@ -1053,7 +1037,6 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) sessionIDVal, sessionStatusVal, totalElapsedTimeSecondVal, transactionIDVal, transactionIsolationLevelVal, waitResourceVal, waitTimeSecondVal, waitTypeVal, writesVal, usernameVal, - row[storedProcedureID], row[storedProcedureName], ) if !resourcesAdded { diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 8b80d467fb928..1ec36f87e77fc 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -232,19 +232,19 @@ func TestScrapeCacheAndDiff(t *testing.T) { assert.NotNil(t, scrapers) scraper := scrapers[0] - cached, val := scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", -1) + cached, val := scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", -1) assert.False(t, cached) assert.Equal(t, int64(0), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 1) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 1) assert.False(t, cached) assert.Equal(t, int64(1), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 1) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 1) assert.True(t, cached) assert.Equal(t, int64(0), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 3) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 3) assert.True(t, cached) assert.Equal(t, int64(2), val) } @@ -476,15 +476,14 @@ func TestQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - procedureID := "0" - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalElapsedTime, 846) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalWorkerTime, 845) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalGrant, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, totalElapsedTime, 846) + scraper.cacheAndDiff(queryHash, queryPlanHash, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, totalWorkerTime, 845) + scraper.cacheAndDiff(queryHash, queryPlanHash, totalGrant, 1) scraper.client = mockClient{ instanceName: scraper.config.InstanceName, @@ -536,15 +535,14 @@ func TestInvalidQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - procedureID := "0" - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalElapsedTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalWorkerTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalGrant, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, totalElapsedTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, totalWorkerTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, totalGrant, 1) scraper.client = mockInvalidClient{ mockClient: mockClient{ diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 7c0c129284a48..1369dca3f8763 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -2,7 +2,6 @@ with qstats as ( SELECT TOP(@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], - ISNULL(st1.objectid, 0) AS object_id, MAX(qs.plan_handle) AS query_plan_handle, MAX(qs.last_elapsed_time) AS last_elapsed_time, MAX(qs.last_execution_time) AS last_execution_time, @@ -17,9 +16,7 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs - CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 GROUP BY - st1.objectid, qs.query_hash, qs.query_plan_hash HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) @@ -29,12 +26,7 @@ SELECT qs.*, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan, - ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, - ISNULL(CASE WHEN qs.object_id > 0 - THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) - + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) - END, '') AS procedure_name + ISNULL(qp.query_plan, '') AS query_plan FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 5ff57fcee6d99..48cd195e4fad7 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -39,12 +39,7 @@ SELECT TOP(@top) ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, - s.login_name AS username, - ISNULL(CASE WHEN o.objectid > 0 THEN o.objectid END, '') AS procedure_id, - ISNULL(CASE WHEN o.objectid > 0 - THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) - + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) - END, '') AS procedure_name + s.login_name AS username FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index 7c0c129284a48..1369dca3f8763 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -2,7 +2,6 @@ with qstats as ( SELECT TOP(@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], - ISNULL(st1.objectid, 0) AS object_id, MAX(qs.plan_handle) AS query_plan_handle, MAX(qs.last_elapsed_time) AS last_elapsed_time, MAX(qs.last_execution_time) AS last_execution_time, @@ -17,9 +16,7 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs - CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 GROUP BY - st1.objectid, qs.query_hash, qs.query_plan_hash HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) @@ -29,12 +26,7 @@ SELECT qs.*, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan, - ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, - ISNULL(CASE WHEN qs.object_id > 0 - THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) - + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) - END, '') AS procedure_name + ISNULL(qp.query_plan, '') AS query_plan FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp diff --git a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml index b68c276bed762..4042e9baab083 100644 --- a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml @@ -61,14 +61,8 @@ resourceLogs: - key: db.system.name value: stringValue: microsoft.sql_server - - key: sqlserver.procedure_id - value: - stringValue: "0" - - key: sqlserver.procedure_name - value: - stringValue: "" body: {} - eventName: "db.server.top_query" + eventName: db.server.top_query spanId: "" timeUnixNano: "1749224037462260000" traceId: "" diff --git a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml index b0babe6d17b09..d0924bd35b480 100644 --- a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml @@ -115,12 +115,6 @@ resourceLogs: - key: user.name value: stringValue: sa - - key: sqlserver.procedure_id - value: - stringValue: "0" - - key: sqlserver.procedure_name - value: - stringValue: "" body: {} eventName: db.server.query_sample spanId: a7ad6a7169203331 diff --git a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml index c91eabef757bc..6a24c138b13c5 100644 --- a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml @@ -115,12 +115,6 @@ resourceLogs: - key: user.name value: stringValue: sa - - key: sqlserver.procedure_id - value: - stringValue: "0" - - key: sqlserver.procedure_name - value: - stringValue: "" body: {} eventName: db.server.query_sample spanId: "" diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt index 7f7bf747a1c71..abd3e31b681f7 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt @@ -14,8 +14,6 @@ "total_rows": "2", "total_grant_kb": "3096", "query_text": "with qstats as (\n SELECT TOP(@topNValue)\n REPLACE(@@SERVERNAME,'\\',':') AS [sql_instance],\n HOST_NAME() AS [computer_name],\n MAX(qs.plan_handle) AS query_plan_handle,\n qs.query_hash AS query_hash,\n qs.query_plan_hash AS query_plan_hash,\n SUM(qs.execution_count) AS execution_count,\n SUM(qs.total_elapsed_time) AS total_elapsed_time,\n SUM(qs.total_worker_time) AS total_worker_time,\n SUM(qs.total_logical_reads) AS total_logical_reads,\n SUM(qs.total_physical_reads) AS total_physical_reads,\n SUM(qs.total_logical_writes) AS total_logical_writes,\n SUM(qs.total_rows) AS total_rows,\n SUM(qs.total_grant_kb) as total_grant_kb\n FROM sys.dm_exec_query_stats AS qs\n WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @granularity, GETDATE()) AND GETDATE()\n GROUP BY\n qs.query_hash,\n qs.query_plan_hash\n)\nSELECT qs.*,\n SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1,\n ((CASE statement_end_offset\n WHEN -1 THEN DATALENGTH(st.text)\n ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS text,\n ISNULL(qp.query_plan, '') AS query_plan\nFROM qstats AS qs\n INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle\n CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp\n CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st", - "query_plan": "", - "procedure_id": "0", - "procedure_name": "" + "query_plan": "" } ] diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt index 4fad7195dd197..da3925707bf19 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt @@ -14,8 +14,6 @@ "total_rows": "2A", "total_grant_kb": "3096A", "query_text": "SELECT cpu_time AS [CPU Usage (time)", - "query_plan": " 0 THEN o.objectid END, '') AS procedure_id, - ISNULL(CASE WHEN o.objectid > 0 - THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) - + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) - END, '') AS procedure_name + s.login_name AS username FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id From 0712589dba7c041956f81033bcb4ffe74dea0ce3 Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 22 Jan 2026 11:16:13 +0000 Subject: [PATCH 38/49] Reapply "Add stored procedure columns to events (#1)" This reverts commit 135c98803642651e35fb9e5b75bcc6b0474c6012. --- receiver/sqlserverreceiver/documentation.md | 4 ++ receiver/sqlserverreceiver/factory.go | 2 +- receiver/sqlserverreceiver/factory_test.go | 2 +- .../internal/metadata/generated_logs.go | 16 +++--- .../internal/metadata/generated_logs_test.go | 16 +++++- receiver/sqlserverreceiver/metadata.yaml | 13 ++++- receiver/sqlserverreceiver/scraper.go | 53 ++++++++++++------- receiver/sqlserverreceiver/scraper_test.go | 42 ++++++++------- .../templates/dbQueryAndTextQuery.tmpl | 10 +++- .../templates/sqlServerQuerySample.tmpl | 7 ++- .../databaseTopQueryWithoutInstanceName.txt | 10 +++- .../expectedQueryTextAndPlanQuery.yaml | 8 ++- .../expectedRecordDatabaseSampleQuery.yaml | 6 +++ ...ordDatabaseSampleQueryWithInvalidData.yaml | 6 +++ .../testdata/queryTextAndPlanQueryData.txt | 4 +- .../queryTextAndPlanQueryInvalidData.txt | 4 +- .../recordDatabaseSampleQueryData.txt | 4 +- .../recordInvalidDatabaseSampleQueryData.txt | 6 ++- .../testdata/testQuerySampleQuery.txt | 7 ++- 19 files changed, 161 insertions(+), 59 deletions(-) diff --git a/receiver/sqlserverreceiver/documentation.md b/receiver/sqlserverreceiver/documentation.md index a018477cf1617..55fb14a46e8ae 100644 --- a/receiver/sqlserverreceiver/documentation.md +++ b/receiver/sqlserverreceiver/documentation.md @@ -606,6 +606,8 @@ query sample | sqlserver.wait_type | Type of wait encountered by the request. Empty if none. | Any Str | | sqlserver.writes | Number of writes performed by the query. | Any Int | | user.name | Login name associated with the SQL Server session. | Any Str | +| sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | +| sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | ### db.server.top_query @@ -630,6 +632,8 @@ top query | server.address | The network address of the server hosting the database. | Any Str | | server.port | The port number on which the server is listening. | Any Int | | db.system.name | The database management system (DBMS) product as identified by the client instrumentation. | Any Str | +| sqlserver.procedure_id | The SQLServer ID of the stored procedure, if any | Any Str | +| sqlserver.procedure_name | The name of the stored procedure, if any | Any Str | ## Resource Attributes diff --git a/receiver/sqlserverreceiver/factory.go b/receiver/sqlserverreceiver/factory.go index e2079934fc7a9..faa73392b8cff 100644 --- a/receiver/sqlserverreceiver/factory.go +++ b/receiver/sqlserverreceiver/factory.go @@ -57,7 +57,7 @@ func createDefaultConfig() component.Config { }, TopQueryCollection: TopQueryCollection{ MaxQuerySampleCount: 1000, - TopQueryCount: 200, + TopQueryCount: 250, CollectionInterval: time.Minute, }, } diff --git a/receiver/sqlserverreceiver/factory_test.go b/receiver/sqlserverreceiver/factory_test.go index 27300a46fe0f3..228d9b8236430 100644 --- a/receiver/sqlserverreceiver/factory_test.go +++ b/receiver/sqlserverreceiver/factory_test.go @@ -44,7 +44,7 @@ func TestFactory(t *testing.T) { }, TopQueryCollection: TopQueryCollection{ MaxQuerySampleCount: 1000, - TopQueryCount: 200, + TopQueryCount: 250, CollectionInterval: time.Minute, }, QuerySample: QuerySample{ diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go index 2224f758a2990..0d7489892146a 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs.go @@ -18,7 +18,7 @@ type eventDbServerQuerySample struct { config EventConfig // event config provided by user. } -func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string) { +func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { if !e.config.Enabled { return } @@ -63,6 +63,8 @@ func (e *eventDbServerQuerySample) recordEvent(ctx context.Context, timestamp pc dp.Attributes().PutStr("sqlserver.wait_type", sqlserverWaitTypeAttributeValue) dp.Attributes().PutInt("sqlserver.writes", sqlserverWritesAttributeValue) dp.Attributes().PutStr("user.name", userNameAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) } @@ -86,7 +88,7 @@ type eventDbServerTopQuery struct { config EventConfig // event config provided by user. } -func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string) { +func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { if !e.config.Enabled { return } @@ -113,6 +115,8 @@ func (e *eventDbServerTopQuery) recordEvent(ctx context.Context, timestamp pcomm dp.Attributes().PutStr("server.address", serverAddressAttributeValue) dp.Attributes().PutInt("server.port", serverPortAttributeValue) dp.Attributes().PutStr("db.system.name", dbSystemNameAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_id", sqlserverProcedureIDAttributeValue) + dp.Attributes().PutStr("sqlserver.procedure_name", sqlserverProcedureNameAttributeValue) } @@ -284,11 +288,11 @@ func (lb *LogsBuilder) Emit(options ...ResourceLogsOption) plog.Logs { } // RecordDbServerQuerySampleEvent adds a log record of db.server.query_sample event. -func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string) { - lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue) +func (lb *LogsBuilder) RecordDbServerQuerySampleEvent(ctx context.Context, timestamp pcommon.Timestamp, clientAddressAttributeValue string, clientPortAttributeValue int64, dbNamespaceAttributeValue string, dbQueryTextAttributeValue string, dbSystemNameAttributeValue string, networkPeerAddressAttributeValue string, networkPeerPortAttributeValue int64, sqlserverBlockingSessionIDAttributeValue int64, sqlserverContextInfoAttributeValue string, sqlserverCommandAttributeValue string, sqlserverCPUTimeAttributeValue float64, sqlserverDeadlockPriorityAttributeValue int64, sqlserverEstimatedCompletionTimeAttributeValue float64, sqlserverLockTimeoutAttributeValue float64, sqlserverLogicalReadsAttributeValue int64, sqlserverOpenTransactionCountAttributeValue int64, sqlserverPercentCompleteAttributeValue float64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverQueryStartAttributeValue string, sqlserverReadsAttributeValue int64, sqlserverRequestStatusAttributeValue string, sqlserverRowCountAttributeValue int64, sqlserverSessionIDAttributeValue int64, sqlserverSessionStatusAttributeValue string, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTransactionIDAttributeValue int64, sqlserverTransactionIsolationLevelAttributeValue int64, sqlserverWaitResourceAttributeValue string, sqlserverWaitTimeAttributeValue float64, sqlserverWaitTypeAttributeValue string, sqlserverWritesAttributeValue int64, userNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { + lb.eventDbServerQuerySample.recordEvent(ctx, timestamp, clientAddressAttributeValue, clientPortAttributeValue, dbNamespaceAttributeValue, dbQueryTextAttributeValue, dbSystemNameAttributeValue, networkPeerAddressAttributeValue, networkPeerPortAttributeValue, sqlserverBlockingSessionIDAttributeValue, sqlserverContextInfoAttributeValue, sqlserverCommandAttributeValue, sqlserverCPUTimeAttributeValue, sqlserverDeadlockPriorityAttributeValue, sqlserverEstimatedCompletionTimeAttributeValue, sqlserverLockTimeoutAttributeValue, sqlserverLogicalReadsAttributeValue, sqlserverOpenTransactionCountAttributeValue, sqlserverPercentCompleteAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverQueryStartAttributeValue, sqlserverReadsAttributeValue, sqlserverRequestStatusAttributeValue, sqlserverRowCountAttributeValue, sqlserverSessionIDAttributeValue, sqlserverSessionStatusAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTransactionIDAttributeValue, sqlserverTransactionIsolationLevelAttributeValue, sqlserverWaitResourceAttributeValue, sqlserverWaitTimeAttributeValue, sqlserverWaitTypeAttributeValue, sqlserverWritesAttributeValue, userNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue) } // RecordDbServerTopQueryEvent adds a log record of db.server.top_query event. -func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string) { - lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue) +func (lb *LogsBuilder) RecordDbServerTopQueryEvent(ctx context.Context, timestamp pcommon.Timestamp, sqlserverTotalWorkerTimeAttributeValue float64, dbQueryTextAttributeValue string, sqlserverExecutionCountAttributeValue int64, sqlserverTotalLogicalReadsAttributeValue int64, sqlserverTotalLogicalWritesAttributeValue int64, sqlserverTotalPhysicalReadsAttributeValue int64, sqlserverQueryHashAttributeValue string, sqlserverQueryPlanAttributeValue string, sqlserverQueryPlanHashAttributeValue string, sqlserverTotalRowsAttributeValue int64, sqlserverTotalElapsedTimeAttributeValue float64, sqlserverTotalGrantKbAttributeValue int64, serverAddressAttributeValue string, serverPortAttributeValue int64, dbSystemNameAttributeValue string, sqlserverProcedureIDAttributeValue string, sqlserverProcedureNameAttributeValue string) { + lb.eventDbServerTopQuery.recordEvent(ctx, timestamp, sqlserverTotalWorkerTimeAttributeValue, dbQueryTextAttributeValue, sqlserverExecutionCountAttributeValue, sqlserverTotalLogicalReadsAttributeValue, sqlserverTotalLogicalWritesAttributeValue, sqlserverTotalPhysicalReadsAttributeValue, sqlserverQueryHashAttributeValue, sqlserverQueryPlanAttributeValue, sqlserverQueryPlanHashAttributeValue, sqlserverTotalRowsAttributeValue, sqlserverTotalElapsedTimeAttributeValue, sqlserverTotalGrantKbAttributeValue, serverAddressAttributeValue, serverPortAttributeValue, dbSystemNameAttributeValue, sqlserverProcedureIDAttributeValue, sqlserverProcedureNameAttributeValue) } diff --git a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go index 53531d2d36903..7038dc4bf960c 100644 --- a/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go +++ b/receiver/sqlserverreceiver/internal/metadata/generated_logs_test.go @@ -135,10 +135,10 @@ func TestLogsBuilder(t *testing.T) { allEventsCount := 0 allEventsCount++ - lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val") + lb.RecordDbServerQuerySampleEvent(ctx, timestamp, "client.address-val", 11, "db.namespace-val", "db.query.text-val", "db.system.name-val", "network.peer.address-val", 17, 29, "sqlserver.context_info-val", "sqlserver.command-val", 18.100000, 27, 35.100000, 22.100000, 23, 32, 26.100000, "sqlserver.query_hash-val", "sqlserver.query_plan_hash-val", "sqlserver.query_start-val", 15, "sqlserver.request_status-val", 19, 20, "sqlserver.session_status-val", 28.100000, 24, 37, "sqlserver.wait_resource-val", 19.100000, "sqlserver.wait_type-val", 16, "user.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val") allEventsCount++ - lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val") + lb.RecordDbServerTopQueryEvent(ctx, timestamp, 27.100000, "db.query.text-val", 25, 29, 30, 30, "sqlserver.query_hash-val", "sqlserver.query_plan-val", "sqlserver.query_plan_hash-val", 20, 28.100000, 24, "server.address-val", 11, "db.system.name-val", "sqlserver.procedure_id-val", "sqlserver.procedure_name-val") rb := lb.NewResourceBuilder() rb.SetHostName("host.name-val") @@ -276,6 +276,12 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("user.name") assert.True(t, ok) assert.Equal(t, "user.name-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_id") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_id-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) case "db.server.top_query": assert.False(t, validatedEvents["db.server.top_query"], "Found a duplicate in the events slice: db.server.top_query") validatedEvents["db.server.top_query"] = true @@ -328,6 +334,12 @@ func TestLogsBuilder(t *testing.T) { attrVal, ok = lr.Attributes().Get("db.system.name") assert.True(t, ok) assert.Equal(t, "db.system.name-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_id") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_id-val", attrVal.Str()) + attrVal, ok = lr.Attributes().Get("sqlserver.procedure_name") + assert.True(t, ok) + assert.Equal(t, "sqlserver.procedure_name-val", attrVal.Str()) } } }) diff --git a/receiver/sqlserverreceiver/metadata.yaml b/receiver/sqlserverreceiver/metadata.yaml index 3b1caab551324..90131ae0f091b 100644 --- a/receiver/sqlserverreceiver/metadata.yaml +++ b/receiver/sqlserverreceiver/metadata.yaml @@ -1,5 +1,5 @@ type: sqlserver - +#generated_package_name: sqlserverreceiver status: class: receiver stability: @@ -133,6 +133,13 @@ attributes: sqlserver.percent_complete: description: Percentage of work completed. type: double + # Stored Procedures + sqlserver.procedure_id: + description: The SQLServer ID of the stored procedure, if any + type: string + sqlserver.procedure_name: + description: The name of the stored procedure, if any + type: string sqlserver.query_hash: description: Binary hash value calculated on the query and used to identify queries with similar logic, reported in the HEX format. type: string @@ -262,6 +269,8 @@ events: - sqlserver.wait_type - sqlserver.writes - user.name + - sqlserver.procedure_id + - sqlserver.procedure_name db.server.top_query: enabled: false @@ -282,6 +291,8 @@ events: - server.address - server.port - db.system.name + - sqlserver.procedure_id + - sqlserver.procedure_name metrics: sqlserver.batch.request.rate: diff --git a/receiver/sqlserverreceiver/scraper.go b/receiver/sqlserverreceiver/scraper.go index ea3d689138bbe..c37320f1c9ec6 100644 --- a/receiver/sqlserverreceiver/scraper.go +++ b/receiver/sqlserverreceiver/scraper.go @@ -152,7 +152,7 @@ func (s *sqlServerScraperHelper) ScrapeLogs(ctx context.Context) (plog.Logs, err 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) + resources, err = s.recordDatabaseQueryTextAndPlan(ctx) case getSQLServerQuerySamplesQuery(): resources, err = s.recordDatabaseSampleQuery(ctx) default: @@ -619,7 +619,7 @@ func (s *sqlServerScraperHelper) recordDatabaseWaitMetrics(ctx context.Context) return errors.Join(errs...) } -func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context, topQueryCount uint) (pcommon.Resource, error) { +func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Context) (pcommon.Resource, error) { // Constants are the column names of the database status const ( executionCount = "execution_count" @@ -638,6 +638,10 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont totalWorkerTime = "total_worker_time" dbSystemNameVal = "microsoft.sql_server" + + // stored procedure columns + storedProcedureID = "procedure_id" + storedProcedureName = "procedure_name" ) resources := pcommon.NewResource() @@ -660,23 +664,23 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont for i, row := range rows { queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) + procID := row[storedProcedureID] // defaulted to '0' if not present elapsedTimeMicrosecond, err := strconv.ParseInt(row[totalElapsedTime], 10, 64) if err != nil { - s.logger.Info(fmt.Sprintf("sqlServerScraperHelper failed getting rows: %s", err)) + s.logger.Warn(fmt.Sprintf("sqlServerScraperHelper failed getting rows: %s", err)) errs = append(errs, err) } else { // we're trying to get the queries that used the most time. // caching the total elapsed time (in microsecond) and compare in the next scrape. - if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { + if cached, diff := s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalElapsedTime, elapsedTimeMicrosecond); cached && diff > 0 { totalElapsedTimeDiffsMicrosecond[i] = diff } } } - // sort the rows based on the totalElapsedTimeDiffs in descending order, // only report first T(T=topQueryCount) rows. - rows = sortRows(rows, totalElapsedTimeDiffsMicrosecond, topQueryCount) + rows = sortRows(rows, totalElapsedTimeDiffsMicrosecond, s.config.TopQueryCount) // sort the totalElapsedTimeDiffs in descending order as well sort.Slice(totalElapsedTimeDiffsMicrosecond, func(i, j int) bool { return totalElapsedTimeDiffsMicrosecond[i] > totalElapsedTimeDiffsMicrosecond[j] }) @@ -689,6 +693,7 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont // reporting human-readable query hash and query hash plan queryHashVal := hex.EncodeToString([]byte(row[queryHash])) queryPlanHashVal := hex.EncodeToString([]byte(row[queryPlanHash])) + procID := row[storedProcedureID] queryTextVal := s.retrieveValue(row, queryText, &errs, func(row sqlquery.StringMap, columnName string) (any, error) { statement := row[columnName] @@ -701,26 +706,28 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont return obfuscated, nil }) + var cached bool + executionCountVal := s.retrieveValue(row, executionCount, &errs, retrieveInt) - cached, executionCountVal := s.cacheAndDiff(queryHashVal, queryPlanHashVal, executionCount, executionCountVal.(int64)) + cached, executionCountVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, executionCount, executionCountVal.(int64)) if !cached { executionCountVal = int64(0) } logicalReadsVal := s.retrieveValue(row, logicalReads, &errs, retrieveInt) - cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalReads, logicalReadsVal.(int64)) + cached, logicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, logicalReads, logicalReadsVal.(int64)) if !cached { logicalReadsVal = int64(0) } logicalWritesVal := s.retrieveValue(row, logicalWrites, &errs, retrieveInt) - cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, logicalWrites, logicalWritesVal.(int64)) + cached, logicalWritesVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, logicalWrites, logicalWritesVal.(int64)) if !cached { logicalWritesVal = int64(0) } physicalReadsVal := s.retrieveValue(row, physicalReads, &errs, retrieveInt) - cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, physicalReads, physicalReadsVal.(int64)) + cached, physicalReadsVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, physicalReads, physicalReadsVal.(int64)) if !cached { physicalReadsVal = int64(0) } @@ -730,19 +737,19 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont }) rowsReturnedVal := s.retrieveValue(row, rowsReturned, &errs, retrieveInt) - cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, rowsReturned, rowsReturnedVal.(int64)) + cached, rowsReturnedVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, rowsReturned, rowsReturnedVal.(int64)) if !cached { rowsReturnedVal = int64(0) } totalGrantVal := s.retrieveValue(row, totalGrant, &errs, retrieveInt) - cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalGrant, totalGrantVal.(int64)) + cached, totalGrantVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalGrant, totalGrantVal.(int64)) if !cached { totalGrantVal = int64(0) } totalWorkerTimeVal := s.retrieveValue(row, totalWorkerTime, &errs, retrieveInt) - cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, totalWorkerTime, totalWorkerTimeVal.(int64)) + cached, totalWorkerTimeVal = s.cacheAndDiff(queryHashVal, queryPlanHashVal, procID, totalWorkerTime, totalWorkerTimeVal.(int64)) totalWorkerTimeInSecVal := float64(0) if cached { totalWorkerTimeInSecVal = float64(totalWorkerTimeVal.(int64)) / 1_000_000 @@ -781,7 +788,10 @@ func (s *sqlServerScraperHelper) recordDatabaseQueryTextAndPlan(ctx context.Cont totalGrantVal.(int64), s.config.Server, int64(s.config.Port), - dbSystemNameVal) + dbSystemNameVal, + row[storedProcedureID], + row[storedProcedureName], + ) } return resources, errors.Join(errs...) } @@ -804,13 +814,15 @@ func (s *sqlServerScraperHelper) retrieveValue( // cacheAndDiff store row(in int) with query hash and query plan hash variables // (1) returns true if the key is cached before // (2) returns positive value if the value is larger than the cached value -func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, column string, val int64) (bool, int64) { +func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, procedureID, column string, val int64) (bool, int64) { if val < 0 { return false, 0 } key := queryHash + "-" + queryPlanHash + "-" + column - + if procedureID != "0" { // procedureID is '0' when not a stored procedure + key = procedureID + "-" + key + } cached, ok := s.cache.Get(key) s.cache.Add(key, val) if !ok { @@ -820,7 +832,6 @@ func (s *sqlServerScraperHelper) cacheAndDiff(queryHash, queryPlanHash, column s if val > cached { return true, val - cached } - return true, 0 } @@ -928,6 +939,9 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) const waitTimeMillisecond = "wait_time" const waitType = "wait_type" const writes = "writes" + // stored procedure columns + const storedProcedureID = "procedure_id" + const storedProcedureName = "procedure_name" rows, err := s.client.QueryRows( ctx, @@ -1021,7 +1035,9 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) } else { clientAddressVal = row[clientAddress] } - + if s.logger.Level() == zap.DebugLevel && row[storedProcedureID] != "0" { + s.logger.Debug("Stored proc data", zap.String("id", row[storedProcedureID]), zap.String("name", row[storedProcedureName])) + } s.lb.RecordDbServerQuerySampleEvent( contextFromQuery, timestamp, clientAddressVal, clientPortVal, @@ -1037,6 +1053,7 @@ func (s *sqlServerScraperHelper) recordDatabaseSampleQuery(ctx context.Context) sessionIDVal, sessionStatusVal, totalElapsedTimeSecondVal, transactionIDVal, transactionIsolationLevelVal, waitResourceVal, waitTimeSecondVal, waitTypeVal, writesVal, usernameVal, + row[storedProcedureID], row[storedProcedureName], ) if !resourcesAdded { diff --git a/receiver/sqlserverreceiver/scraper_test.go b/receiver/sqlserverreceiver/scraper_test.go index 1ec36f87e77fc..8b80d467fb928 100644 --- a/receiver/sqlserverreceiver/scraper_test.go +++ b/receiver/sqlserverreceiver/scraper_test.go @@ -232,19 +232,19 @@ func TestScrapeCacheAndDiff(t *testing.T) { assert.NotNil(t, scrapers) scraper := scrapers[0] - cached, val := scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", -1) + cached, val := scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", -1) assert.False(t, cached) assert.Equal(t, int64(0), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 1) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 1) assert.False(t, cached) assert.Equal(t, int64(1), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 1) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 1) assert.True(t, cached) assert.Equal(t, int64(0), val) - cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "column", 3) + cached, val = scraper.cacheAndDiff("query_hash", "query_plan_hash", "procedure_id", "column", 3) assert.True(t, cached) assert.Equal(t, int64(2), val) } @@ -476,14 +476,15 @@ func TestQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalElapsedTime, 846) - scraper.cacheAndDiff(queryHash, queryPlanHash, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalWorkerTime, 845) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalGrant, 1) + procedureID := "0" + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalElapsedTime, 846) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalWorkerTime, 845) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalGrant, 1) scraper.client = mockClient{ instanceName: scraper.config.InstanceName, @@ -535,14 +536,15 @@ func TestInvalidQueryTextAndPlanQuery(t *testing.T) { queryHash := hex.EncodeToString([]byte("0x37849E874171E3F3")) queryPlanHash := hex.EncodeToString([]byte("0xD3112909429A1B50")) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalElapsedTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, rowsReturned, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, logicalWrites, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, physicalReads, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, executionCount, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalWorkerTime, 1) - scraper.cacheAndDiff(queryHash, queryPlanHash, totalGrant, 1) + procedureID := "0" + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalElapsedTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, rowsReturned, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, logicalWrites, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, physicalReads, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, executionCount, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalWorkerTime, 1) + scraper.cacheAndDiff(queryHash, queryPlanHash, procedureID, totalGrant, 1) scraper.client = mockInvalidClient{ mockClient: mockClient{ diff --git a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl index 1369dca3f8763..7c0c129284a48 100644 --- a/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl +++ b/receiver/sqlserverreceiver/templates/dbQueryAndTextQuery.tmpl @@ -2,6 +2,7 @@ with qstats as ( SELECT TOP(@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], + ISNULL(st1.objectid, 0) AS object_id, MAX(qs.plan_handle) AS query_plan_handle, MAX(qs.last_elapsed_time) AS last_elapsed_time, MAX(qs.last_execution_time) AS last_execution_time, @@ -16,7 +17,9 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 GROUP BY + st1.objectid, qs.query_hash, qs.query_plan_hash HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) @@ -26,7 +29,12 @@ SELECT qs.*, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 48cd195e4fad7..5ff57fcee6d99 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -39,7 +39,12 @@ SELECT TOP(@top) ISNULL(r.query_hash, CONVERT(VARBINARY, '')) AS query_hash, ISNULL(r.query_plan_hash, CONVERT(VARBINARY, '')) AS query_plan_hash, ISNULL(r.context_info, CONVERT(VARBINARY, '')) AS context_info, - s.login_name AS username + s.login_name AS username, + ISNULL(CASE WHEN o.objectid > 0 THEN o.objectid END, '') AS procedure_id, + ISNULL(CASE WHEN o.objectid > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) + END, '') AS procedure_name FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id diff --git a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt index 1369dca3f8763..7c0c129284a48 100644 --- a/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt +++ b/receiver/sqlserverreceiver/testdata/databaseTopQueryWithoutInstanceName.txt @@ -2,6 +2,7 @@ with qstats as ( SELECT TOP(@maxSampleCount) REPLACE(@@SERVERNAME,'\',':') AS [sql_instance], HOST_NAME() AS [computer_name], + ISNULL(st1.objectid, 0) AS object_id, MAX(qs.plan_handle) AS query_plan_handle, MAX(qs.last_elapsed_time) AS last_elapsed_time, MAX(qs.last_execution_time) AS last_execution_time, @@ -16,7 +17,9 @@ with qstats as ( SUM(qs.total_rows) AS total_rows, SUM(qs.total_grant_kb) as total_grant_kb FROM sys.dm_exec_query_stats AS qs + CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st1 GROUP BY + st1.objectid, qs.query_hash, qs.query_plan_hash HAVING MAX(DATEADD(ms, qs.last_elapsed_time / 1000, qs.last_execution_time)) > DATEADD(SECOND, @lookbackTime, GETDATE()) @@ -26,7 +29,12 @@ SELECT qs.*, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS query_text, - ISNULL(qp.query_plan, '') AS query_plan + ISNULL(qp.query_plan, '') AS query_plan, + ISNULL(CASE WHEN qs.object_id > 0 THEN qs.object_id END, '') AS procedure_id, + ISNULL(CASE WHEN qs.object_id > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(qs.object_id, st.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(qs.object_id, st.dbid)) + END, '') AS procedure_name FROM qstats AS qs INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp diff --git a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml index 4042e9baab083..b68c276bed762 100644 --- a/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedQueryTextAndPlanQuery.yaml @@ -61,8 +61,14 @@ resourceLogs: - key: db.system.name value: stringValue: microsoft.sql_server + - key: sqlserver.procedure_id + value: + stringValue: "0" + - key: sqlserver.procedure_name + value: + stringValue: "" body: {} - eventName: db.server.top_query + eventName: "db.server.top_query" spanId: "" timeUnixNano: "1749224037462260000" traceId: "" diff --git a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml index d0924bd35b480..b0babe6d17b09 100644 --- a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQuery.yaml @@ -115,6 +115,12 @@ resourceLogs: - key: user.name value: stringValue: sa + - key: sqlserver.procedure_id + value: + stringValue: "0" + - key: sqlserver.procedure_name + value: + stringValue: "" body: {} eventName: db.server.query_sample spanId: a7ad6a7169203331 diff --git a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml index 6a24c138b13c5..c91eabef757bc 100644 --- a/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml +++ b/receiver/sqlserverreceiver/testdata/expectedRecordDatabaseSampleQueryWithInvalidData.yaml @@ -115,6 +115,12 @@ resourceLogs: - key: user.name value: stringValue: sa + - key: sqlserver.procedure_id + value: + stringValue: "0" + - key: sqlserver.procedure_name + value: + stringValue: "" body: {} eventName: db.server.query_sample spanId: "" diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt index abd3e31b681f7..7f7bf747a1c71 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryData.txt @@ -14,6 +14,8 @@ "total_rows": "2", "total_grant_kb": "3096", "query_text": "with qstats as (\n SELECT TOP(@topNValue)\n REPLACE(@@SERVERNAME,'\\',':') AS [sql_instance],\n HOST_NAME() AS [computer_name],\n MAX(qs.plan_handle) AS query_plan_handle,\n qs.query_hash AS query_hash,\n qs.query_plan_hash AS query_plan_hash,\n SUM(qs.execution_count) AS execution_count,\n SUM(qs.total_elapsed_time) AS total_elapsed_time,\n SUM(qs.total_worker_time) AS total_worker_time,\n SUM(qs.total_logical_reads) AS total_logical_reads,\n SUM(qs.total_physical_reads) AS total_physical_reads,\n SUM(qs.total_logical_writes) AS total_logical_writes,\n SUM(qs.total_rows) AS total_rows,\n SUM(qs.total_grant_kb) as total_grant_kb\n FROM sys.dm_exec_query_stats AS qs\n WHERE qs.last_execution_time BETWEEN DATEADD(SECOND, @granularity, GETDATE()) AND GETDATE()\n GROUP BY\n qs.query_hash,\n qs.query_plan_hash\n)\nSELECT qs.*,\n SUBSTRING(st.text, (stats.statement_start_offset / 2) + 1,\n ((CASE statement_end_offset\n WHEN -1 THEN DATALENGTH(st.text)\n ELSE stats.statement_end_offset END - stats.statement_start_offset) / 2) + 1) AS text,\n ISNULL(qp.query_plan, '') AS query_plan\nFROM qstats AS qs\n INNER JOIN sys.dm_exec_query_stats AS stats on qs.query_plan_handle = stats.plan_handle\n CROSS APPLY sys.dm_exec_query_plan(qs.query_plan_handle) AS qp\n CROSS APPLY sys.dm_exec_sql_text(qs.query_plan_handle) AS st", - "query_plan": "" + "query_plan": "", + "procedure_id": "0", + "procedure_name": "" } ] diff --git a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt index da3925707bf19..4fad7195dd197 100644 --- a/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt +++ b/receiver/sqlserverreceiver/testdata/queryTextAndPlanQueryInvalidData.txt @@ -14,6 +14,8 @@ "total_rows": "2A", "total_grant_kb": "3096A", "query_text": "SELECT cpu_time AS [CPU Usage (time)", - "query_plan": " 0 THEN o.objectid END, '') AS procedure_id, + ISNULL(CASE WHEN o.objectid > 0 + THEN QUOTENAME(OBJECT_SCHEMA_NAME(o.objectid, o.dbid)) + + N'.' + QUOTENAME(OBJECT_NAME(o.objectid, o.dbid)) + END, '') AS procedure_name FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id From 5b3bf23fac0d8199ce164c817fb3937cf2994863 Mon Sep 17 00:00:00 2001 From: cjk Date: Thu, 22 Jan 2026 11:30:41 +0000 Subject: [PATCH 39/49] removing not null from samples again --- receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl | 3 +-- receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl index 2fbc90607539c..5ff57fcee6d99 100644 --- a/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl +++ b/receiver/sqlserverreceiver/templates/sqlServerQuerySample.tmpl @@ -48,5 +48,4 @@ SELECT TOP(@top) FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o -WHERE st.text IS NOT NULL; \ No newline at end of file +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file diff --git a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt index 2fbc90607539c..5ff57fcee6d99 100644 --- a/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt +++ b/receiver/sqlserverreceiver/testdata/testQuerySampleQuery.txt @@ -48,5 +48,4 @@ SELECT TOP(@top) FROM sys.dm_exec_requests r INNER JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id -CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o -WHERE st.text IS NOT NULL; \ No newline at end of file +CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS o; \ No newline at end of file From d71a5a0041369607091cf16e37cc0025e9ce613b Mon Sep 17 00:00:00 2001 From: Paulo Dias <44772900+paulojmdias@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:03:27 +0000 Subject: [PATCH 40/49] [chore][processor/resourcedetection] Add E2E tests for upcloud detector (#45584) #### Description Add E2E tests for Upcloud detector #### Link to tracking issue Relates to #24671 --------- Signed-off-by: Paulo Dias --- .../resourcedetectionprocessor/e2e_test.go | 47 ++++++++ .../collector/01-metadata-configmap.yaml | 53 +++++++++ .../e2e/upcloud/collector/02-configmap.yaml | 39 +++++++ .../upcloud/collector/03-serviceaccount.yaml | 5 + .../e2e/upcloud/collector/04-service.yaml | 12 ++ .../e2e/upcloud/collector/05-deployment.yaml | 80 +++++++++++++ .../testdata/e2e/upcloud/expected.yaml | 107 ++++++++++++++++++ 7 files changed, 343 insertions(+) create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/01-metadata-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/02-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/03-serviceaccount.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/04-service.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/05-deployment.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/upcloud/expected.yaml diff --git a/processor/resourcedetectionprocessor/e2e_test.go b/processor/resourcedetectionprocessor/e2e_test.go index 8200316a2b18f..f79f89b54a529 100644 --- a/processor/resourcedetectionprocessor/e2e_test.go +++ b/processor/resourcedetectionprocessor/e2e_test.go @@ -384,6 +384,53 @@ func TestE2EOpenstackNovaDetector(t *testing.T) { }, 3*time.Minute, 1*time.Second) } +// TestE2EUpcloudDetector tests the UpCloud detector by deploying a metadata-server +// sidecar that simulates the UpCloud IMDS and verifying that the resource attributes +// are correctly detected and attached to metrics. +func TestE2EUpcloudDetector(t *testing.T) { + var expected pmetric.Metrics + expectedFile := filepath.Join("testdata", "e2e", "upcloud", "expected.yaml") + expected, err := golden.ReadMetrics(expectedFile) + require.NoError(t, err) + + k8sClient, err := k8stest.NewK8sClient(testKubeConfig) + require.NoError(t, err) + + metricsConsumer := new(consumertest.MetricsSink) + shutdownSink := startUpSink(t, metricsConsumer) + defer shutdownSink() + + testID := uuid.NewString()[:8] + collectorObjs := k8stest.CreateCollectorObjects(t, k8sClient, testID, filepath.Join(".", "testdata", "e2e", "upcloud", "collector"), map[string]string{}, "") + + defer func() { + for _, obj := range collectorObjs { + require.NoErrorf(t, k8stest.DeleteObject(k8sClient, obj), "failed to delete object %s", obj.GetName()) + } + }() + + wantEntries := 10 + waitForData(t, wantEntries, metricsConsumer) + + // Uncomment to regenerate golden file + // golden.WriteMetrics(t, expectedFile+".actual", metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1]) + + require.EventuallyWithT(t, func(tt *assert.CollectT) { + assert.NoError(tt, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1], + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + pmetrictest.IgnoreScopeVersion(), + pmetrictest.IgnoreResourceMetricsOrder(), + pmetrictest.IgnoreMetricsOrder(), + pmetrictest.IgnoreScopeMetricsOrder(), + pmetrictest.IgnoreMetricDataPointsOrder(), + pmetrictest.IgnoreMetricValues(), + pmetrictest.IgnoreSubsequentDataPoints("system.cpu.time"), + ), + ) + }, 3*time.Minute, 1*time.Second) +} + func replaceWithStar(_ string) string { return "*" } diff --git a/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/01-metadata-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/01-metadata-configmap.yaml new file mode 100644 index 0000000000000..6e436d0154d83 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/01-metadata-configmap.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-metadata-config + namespace: default +data: + server.py: | + import json + import os + from http.server import BaseHTTPRequestHandler, HTTPServer + + # UpCloud IMDS metadata format + # See https://upcloud.com/docs/guides/upcloud-metadata-service/ + UPCLOUD_METADATA = { + "cloud_name": "upcloud", + "hostname": "test-upcloud-server", + "instance_id": "00aabbcc-1122-3344-5566-778899aabbcc", + "region": "fi-hel1" + } + + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + # Health check endpoint + if self.path == "/healthz": + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # UpCloud IMDS metadata endpoint + if self.path == "/metadata/v1.json": + body = json.dumps(UPCLOUD_METADATA).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # Not found + self.send_response(404) + self.end_headers() + + def log_message(self, fmt, *args): + return + + if __name__ == "__main__": + port = int(os.environ.get("PORT", "80")) + server = HTTPServer(("", port), Handler) + server.serve_forever() diff --git a/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/02-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/02-configmap.yaml new file mode 100644 index 0000000000000..687150f3ad0fb --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/02-configmap.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-config + namespace: default +data: + relay: | + exporters: + otlp: + endpoint: {{ .HostEndpoint }}:4317 + tls: + insecure: true + extensions: + health_check: + endpoint: 0.0.0.0:13133 + processors: + resourcedetection: + detectors: [upcloud] + timeout: 2s + override: false + receivers: + hostmetrics: + collection_interval: 1s + scrapers: + cpu: + service: + telemetry: + logs: + level: "debug" + extensions: + - health_check + pipelines: + metrics: + receivers: + - hostmetrics + processors: + - resourcedetection + exporters: + - otlp diff --git a/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/03-serviceaccount.yaml b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/03-serviceaccount.yaml new file mode 100644 index 0000000000000..7a9803b445b6c --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/03-serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Name }}-sa + namespace: default diff --git a/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/04-service.yaml b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/04-service.yaml new file mode 100644 index 0000000000000..94587d231bbf0 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/04-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Name }} + namespace: default +spec: + selector: + app: {{ .Name }} + ports: + - name: health + port: 13133 + targetPort: 13133 diff --git a/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/05-deployment.yaml b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/05-deployment.yaml new file mode 100644 index 0000000000000..7214d769be5a8 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/collector/05-deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Name }} + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Name }} + template: + metadata: + labels: + app: {{ .Name }} + spec: + serviceAccountName: {{ .Name }}-sa + initContainers: + - name: metadata-server + image: python:3.13-alpine + imagePullPolicy: IfNotPresent + restartPolicy: Always + securityContext: + runAsUser: 0 + capabilities: + drop: + - "ALL" + add: + - "NET_BIND_SERVICE" + command: + - python3 + - /scripts/server.py + env: + - name: PORT + value: "80" + ports: + - containerPort: 80 + name: metadata + startupProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 1 + periodSeconds: 1 + failureThreshold: 10 + volumeMounts: + - name: metadata-script + mountPath: /scripts + - name: setup-network + image: alpine:3.21 + imagePullPolicy: IfNotPresent + securityContext: + capabilities: + add: + - NET_ADMIN + command: + - sh + - -c + - | + apk add --no-cache iptables + iptables -t nat -A OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:80 + echo "iptables rule added to redirect 169.254.169.254:80 -> 127.0.0.1:80" + containers: + - name: otelcol + image: otelcontribcol:latest + imagePullPolicy: Never + args: + - "--config=/conf/relay" + volumeMounts: + - name: config + mountPath: /conf + volumes: + - name: config + configMap: + name: {{ .Name }}-config + items: + - key: relay + path: relay + - name: metadata-script + configMap: + name: {{ .Name }}-metadata-config diff --git a/processor/resourcedetectionprocessor/testdata/e2e/upcloud/expected.yaml b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/expected.yaml new file mode 100644 index 0000000000000..9a4857752dfa8 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/upcloud/expected.yaml @@ -0,0 +1,107 @@ +resourceMetrics: + - resource: + attributes: + - key: cloud.provider + value: + stringValue: upcloud + - key: cloud.region + value: + stringValue: fi-hel1 + - key: host.id + value: + stringValue: 00aabbcc-1122-3344-5566-778899aabbcc + - key: host.name + value: + stringValue: test-upcloud-server + schemaUrl: https://opentelemetry.io/schemas/1.9.0 + scopeMetrics: + - metrics: + - description: Total seconds each logical CPU spent on each mode. + name: system.cpu.time + sum: + aggregationTemporality: 2 + dataPoints: + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: idle + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: interrupt + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: nice + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: softirq + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: steal + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: system + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: user + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: wait + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + isMonotonic: true + unit: s + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper From b44a544ccb5590445785179c77e78081fcfef2c7 Mon Sep 17 00:00:00 2001 From: Paulo Dias <44772900+paulojmdias@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:08:19 +0000 Subject: [PATCH 41/49] [chore][processor/resourcedetection] Add E2E tests for vultr detector (#45583) #### Description Add E2E tests for Vultr detector #### Link to tracking issue Relates to #24671 Signed-off-by: Paulo Dias --- .../resourcedetectionprocessor/e2e_test.go | 47 ++++++++ .../collector/01-metadata-configmap.yaml | 58 ++++++++++ .../e2e/vultr/collector/02-configmap.yaml | 39 +++++++ .../vultr/collector/03-serviceaccount.yaml | 5 + .../e2e/vultr/collector/04-service.yaml | 12 ++ .../e2e/vultr/collector/05-deployment.yaml | 80 +++++++++++++ .../testdata/e2e/vultr/expected.yaml | 107 ++++++++++++++++++ 7 files changed, 348 insertions(+) create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/01-metadata-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/02-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/03-serviceaccount.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/04-service.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/05-deployment.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/vultr/expected.yaml diff --git a/processor/resourcedetectionprocessor/e2e_test.go b/processor/resourcedetectionprocessor/e2e_test.go index f79f89b54a529..54b0df002becc 100644 --- a/processor/resourcedetectionprocessor/e2e_test.go +++ b/processor/resourcedetectionprocessor/e2e_test.go @@ -431,6 +431,53 @@ func TestE2EUpcloudDetector(t *testing.T) { }, 3*time.Minute, 1*time.Second) } +// TestE2EVultrDetector tests the Vultr detector by deploying a metadata-server +// sidecar that simulates the Vultr IMDS and verifying that the resource attributes +// are correctly detected and attached to metrics. +func TestE2EVultrDetector(t *testing.T) { + var expected pmetric.Metrics + expectedFile := filepath.Join("testdata", "e2e", "vultr", "expected.yaml") + expected, err := golden.ReadMetrics(expectedFile) + require.NoError(t, err) + + k8sClient, err := k8stest.NewK8sClient(testKubeConfig) + require.NoError(t, err) + + metricsConsumer := new(consumertest.MetricsSink) + shutdownSink := startUpSink(t, metricsConsumer) + defer shutdownSink() + + testID := uuid.NewString()[:8] + collectorObjs := k8stest.CreateCollectorObjects(t, k8sClient, testID, filepath.Join(".", "testdata", "e2e", "vultr", "collector"), map[string]string{}, "") + + defer func() { + for _, obj := range collectorObjs { + require.NoErrorf(t, k8stest.DeleteObject(k8sClient, obj), "failed to delete object %s", obj.GetName()) + } + }() + + wantEntries := 10 + waitForData(t, wantEntries, metricsConsumer) + + // Uncomment to regenerate golden file + // golden.WriteMetrics(t, expectedFile+".actual", metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1]) + + require.EventuallyWithT(t, func(tt *assert.CollectT) { + assert.NoError(tt, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1], + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + pmetrictest.IgnoreScopeVersion(), + pmetrictest.IgnoreResourceMetricsOrder(), + pmetrictest.IgnoreMetricsOrder(), + pmetrictest.IgnoreScopeMetricsOrder(), + pmetrictest.IgnoreMetricDataPointsOrder(), + pmetrictest.IgnoreMetricValues(), + pmetrictest.IgnoreSubsequentDataPoints("system.cpu.time"), + ), + ) + }, 3*time.Minute, 1*time.Second) +} + func replaceWithStar(_ string) string { return "*" } diff --git a/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/01-metadata-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/01-metadata-configmap.yaml new file mode 100644 index 0000000000000..17f20e268e97d --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/01-metadata-configmap.yaml @@ -0,0 +1,58 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-metadata-config + namespace: default +data: + server.py: | + import json + import os + from http.server import BaseHTTPRequestHandler, HTTPServer + from urllib.parse import urlparse + + # Vultr IMDS metadata format (see https://www.vultr.com/metadata/) + VULTR_METADATA = { + "hostname": "test-vultr-instance", + "instanceid": "12345678", + "instance-v2-id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "region": { + "regioncode": "EWR" + } + } + + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + parsed = urlparse(self.path) + path = parsed.path + + # Health check endpoint + if path == "/healthz": + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # Vultr IMDS endpoint (returns full metadata as JSON) + if path == "/v1.json": + body = json.dumps(VULTR_METADATA).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # Not found + self.send_response(404) + self.end_headers() + + def log_message(self, fmt, *args): + return + + if __name__ == "__main__": + port = int(os.environ.get("PORT", "80")) + server = HTTPServer(("", port), Handler) + server.serve_forever() diff --git a/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/02-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/02-configmap.yaml new file mode 100644 index 0000000000000..13caebe5818a4 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/02-configmap.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-config + namespace: default +data: + relay: | + exporters: + otlp: + endpoint: {{ .HostEndpoint }}:4317 + tls: + insecure: true + extensions: + health_check: + endpoint: 0.0.0.0:13133 + processors: + resourcedetection: + detectors: [vultr] + timeout: 2s + override: false + receivers: + hostmetrics: + collection_interval: 1s + scrapers: + cpu: + service: + telemetry: + logs: + level: "debug" + extensions: + - health_check + pipelines: + metrics: + receivers: + - hostmetrics + processors: + - resourcedetection + exporters: + - otlp diff --git a/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/03-serviceaccount.yaml b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/03-serviceaccount.yaml new file mode 100644 index 0000000000000..7a9803b445b6c --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/03-serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Name }}-sa + namespace: default diff --git a/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/04-service.yaml b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/04-service.yaml new file mode 100644 index 0000000000000..94587d231bbf0 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/04-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Name }} + namespace: default +spec: + selector: + app: {{ .Name }} + ports: + - name: health + port: 13133 + targetPort: 13133 diff --git a/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/05-deployment.yaml b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/05-deployment.yaml new file mode 100644 index 0000000000000..7214d769be5a8 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/vultr/collector/05-deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Name }} + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Name }} + template: + metadata: + labels: + app: {{ .Name }} + spec: + serviceAccountName: {{ .Name }}-sa + initContainers: + - name: metadata-server + image: python:3.13-alpine + imagePullPolicy: IfNotPresent + restartPolicy: Always + securityContext: + runAsUser: 0 + capabilities: + drop: + - "ALL" + add: + - "NET_BIND_SERVICE" + command: + - python3 + - /scripts/server.py + env: + - name: PORT + value: "80" + ports: + - containerPort: 80 + name: metadata + startupProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 1 + periodSeconds: 1 + failureThreshold: 10 + volumeMounts: + - name: metadata-script + mountPath: /scripts + - name: setup-network + image: alpine:3.21 + imagePullPolicy: IfNotPresent + securityContext: + capabilities: + add: + - NET_ADMIN + command: + - sh + - -c + - | + apk add --no-cache iptables + iptables -t nat -A OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:80 + echo "iptables rule added to redirect 169.254.169.254:80 -> 127.0.0.1:80" + containers: + - name: otelcol + image: otelcontribcol:latest + imagePullPolicy: Never + args: + - "--config=/conf/relay" + volumeMounts: + - name: config + mountPath: /conf + volumes: + - name: config + configMap: + name: {{ .Name }}-config + items: + - key: relay + path: relay + - name: metadata-script + configMap: + name: {{ .Name }}-metadata-config diff --git a/processor/resourcedetectionprocessor/testdata/e2e/vultr/expected.yaml b/processor/resourcedetectionprocessor/testdata/e2e/vultr/expected.yaml new file mode 100644 index 0000000000000..ed2f969413dc0 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/vultr/expected.yaml @@ -0,0 +1,107 @@ +resourceMetrics: + - resource: + attributes: + - key: cloud.provider + value: + stringValue: vultr + - key: cloud.region + value: + stringValue: ewr + - key: host.id + value: + stringValue: a1b2c3d4-e5f6-7890-abcd-ef1234567890 + - key: host.name + value: + stringValue: test-vultr-instance + schemaUrl: https://opentelemetry.io/schemas/1.9.0 + scopeMetrics: + - metrics: + - description: Total seconds each logical CPU spent on each mode. + name: system.cpu.time + sum: + aggregationTemporality: 2 + dataPoints: + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: idle + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: interrupt + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: nice + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: softirq + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: steal + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: system + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: user + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: wait + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + isMonotonic: true + unit: s + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper From 715f827d520740d13d216564e71e2544a5a851cb Mon Sep 17 00:00:00 2001 From: Paulo Dias <44772900+paulojmdias@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:35:04 +0000 Subject: [PATCH 42/49] [chore][processor/resourcedetection] Add E2E tests for ec2 detector (#45438) #### Description Add E2E tests for EC2 detector #### Link to tracking issue Relates to #24671 Signed-off-by: Paulo Dias --- .../resourcedetectionprocessor/e2e_test.go | 46 +++++++ .../testdata/e2e/ec2/collector/configmap.yaml | 59 +++++++++ .../e2e/ec2/collector/deployment.yaml | 43 ++++++ .../testdata/e2e/ec2/collector/service.yaml | 12 ++ .../e2e/ec2/collector/serviceaccount.yaml | 5 + .../testdata/e2e/ec2/expected.yaml | 122 ++++++++++++++++++ 6 files changed, 287 insertions(+) create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/deployment.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/service.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/serviceaccount.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/ec2/expected.yaml diff --git a/processor/resourcedetectionprocessor/e2e_test.go b/processor/resourcedetectionprocessor/e2e_test.go index 54b0df002becc..2b60bdd550bf7 100644 --- a/processor/resourcedetectionprocessor/e2e_test.go +++ b/processor/resourcedetectionprocessor/e2e_test.go @@ -478,6 +478,52 @@ func TestE2EVultrDetector(t *testing.T) { }, 3*time.Minute, 1*time.Second) } +// TestE2EEC2Detector tests the EC2 detector by verifying that EC2 metadata +// (like cloud.provider, cloud.region, host.id, etc.) is correctly detected and attached to metrics. +func TestE2EEC2Detector(t *testing.T) { + var expected pmetric.Metrics + expectedFile := filepath.Join("testdata", "e2e", "ec2", "expected.yaml") + expected, err := golden.ReadMetrics(expectedFile) + require.NoError(t, err) + + k8sClient, err := k8stest.NewK8sClient(testKubeConfig) + require.NoError(t, err) + + metricsConsumer := new(consumertest.MetricsSink) + shutdownSink := startUpSink(t, metricsConsumer) + defer shutdownSink() + + testID := uuid.NewString()[:8] + collectorObjs := k8stest.CreateCollectorObjects(t, k8sClient, testID, filepath.Join(".", "testdata", "e2e", "ec2", "collector"), map[string]string{}, "") + + defer func() { + for _, obj := range collectorObjs { + require.NoErrorf(t, k8stest.DeleteObject(k8sClient, obj), "failed to delete object %s", obj.GetName()) + } + }() + + wantEntries := 10 + waitForData(t, wantEntries, metricsConsumer) + + // Uncomment to regenerate golden file + // golden.WriteMetrics(t, expectedFile+".actual", metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1]) + + require.EventuallyWithT(t, func(tt *assert.CollectT) { + assert.NoError(tt, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1], + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + pmetrictest.IgnoreScopeVersion(), + pmetrictest.IgnoreResourceMetricsOrder(), + pmetrictest.IgnoreMetricsOrder(), + pmetrictest.IgnoreScopeMetricsOrder(), + pmetrictest.IgnoreMetricDataPointsOrder(), + pmetrictest.IgnoreMetricValues(), + pmetrictest.IgnoreSubsequentDataPoints("system.cpu.time"), + ), + ) + }, 3*time.Minute, 1*time.Second) +} + func replaceWithStar(_ string) string { return "*" } diff --git a/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/configmap.yaml new file mode 100644 index 0000000000000..7d3de056dd547 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/configmap.yaml @@ -0,0 +1,59 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-config + namespace: default +data: + relay: | + exporters: + otlp: + endpoint: {{ .HostEndpoint }}:4317 + tls: + insecure: true + extensions: + health_check: + endpoint: 0.0.0.0:13133 + processors: + resourcedetection: + detectors: [ec2] + timeout: 2s + override: false + ec2: + resource_attributes: + host.name: + enabled: true + host.id: + enabled: true + cloud.provider: + enabled: true + cloud.platform: + enabled: true + cloud.account.id: + enabled: true + cloud.region: + enabled: true + cloud.availability_zone: + enabled: true + host.image.id: + enabled: true + host.type: + enabled: true + receivers: + hostmetrics: + collection_interval: 1s + scrapers: + cpu: + service: + telemetry: + logs: + level: "debug" + extensions: + - health_check + pipelines: + metrics: + receivers: + - hostmetrics + processors: + - resourcedetection + exporters: + - otlp diff --git a/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/deployment.yaml b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/deployment.yaml new file mode 100644 index 0000000000000..40916b2213ce6 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/deployment.yaml @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Name }} + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Name }} + template: + metadata: + labels: + app: {{ .Name }} + spec: + serviceAccountName: {{ .Name }}-sa + containers: + - name: ec2-metadata-mock + image: public.ecr.aws/aws-ec2/amazon-ec2-metadata-mock:v1.13.0 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 1338 + name: metadata + - name: otelcol + image: otelcontribcol:latest + imagePullPolicy: Never + env: + - name: AWS_EC2_METADATA_SERVICE_ENDPOINT + value: "http://localhost:1338" + - name: AWS_REGION + value: "us-east-1" + args: + - "--config=/conf/relay" + volumeMounts: + - name: config + mountPath: /conf + volumes: + - name: config + configMap: + name: {{ .Name }}-config + items: + - key: relay + path: relay diff --git a/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/service.yaml b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/service.yaml new file mode 100644 index 0000000000000..94587d231bbf0 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Name }} + namespace: default +spec: + selector: + app: {{ .Name }} + ports: + - name: health + port: 13133 + targetPort: 13133 diff --git a/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/serviceaccount.yaml b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/serviceaccount.yaml new file mode 100644 index 0000000000000..7a9803b445b6c --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/ec2/collector/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Name }}-sa + namespace: default diff --git a/processor/resourcedetectionprocessor/testdata/e2e/ec2/expected.yaml b/processor/resourcedetectionprocessor/testdata/e2e/ec2/expected.yaml new file mode 100644 index 0000000000000..28003a61cc534 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/ec2/expected.yaml @@ -0,0 +1,122 @@ +resourceMetrics: + - resource: + attributes: + - key: cloud.account.id + value: + stringValue: "0123456789" + - key: cloud.availability_zone + value: + stringValue: us-east-1f + - key: cloud.platform + value: + stringValue: aws_ec2 + - key: cloud.provider + value: + stringValue: aws + - key: cloud.region + value: + stringValue: us-east-1 + - key: host.id + value: + stringValue: i-1234567890abcdef0 + - key: host.image.id + value: + stringValue: ami-0b69ea66ff7391e80 + - key: host.name + value: + stringValue: ip-172-16-34-43.ec2.internal + - key: host.type + value: + stringValue: m4.xlarge + schemaUrl: https://opentelemetry.io/schemas/1.9.0 + scopeMetrics: + - metrics: + - description: Total seconds each logical CPU spent on each mode. + name: system.cpu.time + sum: + aggregationTemporality: 2 + dataPoints: + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: idle + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: interrupt + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: nice + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: softirq + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: steal + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: system + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: user + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: wait + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + isMonotonic: true + unit: s + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper From fd714902600bab3ffec33b719f4f6a4f63d1d3a7 Mon Sep 17 00:00:00 2001 From: Paulo Dias <44772900+paulojmdias@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:40:15 +0000 Subject: [PATCH 43/49] [chore][processor/resourcedetection] Add E2E tests for scaleway detector (#45590) #### Description Add E2E tests for scaleway detector #### Link to tracking issue Relates to #24671 --------- Signed-off-by: Paulo Dias --- .../resourcedetectionprocessor/e2e_test.go | 47 +++++++ .../collector/01-metadata-configmap.yaml | 95 +++++++++++++ .../e2e/scaleway/collector/02-configmap.yaml | 39 ++++++ .../scaleway/collector/03-serviceaccount.yaml | 5 + .../e2e/scaleway/collector/04-service.yaml | 12 ++ .../e2e/scaleway/collector/05-deployment.yaml | 97 ++++++++++++++ .../testdata/e2e/scaleway/expected.yaml | 125 ++++++++++++++++++ 7 files changed, 420 insertions(+) create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/01-metadata-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/02-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/03-serviceaccount.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/04-service.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/05-deployment.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/scaleway/expected.yaml diff --git a/processor/resourcedetectionprocessor/e2e_test.go b/processor/resourcedetectionprocessor/e2e_test.go index 2b60bdd550bf7..097f6d626bc42 100644 --- a/processor/resourcedetectionprocessor/e2e_test.go +++ b/processor/resourcedetectionprocessor/e2e_test.go @@ -524,6 +524,53 @@ func TestE2EEC2Detector(t *testing.T) { }, 3*time.Minute, 1*time.Second) } +// TestE2EScalewayDetector tests the Scaleway detector by deploying a metadata-server +// sidecar that simulates the Scaleway IMDS (at 169.254.42.42) and verifying that +// the resource attributes are correctly detected and attached to metrics. +func TestE2EScalewayDetector(t *testing.T) { + var expected pmetric.Metrics + expectedFile := filepath.Join("testdata", "e2e", "scaleway", "expected.yaml") + expected, err := golden.ReadMetrics(expectedFile) + require.NoError(t, err) + + k8sClient, err := k8stest.NewK8sClient(testKubeConfig) + require.NoError(t, err) + + metricsConsumer := new(consumertest.MetricsSink) + shutdownSink := startUpSink(t, metricsConsumer) + defer shutdownSink() + + testID := uuid.NewString()[:8] + collectorObjs := k8stest.CreateCollectorObjects(t, k8sClient, testID, filepath.Join(".", "testdata", "e2e", "scaleway", "collector"), map[string]string{}, "") + + defer func() { + for _, obj := range collectorObjs { + require.NoErrorf(t, k8stest.DeleteObject(k8sClient, obj), "failed to delete object %s", obj.GetName()) + } + }() + + wantEntries := 10 + waitForData(t, wantEntries, metricsConsumer) + + // Uncomment to regenerate golden file + // golden.WriteMetrics(t, expectedFile+".actual", metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1]) + + require.EventuallyWithT(t, func(tt *assert.CollectT) { + assert.NoError(tt, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1], + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + pmetrictest.IgnoreScopeVersion(), + pmetrictest.IgnoreResourceMetricsOrder(), + pmetrictest.IgnoreMetricsOrder(), + pmetrictest.IgnoreScopeMetricsOrder(), + pmetrictest.IgnoreMetricDataPointsOrder(), + pmetrictest.IgnoreMetricValues(), + pmetrictest.IgnoreSubsequentDataPoints("system.cpu.time"), + ), + ) + }, 3*time.Minute, 1*time.Second) +} + func replaceWithStar(_ string) string { return "*" } diff --git a/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/01-metadata-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/01-metadata-configmap.yaml new file mode 100644 index 0000000000000..b1bf991292947 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/01-metadata-configmap.yaml @@ -0,0 +1,95 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-metadata-config + namespace: default +data: + server.py: | + import json + import os + import socket + from http.server import BaseHTTPRequestHandler, HTTPServer + + # Scaleway Instance Metadata format + # See https://github.com/scaleway/scaleway-sdk-go/blob/master/api/instance/v1/instance_metadata_sdk.go + SCALEWAY_METADATA = { + "id": "11111111-1111-1111-1111-111111111111", + "name": "test-scaleway-instance", + "hostname": "test-scaleway-instance", + "organization": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "project": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + "commercial_type": "DEV1-S", + "arch": "x86_64", + "state": "running", + "zone": "fr-par-1", + "location": { + "platform_id": "1", + "hypervisor_id": "1", + "node_id": "1", + "cluster_id": "1", + "zone_id": "fr-par-1" + }, + "image": { + "id": "cccccccc-cccc-cccc-cccc-cccccccccccc", + "name": "Ubuntu 22.04 Jammy Jellyfish", + "arch": "x86_64" + }, + "tags": ["otel", "e2e-test"], + "private_ip": "10.0.0.1", + "public_ip": { + "id": "dddddddd-dddd-dddd-dddd-dddddddddddd", + "address": "51.15.0.1", + "gateway": "51.15.0.254", + "netmask": "32", + "family": "inet", + "provisioning_mode": "dhcp" + }, + "ipv6": None, + "ssh_public_keys": [], + "volumes": {}, + "timezone": "UTC" + } + + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + # Health check endpoint + if self.path == "/healthz": + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # Scaleway IMDS metadata endpoint + if self.path == "/conf?format=json" or self.path == "/conf": + body = json.dumps(SCALEWAY_METADATA).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # Not found + self.send_response(404) + self.end_headers() + + def log_message(self, fmt, *args): + return + + class DualStackHTTPServer(HTTPServer): + address_family = socket.AF_INET6 + + def server_bind(self): + # Disable IPV6_V6ONLY to accept both IPv4 and IPv6 connections + self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + super().server_bind() + + if __name__ == "__main__": + port = int(os.environ.get("PORT", "80")) + + # Start dual-stack server (IPv4 + IPv6) + server = DualStackHTTPServer(("::", port), Handler) + server.serve_forever() diff --git a/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/02-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/02-configmap.yaml new file mode 100644 index 0000000000000..a35424a571b63 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/02-configmap.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-config + namespace: default +data: + relay: | + exporters: + otlp: + endpoint: {{ .HostEndpoint }}:4317 + tls: + insecure: true + extensions: + health_check: + endpoint: 0.0.0.0:13133 + processors: + resourcedetection: + detectors: [scaleway] + timeout: 2s + override: false + receivers: + hostmetrics: + collection_interval: 1s + scrapers: + cpu: + service: + telemetry: + logs: + level: "debug" + extensions: + - health_check + pipelines: + metrics: + receivers: + - hostmetrics + processors: + - resourcedetection + exporters: + - otlp diff --git a/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/03-serviceaccount.yaml b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/03-serviceaccount.yaml new file mode 100644 index 0000000000000..7a9803b445b6c --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/03-serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Name }}-sa + namespace: default diff --git a/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/04-service.yaml b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/04-service.yaml new file mode 100644 index 0000000000000..94587d231bbf0 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/04-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Name }} + namespace: default +spec: + selector: + app: {{ .Name }} + ports: + - name: health + port: 13133 + targetPort: 13133 diff --git a/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/05-deployment.yaml b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/05-deployment.yaml new file mode 100644 index 0000000000000..dfb616d5eebc7 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/collector/05-deployment.yaml @@ -0,0 +1,97 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Name }} + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Name }} + template: + metadata: + labels: + app: {{ .Name }} + spec: + serviceAccountName: {{ .Name }}-sa + initContainers: + - name: metadata-server + image: python:3.13-alpine + imagePullPolicy: IfNotPresent + restartPolicy: Always + securityContext: + runAsUser: 0 + capabilities: + drop: + - "ALL" + add: + - "NET_BIND_SERVICE" + command: + - python3 + - /scripts/server.py + env: + - name: PORT + value: "80" + ports: + - containerPort: 80 + name: metadata + startupProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 1 + periodSeconds: 1 + failureThreshold: 10 + volumeMounts: + - name: metadata-script + mountPath: /scripts + - name: setup-network + image: alpine:3.21 + imagePullPolicy: IfNotPresent + securityContext: + capabilities: + add: + - NET_ADMIN + command: + - sh + - -c + - | + apk add --no-cache iptables ip6tables iproute2 curl + # IPv4 redirect for 169.254.42.42 + iptables -t nat -A OUTPUT -d 169.254.42.42 -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:80 + # IPv6: Add local route and redirect for fd00:42::42 + # The route makes the address routable via loopback, then DNAT redirects to ::1 + ip -6 addr add fd00:42::42/128 dev lo 2>/dev/null || true + ip6tables -t nat -A OUTPUT -d fd00:42::42 -p tcp --dport 80 -j DNAT --to-destination [::1]:80 + echo "iptables rules added to redirect 169.254.42.42:80 and fd00:42::42:80 -> localhost:80" + # Verify IPv4 redirect is working + until curl -sf http://169.254.42.42/conf?format=json > /dev/null; do + echo "Waiting for IPv4 metadata redirect to be ready..." + sleep 1 + done + echo "IPv4 metadata redirect verified working" + # Verify IPv6 redirect is working + until curl -sf -g "http://[fd00:42::42]/conf?format=json" > /dev/null; do + echo "Waiting for IPv6 metadata redirect to be ready..." + sleep 1 + done + echo "IPv6 metadata redirect verified working" + containers: + - name: otelcol + image: otelcontribcol:latest + imagePullPolicy: Never + args: + - "--config=/conf/relay" + volumeMounts: + - name: config + mountPath: /conf + volumes: + - name: config + configMap: + name: {{ .Name }}-config + items: + - key: relay + path: relay + - name: metadata-script + configMap: + name: {{ .Name }}-metadata-config diff --git a/processor/resourcedetectionprocessor/testdata/e2e/scaleway/expected.yaml b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/expected.yaml new file mode 100644 index 0000000000000..e93cacdb6d9bb --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/scaleway/expected.yaml @@ -0,0 +1,125 @@ +resourceMetrics: + - resource: + attributes: + - key: cloud.account.id + value: + stringValue: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa + - key: cloud.availability_zone + value: + stringValue: fr-par-1 + - key: cloud.platform + value: + stringValue: scaleway_cloud_compute + - key: cloud.provider + value: + stringValue: scaleway_cloud + - key: cloud.region + value: + stringValue: fr-par + - key: host.id + value: + stringValue: 11111111-1111-1111-1111-111111111111 + - key: host.image.id + value: + stringValue: cccccccc-cccc-cccc-cccc-cccccccccccc + - key: host.image.name + value: + stringValue: Ubuntu 22.04 Jammy Jellyfish + - key: host.name + value: + stringValue: test-scaleway-instance + - key: host.type + value: + stringValue: DEV1-S + schemaUrl: https://opentelemetry.io/schemas/1.9.0 + scopeMetrics: + - metrics: + - description: Total seconds each logical CPU spent on each mode. + name: system.cpu.time + sum: + aggregationTemporality: 2 + dataPoints: + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: idle + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: interrupt + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: nice + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: softirq + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: steal + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: system + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: user + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: wait + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + isMonotonic: true + unit: s + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper From 8a0e2438bf9843cac143db889c92960cb9bb9ca5 Mon Sep 17 00:00:00 2001 From: Paulo Dias <44772900+paulojmdias@users.noreply.github.com> Date: Fri, 23 Jan 2026 00:12:44 +0000 Subject: [PATCH 44/49] [chore][processor/resourcedetection] Add E2E tests for azure detector (#45569) #### Description Add E2E tests for Azure detector #### Link to tracking issue Relates to #24671 --------- Signed-off-by: Paulo Dias --- .../resourcedetectionprocessor/e2e_test.go | 47 ++++++ .../collector/01-metadata-configmap.yaml | 89 ++++++++++++ .../e2e/azure/collector/02-configmap.yaml | 46 ++++++ .../azure/collector/03-serviceaccount.yaml | 5 + .../e2e/azure/collector/04-service.yaml | 12 ++ .../e2e/azure/collector/05-deployment.yaml | 80 +++++++++++ .../testdata/e2e/azure/expected.yaml | 134 ++++++++++++++++++ 7 files changed, 413 insertions(+) create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/azure/collector/01-metadata-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/azure/collector/02-configmap.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/azure/collector/03-serviceaccount.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/azure/collector/04-service.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/azure/collector/05-deployment.yaml create mode 100644 processor/resourcedetectionprocessor/testdata/e2e/azure/expected.yaml diff --git a/processor/resourcedetectionprocessor/e2e_test.go b/processor/resourcedetectionprocessor/e2e_test.go index 097f6d626bc42..7ed397974acff 100644 --- a/processor/resourcedetectionprocessor/e2e_test.go +++ b/processor/resourcedetectionprocessor/e2e_test.go @@ -571,6 +571,53 @@ func TestE2EScalewayDetector(t *testing.T) { }, 3*time.Minute, 1*time.Second) } +// TestE2EAzureDetector tests the Azure detector by deploying a metadata-server +// sidecar that simulates the Azure IMDS and verifying that the resource attributes +// are correctly detected and attached to metrics. +func TestE2EAzureDetector(t *testing.T) { + var expected pmetric.Metrics + expectedFile := filepath.Join("testdata", "e2e", "azure", "expected.yaml") + expected, err := golden.ReadMetrics(expectedFile) + require.NoError(t, err) + + k8sClient, err := k8stest.NewK8sClient(testKubeConfig) + require.NoError(t, err) + + metricsConsumer := new(consumertest.MetricsSink) + shutdownSink := startUpSink(t, metricsConsumer) + defer shutdownSink() + + testID := uuid.NewString()[:8] + collectorObjs := k8stest.CreateCollectorObjects(t, k8sClient, testID, filepath.Join(".", "testdata", "e2e", "azure", "collector"), map[string]string{}, "") + + defer func() { + for _, obj := range collectorObjs { + require.NoErrorf(t, k8stest.DeleteObject(k8sClient, obj), "failed to delete object %s", obj.GetName()) + } + }() + + wantEntries := 10 + waitForData(t, wantEntries, metricsConsumer) + + // Uncomment to regenerate golden file + // golden.WriteMetrics(t, expectedFile+".actual", metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1]) + + require.EventuallyWithT(t, func(tt *assert.CollectT) { + assert.NoError(tt, pmetrictest.CompareMetrics(expected, metricsConsumer.AllMetrics()[len(metricsConsumer.AllMetrics())-1], + pmetrictest.IgnoreTimestamp(), + pmetrictest.IgnoreStartTimestamp(), + pmetrictest.IgnoreScopeVersion(), + pmetrictest.IgnoreResourceMetricsOrder(), + pmetrictest.IgnoreMetricsOrder(), + pmetrictest.IgnoreScopeMetricsOrder(), + pmetrictest.IgnoreMetricDataPointsOrder(), + pmetrictest.IgnoreMetricValues(), + pmetrictest.IgnoreSubsequentDataPoints("system.cpu.time"), + ), + ) + }, 3*time.Minute, 1*time.Second) +} + func replaceWithStar(_ string) string { return "*" } diff --git a/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/01-metadata-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/01-metadata-configmap.yaml new file mode 100644 index 0000000000000..d5461acfd72da --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/01-metadata-configmap.yaml @@ -0,0 +1,89 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-metadata-config + namespace: default +data: + server.py: | + import json + import os + from http.server import BaseHTTPRequestHandler, HTTPServer + from urllib.parse import urlparse, parse_qs + + # Azure IMDS compute metadata format + AZURE_COMPUTE_METADATA = { + "location": "eastus", + "name": "test-azure-vm", + "vmId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "vmSize": "Standard_D2s_v3", + "subscriptionId": "12345678-1234-1234-1234-123456789abc", + "resourceGroupName": "test-resource-group", + "vmScaleSetName": "scaleset-01", + "zone": "az1", + "osProfile": { + "computerName": "test-azure-vm.internal.cloudapp.net" + }, + "tagsList": [ + {"name": "environment", "value": "testing"}, + {"name": "team", "value": "observability"}, + {"name": "cost-center", "value": "12345"} + ] + } + + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + parsed = urlparse(self.path) + path = parsed.path + query = parse_qs(parsed.query) + + # Health check endpoint + if path == "/healthz": + body = b"ok" + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # Azure IMDS compute endpoint + if path == "/metadata/instance/compute": + # Check for required Metadata header + metadata_header = self.headers.get("Metadata") + if metadata_header != "True": + self.send_response(400) + self.send_header("Content-Type", "text/plain") + self.end_headers() + self.wfile.write(b"Missing required Metadata header") + return + + # Check for required query parameters + api_version = query.get("api-version", [None])[0] + format_param = query.get("format", [None])[0] + + if not api_version: + self.send_response(400) + self.send_header("Content-Type", "text/plain") + self.end_headers() + self.wfile.write(b"Missing api-version parameter") + return + + body = json.dumps(AZURE_COMPUTE_METADATA).encode("utf-8") + self.send_response(200) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + + # Not found + self.send_response(404) + self.end_headers() + + def log_message(self, fmt, *args): + return + + if __name__ == "__main__": + port = int(os.environ.get("PORT", "80")) + server = HTTPServer(("", port), Handler) + server.serve_forever() diff --git a/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/02-configmap.yaml b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/02-configmap.yaml new file mode 100644 index 0000000000000..d3cc6846a70a7 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/02-configmap.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Name }}-config + namespace: default +data: + relay: | + exporters: + otlp: + endpoint: {{ .HostEndpoint }}:4317 + tls: + insecure: true + extensions: + health_check: + endpoint: 0.0.0.0:13133 + processors: + resourcedetection: + detectors: [azure] + timeout: 2s + override: false + azure: + resource_attributes: + cloud.availability_zone: + enabled: true + tags: + - ^environment$ + - ^team$ + receivers: + hostmetrics: + collection_interval: 1s + scrapers: + cpu: + service: + telemetry: + logs: + level: "debug" + extensions: + - health_check + pipelines: + metrics: + receivers: + - hostmetrics + processors: + - resourcedetection + exporters: + - otlp diff --git a/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/03-serviceaccount.yaml b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/03-serviceaccount.yaml new file mode 100644 index 0000000000000..7a9803b445b6c --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/03-serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Name }}-sa + namespace: default diff --git a/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/04-service.yaml b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/04-service.yaml new file mode 100644 index 0000000000000..94587d231bbf0 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/04-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Name }} + namespace: default +spec: + selector: + app: {{ .Name }} + ports: + - name: health + port: 13133 + targetPort: 13133 diff --git a/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/05-deployment.yaml b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/05-deployment.yaml new file mode 100644 index 0000000000000..7214d769be5a8 --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/azure/collector/05-deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Name }} + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Name }} + template: + metadata: + labels: + app: {{ .Name }} + spec: + serviceAccountName: {{ .Name }}-sa + initContainers: + - name: metadata-server + image: python:3.13-alpine + imagePullPolicy: IfNotPresent + restartPolicy: Always + securityContext: + runAsUser: 0 + capabilities: + drop: + - "ALL" + add: + - "NET_BIND_SERVICE" + command: + - python3 + - /scripts/server.py + env: + - name: PORT + value: "80" + ports: + - containerPort: 80 + name: metadata + startupProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 1 + periodSeconds: 1 + failureThreshold: 10 + volumeMounts: + - name: metadata-script + mountPath: /scripts + - name: setup-network + image: alpine:3.21 + imagePullPolicy: IfNotPresent + securityContext: + capabilities: + add: + - NET_ADMIN + command: + - sh + - -c + - | + apk add --no-cache iptables + iptables -t nat -A OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:80 + echo "iptables rule added to redirect 169.254.169.254:80 -> 127.0.0.1:80" + containers: + - name: otelcol + image: otelcontribcol:latest + imagePullPolicy: Never + args: + - "--config=/conf/relay" + volumeMounts: + - name: config + mountPath: /conf + volumes: + - name: config + configMap: + name: {{ .Name }}-config + items: + - key: relay + path: relay + - name: metadata-script + configMap: + name: {{ .Name }}-metadata-config diff --git a/processor/resourcedetectionprocessor/testdata/e2e/azure/expected.yaml b/processor/resourcedetectionprocessor/testdata/e2e/azure/expected.yaml new file mode 100644 index 0000000000000..0be2b278415cc --- /dev/null +++ b/processor/resourcedetectionprocessor/testdata/e2e/azure/expected.yaml @@ -0,0 +1,134 @@ +resourceMetrics: + - resource: + attributes: + - key: azure.resourcegroup.name + value: + stringValue: test-resource-group + - key: azure.tag.environment + value: + stringValue: testing + - key: azure.tag.team + value: + stringValue: observability + - key: azure.vm.name + value: + stringValue: test-azure-vm + - key: azure.vm.scaleset.name + value: + stringValue: scaleset-01 + - key: azure.vm.size + value: + stringValue: Standard_D2s_v3 + - key: cloud.account.id + value: + stringValue: 12345678-1234-1234-1234-123456789abc + - key: cloud.availability_zone + value: + stringValue: "az1" + - key: cloud.platform + value: + stringValue: azure_vm + - key: cloud.provider + value: + stringValue: azure + - key: cloud.region + value: + stringValue: eastus + - key: host.id + value: + stringValue: a1b2c3d4-e5f6-7890-abcd-ef1234567890 + - key: host.name + value: + stringValue: test-azure-vm.internal.cloudapp.net + schemaUrl: https://opentelemetry.io/schemas/1.9.0 + scopeMetrics: + - metrics: + - description: Total seconds each logical CPU spent on each mode. + name: system.cpu.time + sum: + aggregationTemporality: 2 + dataPoints: + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: idle + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: interrupt + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: nice + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: softirq + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: steal + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: system + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: user + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + - asDouble: 1.0 + attributes: + - key: cpu + value: + stringValue: cpu0 + - key: state + value: + stringValue: wait + startTimeUnixNano: "1000000" + timeUnixNano: "2000000" + isMonotonic: true + unit: s + scope: + name: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper From ea79aa4d01426a58eced85638061c21f0a71ad00 Mon Sep 17 00:00:00 2001 From: Vengal rao Date: Thu, 22 Jan 2026 20:56:14 -0800 Subject: [PATCH 45/49] Update Move functions in routing connector to not use pointer. (#45563) #### Description #### Link to tracking issue Fixes #### Testing #### Documentation --- ...al-reduce-allocs-in-routing-connector.yaml | 27 ++++ .../internal/pdatautil/utils.go | 23 ++++ .../internal/plogutil/logs.go | 26 ++-- .../internal/pmetricutil/metrics.go | 120 +++++++++--------- .../internal/ptraceutil/traces.go | 30 +++-- 5 files changed, 141 insertions(+), 85 deletions(-) create mode 100644 .chloggen/vengal-reduce-allocs-in-routing-connector.yaml create mode 100644 connector/routingconnector/internal/pdatautil/utils.go diff --git a/.chloggen/vengal-reduce-allocs-in-routing-connector.yaml b/.chloggen/vengal-reduce-allocs-in-routing-connector.yaml new file mode 100644 index 0000000000000..f0be23b14ab06 --- /dev/null +++ b/.chloggen/vengal-reduce-allocs-in-routing-connector.yaml @@ -0,0 +1,27 @@ +# 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. receiver/filelog) +component: connector/routing + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Update existing util functions to reduce allocs. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [45061] + +# (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: + +# 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: [api] diff --git a/connector/routingconnector/internal/pdatautil/utils.go b/connector/routingconnector/internal/pdatautil/utils.go new file mode 100644 index 0000000000000..8efdb1e116ad8 --- /dev/null +++ b/connector/routingconnector/internal/pdatautil/utils.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package pdatautil // import "github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector/internal/pdatautil" + +// OnceValue to set a given value only once. +type OnceValue[K any] struct { + val K + isInit bool +} + +func (ov *OnceValue[K]) IsInit() bool { + return ov.isInit +} + +func (ov *OnceValue[K]) Init(val K) { + ov.isInit = true + ov.val = val +} + +func (ov *OnceValue[K]) Value() K { + return ov.val +} diff --git a/connector/routingconnector/internal/plogutil/logs.go b/connector/routingconnector/internal/plogutil/logs.go index dec69abf958c4..9bafb35a7c383 100644 --- a/connector/routingconnector/internal/plogutil/logs.go +++ b/connector/routingconnector/internal/plogutil/logs.go @@ -5,6 +5,8 @@ package plogutil // import "github.com/open-telemetry/opentelemetry-collector-co import ( "go.opentelemetry.io/collector/pdata/plog" + + "github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector/internal/pdatautil" ) // MoveResourcesIf calls f sequentially for each ResourceLogs present in the first plog.Logs. @@ -27,27 +29,25 @@ func MoveRecordsWithContextIf(from, to plog.Logs, f func(plog.ResourceLogs, plog rls := from.ResourceLogs() rls.RemoveIf(func(rl plog.ResourceLogs) bool { sls := rl.ScopeLogs() - var rlCopy *plog.ResourceLogs + var rlCopy pdatautil.OnceValue[plog.ResourceLogs] sls.RemoveIf(func(sl plog.ScopeLogs) bool { lrs := sl.LogRecords() - var slCopy *plog.ScopeLogs + var slCopy pdatautil.OnceValue[plog.ScopeLogs] lrs.RemoveIf(func(lr plog.LogRecord) bool { if !f(rl, sl, lr) { return false } - if rlCopy == nil { - rlc := to.ResourceLogs().AppendEmpty() - rlCopy = &rlc - rl.Resource().CopyTo(rlCopy.Resource()) - rlCopy.SetSchemaUrl(rl.SchemaUrl()) + if !rlCopy.IsInit() { + rlCopy.Init(to.ResourceLogs().AppendEmpty()) + rl.Resource().CopyTo(rlCopy.Value().Resource()) + rlCopy.Value().SetSchemaUrl(rl.SchemaUrl()) } - if slCopy == nil { - slc := rlCopy.ScopeLogs().AppendEmpty() - slCopy = &slc - sl.Scope().CopyTo(slCopy.Scope()) - slCopy.SetSchemaUrl(sl.SchemaUrl()) + if !slCopy.IsInit() { + slCopy.Init(rlCopy.Value().ScopeLogs().AppendEmpty()) + sl.Scope().CopyTo(slCopy.Value().Scope()) + slCopy.Value().SetSchemaUrl(sl.SchemaUrl()) } - lr.MoveTo(slCopy.LogRecords().AppendEmpty()) + lr.MoveTo(slCopy.Value().LogRecords().AppendEmpty()) return true }) return sl.LogRecords().Len() == 0 diff --git a/connector/routingconnector/internal/pmetricutil/metrics.go b/connector/routingconnector/internal/pmetricutil/metrics.go index b858e7113c970..61c60cf075000 100644 --- a/connector/routingconnector/internal/pmetricutil/metrics.go +++ b/connector/routingconnector/internal/pmetricutil/metrics.go @@ -3,7 +3,11 @@ package pmetricutil // import "github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector/internal/pmetricutil" -import "go.opentelemetry.io/collector/pdata/pmetric" +import ( + "go.opentelemetry.io/collector/pdata/pmetric" + + "github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector/internal/pdatautil" +) // MoveResourcesIf calls f sequentially for each ResourceSpans present in the first pmetric.Metrics. // If f returns true, the element is removed from the first pmetric.Metrics and added to the second pmetric.Metrics. @@ -25,21 +29,21 @@ func MoveMetricsWithContextIf(from, to pmetric.Metrics, f func(pmetric.ResourceM rms := from.ResourceMetrics() rms.RemoveIf(func(rm pmetric.ResourceMetrics) bool { sms := rm.ScopeMetrics() - var rmCopy *pmetric.ResourceMetrics + var rmCopy pdatautil.OnceValue[pmetric.ResourceMetrics] sms.RemoveIf(func(sm pmetric.ScopeMetrics) bool { ms := sm.Metrics() - var smCopy *pmetric.ScopeMetrics + var smCopy pdatautil.OnceValue[pmetric.ScopeMetrics] ms.RemoveIf(func(m pmetric.Metric) bool { if !f(rm, sm, m) { return false } - if rmCopy == nil { - rmCopy = copyResourceMetrics(rm, to.ResourceMetrics()) + if !rmCopy.IsInit() { + rmCopy.Init(copyResourceMetrics(rm, to.ResourceMetrics())) } - if smCopy == nil { - smCopy = copyScopeMetrics(sm, rmCopy.ScopeMetrics()) + if !smCopy.IsInit() { + smCopy.Init(copyScopeMetrics(sm, rmCopy.Value().ScopeMetrics())) } - m.MoveTo(smCopy.Metrics().AppendEmpty()) + m.MoveTo(smCopy.Value().Metrics().AppendEmpty()) return true }) return sm.Metrics().Len() == 0 @@ -56,12 +60,12 @@ func MoveDataPointsWithContextIf(from, to pmetric.Metrics, f func(pmetric.Resour rms := from.ResourceMetrics() rms.RemoveIf(func(rm pmetric.ResourceMetrics) bool { sms := rm.ScopeMetrics() - var rmCopy *pmetric.ResourceMetrics + var rmCopy pdatautil.OnceValue[pmetric.ResourceMetrics] sms.RemoveIf(func(sm pmetric.ScopeMetrics) bool { ms := sm.Metrics() - var smCopy *pmetric.ScopeMetrics + var smCopy pdatautil.OnceValue[pmetric.ScopeMetrics] ms.RemoveIf(func(m pmetric.Metric) bool { - var mCopy *pmetric.Metric + var mCopy pdatautil.OnceValue[pmetric.Metric] // TODO condense this code switch m.Type() { @@ -71,17 +75,17 @@ func MoveDataPointsWithContextIf(from, to pmetric.Metrics, f func(pmetric.Resour if !f(rm, sm, m, dp) { return false } - if rmCopy == nil { - rmCopy = copyResourceMetrics(rm, to.ResourceMetrics()) + if !rmCopy.IsInit() { + rmCopy.Init(copyResourceMetrics(rm, to.ResourceMetrics())) } - if smCopy == nil { - smCopy = copyScopeMetrics(sm, rmCopy.ScopeMetrics()) + if !smCopy.IsInit() { + smCopy.Init(copyScopeMetrics(sm, rmCopy.Value().ScopeMetrics())) } - if mCopy == nil { - mCopy = copyMetricDescription(m, smCopy.Metrics()) - mCopy.SetEmptyGauge() + if !mCopy.IsInit() { + mCopy.Init(copyMetricDescription(m, smCopy.Value().Metrics())) + mCopy.Value().SetEmptyGauge() } - dp.MoveTo(mCopy.Gauge().DataPoints().AppendEmpty()) + dp.MoveTo(mCopy.Value().Gauge().DataPoints().AppendEmpty()) return true }) return dps.Len() == 0 @@ -91,18 +95,18 @@ func MoveDataPointsWithContextIf(from, to pmetric.Metrics, f func(pmetric.Resour if !f(rm, sm, m, dp) { return false } - if rmCopy == nil { - rmCopy = copyResourceMetrics(rm, to.ResourceMetrics()) + if !rmCopy.IsInit() { + rmCopy.Init(copyResourceMetrics(rm, to.ResourceMetrics())) } - if smCopy == nil { - smCopy = copyScopeMetrics(sm, rmCopy.ScopeMetrics()) + if !smCopy.IsInit() { + smCopy.Init(copyScopeMetrics(sm, rmCopy.Value().ScopeMetrics())) } - if mCopy == nil { - mCopy = copyMetricDescription(m, smCopy.Metrics()) - mCopy.SetEmptySum().SetAggregationTemporality(m.Sum().AggregationTemporality()) - mCopy.Sum().SetIsMonotonic(m.Sum().IsMonotonic()) + if !mCopy.IsInit() { + mCopy.Init(copyMetricDescription(m, smCopy.Value().Metrics())) + mCopy.Value().SetEmptySum().SetAggregationTemporality(m.Sum().AggregationTemporality()) + mCopy.Value().Sum().SetIsMonotonic(m.Sum().IsMonotonic()) } - dp.MoveTo(mCopy.Sum().DataPoints().AppendEmpty()) + dp.MoveTo(mCopy.Value().Sum().DataPoints().AppendEmpty()) return true }) return dps.Len() == 0 @@ -112,17 +116,17 @@ func MoveDataPointsWithContextIf(from, to pmetric.Metrics, f func(pmetric.Resour if !f(rm, sm, m, dp) { return false } - if rmCopy == nil { - rmCopy = copyResourceMetrics(rm, to.ResourceMetrics()) + if !rmCopy.IsInit() { + rmCopy.Init(copyResourceMetrics(rm, to.ResourceMetrics())) } - if smCopy == nil { - smCopy = copyScopeMetrics(sm, rmCopy.ScopeMetrics()) + if !smCopy.IsInit() { + smCopy.Init(copyScopeMetrics(sm, rmCopy.Value().ScopeMetrics())) } - if mCopy == nil { - mCopy = copyMetricDescription(m, smCopy.Metrics()) - mCopy.SetEmptyHistogram().SetAggregationTemporality(m.Histogram().AggregationTemporality()) + if !mCopy.IsInit() { + mCopy.Init(copyMetricDescription(m, smCopy.Value().Metrics())) + mCopy.Value().SetEmptyHistogram().SetAggregationTemporality(m.Histogram().AggregationTemporality()) } - dp.MoveTo(mCopy.Histogram().DataPoints().AppendEmpty()) + dp.MoveTo(mCopy.Value().Histogram().DataPoints().AppendEmpty()) return true }) return dps.Len() == 0 @@ -132,17 +136,17 @@ func MoveDataPointsWithContextIf(from, to pmetric.Metrics, f func(pmetric.Resour if !f(rm, sm, m, dp) { return false } - if rmCopy == nil { - rmCopy = copyResourceMetrics(rm, to.ResourceMetrics()) + if !rmCopy.IsInit() { + rmCopy.Init(copyResourceMetrics(rm, to.ResourceMetrics())) } - if smCopy == nil { - smCopy = copyScopeMetrics(sm, rmCopy.ScopeMetrics()) + if !smCopy.IsInit() { + smCopy.Init(copyScopeMetrics(sm, rmCopy.Value().ScopeMetrics())) } - if mCopy == nil { - mCopy = copyMetricDescription(m, smCopy.Metrics()) - mCopy.SetEmptyExponentialHistogram().SetAggregationTemporality(m.ExponentialHistogram().AggregationTemporality()) + if !mCopy.IsInit() { + mCopy.Init(copyMetricDescription(m, smCopy.Value().Metrics())) + mCopy.Value().SetEmptyExponentialHistogram().SetAggregationTemporality(m.ExponentialHistogram().AggregationTemporality()) } - dp.MoveTo(mCopy.ExponentialHistogram().DataPoints().AppendEmpty()) + dp.MoveTo(mCopy.Value().ExponentialHistogram().DataPoints().AppendEmpty()) return true }) return dps.Len() == 0 @@ -152,17 +156,17 @@ func MoveDataPointsWithContextIf(from, to pmetric.Metrics, f func(pmetric.Resour if !f(rm, sm, m, dp) { return false } - if rmCopy == nil { - rmCopy = copyResourceMetrics(rm, to.ResourceMetrics()) + if !rmCopy.IsInit() { + rmCopy.Init(copyResourceMetrics(rm, to.ResourceMetrics())) } - if smCopy == nil { - smCopy = copyScopeMetrics(sm, rmCopy.ScopeMetrics()) + if !smCopy.IsInit() { + smCopy.Init(copyScopeMetrics(sm, rmCopy.Value().ScopeMetrics())) } - if mCopy == nil { - mCopy = copyMetricDescription(m, smCopy.Metrics()) - mCopy.SetEmptySummary() + if !mCopy.IsInit() { + mCopy.Init(copyMetricDescription(m, smCopy.Value().Metrics())) + mCopy.Value().SetEmptySummary() } - dp.MoveTo(mCopy.Summary().DataPoints().AppendEmpty()) + dp.MoveTo(mCopy.Value().Summary().DataPoints().AppendEmpty()) return true }) return dps.Len() == 0 @@ -176,24 +180,24 @@ func MoveDataPointsWithContextIf(from, to pmetric.Metrics, f func(pmetric.Resour }) } -func copyResourceMetrics(from pmetric.ResourceMetrics, to pmetric.ResourceMetricsSlice) *pmetric.ResourceMetrics { +func copyResourceMetrics(from pmetric.ResourceMetrics, to pmetric.ResourceMetricsSlice) pmetric.ResourceMetrics { rmc := to.AppendEmpty() from.Resource().CopyTo(rmc.Resource()) rmc.SetSchemaUrl(from.SchemaUrl()) - return &rmc + return rmc } -func copyScopeMetrics(from pmetric.ScopeMetrics, to pmetric.ScopeMetricsSlice) *pmetric.ScopeMetrics { +func copyScopeMetrics(from pmetric.ScopeMetrics, to pmetric.ScopeMetricsSlice) pmetric.ScopeMetrics { smc := to.AppendEmpty() from.Scope().CopyTo(smc.Scope()) smc.SetSchemaUrl(from.SchemaUrl()) - return &smc + return smc } -func copyMetricDescription(from pmetric.Metric, to pmetric.MetricSlice) *pmetric.Metric { +func copyMetricDescription(from pmetric.Metric, to pmetric.MetricSlice) pmetric.Metric { mc := to.AppendEmpty() mc.SetName(from.Name()) mc.SetDescription(from.Description()) mc.SetUnit(from.Unit()) - return &mc + return mc } diff --git a/connector/routingconnector/internal/ptraceutil/traces.go b/connector/routingconnector/internal/ptraceutil/traces.go index 140c946f54708..25ada879a8c7e 100644 --- a/connector/routingconnector/internal/ptraceutil/traces.go +++ b/connector/routingconnector/internal/ptraceutil/traces.go @@ -3,7 +3,11 @@ package ptraceutil // import "github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector/internal/ptraceutil" -import "go.opentelemetry.io/collector/pdata/ptrace" +import ( + "go.opentelemetry.io/collector/pdata/ptrace" + + "github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector/internal/pdatautil" +) // MoveResourcesIf calls f sequentially for each ResourceSpans present in the first ptrace.Traces. // If f returns true, the element is removed from the first ptrace.Traces and added to the second ptrace.Traces. @@ -25,27 +29,25 @@ func MoveSpansWithContextIf(from, to ptrace.Traces, f func(ptrace.ResourceSpans, resourceSpansSlice := from.ResourceSpans() resourceSpansSlice.RemoveIf(func(rs ptrace.ResourceSpans) bool { scopeSpanSlice := rs.ScopeSpans() - var resourceSpansCopy *ptrace.ResourceSpans + var resourceSpansCopy pdatautil.OnceValue[ptrace.ResourceSpans] scopeSpanSlice.RemoveIf(func(ss ptrace.ScopeSpans) bool { spanSlice := ss.Spans() - var scopeSpansCopy *ptrace.ScopeSpans + var scopeSpansCopy pdatautil.OnceValue[ptrace.ScopeSpans] spanSlice.RemoveIf(func(span ptrace.Span) bool { if !f(rs, ss, span) { return false } - if resourceSpansCopy == nil { - rmc := to.ResourceSpans().AppendEmpty() - resourceSpansCopy = &rmc - rs.Resource().CopyTo(resourceSpansCopy.Resource()) - resourceSpansCopy.SetSchemaUrl(rs.SchemaUrl()) + if !resourceSpansCopy.IsInit() { + resourceSpansCopy.Init(to.ResourceSpans().AppendEmpty()) + rs.Resource().CopyTo(resourceSpansCopy.Value().Resource()) + resourceSpansCopy.Value().SetSchemaUrl(rs.SchemaUrl()) } - if scopeSpansCopy == nil { - smc := resourceSpansCopy.ScopeSpans().AppendEmpty() - scopeSpansCopy = &smc - ss.Scope().CopyTo(scopeSpansCopy.Scope()) - scopeSpansCopy.SetSchemaUrl(ss.SchemaUrl()) + if !scopeSpansCopy.IsInit() { + scopeSpansCopy.Init(resourceSpansCopy.Value().ScopeSpans().AppendEmpty()) + ss.Scope().CopyTo(scopeSpansCopy.Value().Scope()) + scopeSpansCopy.Value().SetSchemaUrl(ss.SchemaUrl()) } - span.MoveTo(scopeSpansCopy.Spans().AppendEmpty()) + span.MoveTo(scopeSpansCopy.Value().Spans().AppendEmpty()) return true }) return ss.Spans().Len() == 0 From 216e339e0dc13279dab3d1c7229283f47ab40398 Mon Sep 17 00:00:00 2001 From: Keagan Peet Date: Fri, 23 Jan 2026 02:24:00 -0500 Subject: [PATCH 46/49] [receiver/filelog] Suppress repeated permission denied errors (#44350) #### Description Added logic to suppress repeated error logs due to file permission errors within the `filelog` receiver. - Updated `makeFingerprint` to check for permission errors via [os.isPermission](https://pkg.go.dev/os#IsPermission) - Log an error only on the first occurrence for a given file path and add it to an in-memory map, guarded by a mutex to avoid race conditions for concurrent read/writes. - Debug log only for subsequent read attempt (to avoid spam) - When the file becomes readable, info log a message and remove the path from the stored set #### Link to tracking issue Fixes https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/39491 #### Testing New unit test verifying: - First unreadable-open produces exactly one Error-level log. - Second attempt does not produce another Error-level log. - When permissions are restored, an Info-level `Previously unreadable...` message appears. NOTE: Test skips Windows due to unreliable os.Chmod behavior #### Documentation Added changelog yaml entry summarizing the issue and fix. --- .chloggen/issue-39491.yaml | 29 ++++ pkg/stanza/fileconsumer/file.go | 36 ++++- pkg/stanza/fileconsumer/unreadable_test.go | 165 +++++++++++++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 .chloggen/issue-39491.yaml create mode 100644 pkg/stanza/fileconsumer/unreadable_test.go diff --git a/.chloggen/issue-39491.yaml b/.chloggen/issue-39491.yaml new file mode 100644 index 0000000000000..e4938d310f433 --- /dev/null +++ b/.chloggen/issue-39491.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. receiver/filelog) +component: receiver/filelog + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Suppress repeated permission-denied errors + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [39491] + +# (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: | + Only one error is logged per file per process run, and an informational message is emitted when the file becomes readable again. + This reduces log spam and improves clarity for operators. + +# 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/pkg/stanza/fileconsumer/file.go b/pkg/stanza/fileconsumer/file.go index 5982cabb63d14..c08bebbd8526a 100644 --- a/pkg/stanza/fileconsumer/file.go +++ b/pkg/stanza/fileconsumer/file.go @@ -5,7 +5,9 @@ package fileconsumer // import "github.com/open-telemetry/opentelemetry-collecto import ( "context" + "errors" "fmt" + "io/fs" "os" "sync" "time" @@ -22,6 +24,12 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/operator" ) +const ( + // maxUnreadableEntries limits the number of paths tracked in the unreadable map + // to prevent memory issues when many files have permission errors. + maxUnreadableEntries = 10000 +) + type Manager struct { set component.TelemetrySettings wg sync.WaitGroup @@ -39,12 +47,17 @@ type Manager struct { pollsToArchive int telemetryBuilder *metadata.TelemetryBuilder + + unreadable map[string]struct{} } func (m *Manager) Start(persister operator.Persister) error { ctx, cancel := context.WithCancel(context.Background()) m.cancel = cancel + // initialize runtime-only tracking of unreadable paths + m.unreadable = make(map[string]struct{}) + if _, err := m.fileMatcher.MatchFiles(); err != nil { m.set.Logger.Warn("finding files", zap.Error(err)) } @@ -175,6 +188,8 @@ func (m *Manager) consume(ctx context.Context, paths []string) { m.telemetryBuilder.FileconsumerOpenFiles.Add(ctx, int64(0-m.tracker.EndConsume())) } +// makeFingerprint opens `path` and computes a fingerprint for the file +// and contains logic to only log file permission errors once per file per startup func (m *Manager) makeFingerprint(path string) (*fingerprint.Fingerprint, *os.File) { // Normalize the path to handle Windows UNC paths correctly normalizedPath, wasCorrupted := normalizePath(path) @@ -183,10 +198,29 @@ func (m *Manager) makeFingerprint(path string) (*fingerprint.Fingerprint, *os.Fi } file, err := os.Open(normalizedPath) // #nosec - operator must read in files defined by user if err != nil { - m.set.Logger.Error("Failed to open file", zap.Error(err), zap.String("original_path", path), zap.String("normalized_path", normalizedPath)) + // If a file is unreadable due to permissions error, store path in map and log error once (unless in debug mode) + if errors.Is(err, fs.ErrPermission) { + _, seen := m.unreadable[path] + if !seen { + // Limit map size to prevent unbounded growth + if len(m.unreadable) < maxUnreadableEntries { + m.unreadable[path] = struct{}{} + } + m.set.Logger.Error("Failed to open file - unreadable", zap.Error(err), zap.String("original_path", path), zap.String("normalized_path", normalizedPath)) + } + } else { + // For non-permission errors, always log + m.set.Logger.Error("Failed to open file", zap.Error(err), zap.String("original_path", path), zap.String("normalized_path", normalizedPath)) + } return nil, nil } + // Notify if previously unreadable file is now able to be read + if _, seen := m.unreadable[path]; seen { + m.set.Logger.Info("Previously unreadable file is now readable", zap.String("path", path)) + delete(m.unreadable, path) + } + fp, err := m.readerFactory.NewFingerprint(file) if err != nil { if err = file.Close(); err != nil { diff --git a/pkg/stanza/fileconsumer/unreadable_test.go b/pkg/stanza/fileconsumer/unreadable_test.go new file mode 100644 index 0000000000000..e5b43d274a26d --- /dev/null +++ b/pkg/stanza/fileconsumer/unreadable_test.go @@ -0,0 +1,165 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package fileconsumer + +import ( + "os" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/internal/filetest" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/testutil" +) + +// TestUnreadableFileLoggedOnce verifies that permission-denied errors when +// opening files are logged only once per file per process run, and that an +// informational message is emitted when the file later becomes readable. +func TestUnreadableFileLoggedOnce(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("permission manipulation tests are not reliable on Windows") + } + + t.Parallel() + + tempDir := t.TempDir() + cfg := NewConfig().includeDir(tempDir) + operator, _ := testManager(t, cfg) + + // Create a file and remove permissions so open will fail + f := filetest.OpenTemp(t, tempDir) + _, err := f.WriteString("abc\n") + require.NoError(t, err) + require.NoError(t, f.Close()) + require.NoError(t, os.Chmod(f.Name(), 0)) + + core, obs := observer.New(zapcore.DebugLevel) + logger := zap.New(core) + set := componenttest.NewNopTelemetrySettings() + set.Logger = logger + + operator.set.Logger = set.Logger + require.NoError(t, operator.Start(testutil.NewUnscopedMockPersister())) + defer func() { + require.NoError(t, operator.Stop()) + }() + + // First poll should attempt to open and log an error once + operator.poll(t.Context()) + + // Verify the unreadable map recorded the path and exactly one error was logged + require.Eventually(t, func() bool { + return len(operator.unreadable) == 1 + }, 2*time.Second, 10*time.Millisecond, "expected unreadable map to have one entry after first poll") + + require.Eventually(t, func() bool { + countErrMsgs := 0 + for _, e := range obs.All() { + if e.Level == zapcore.ErrorLevel && e.Message == "Failed to open file - unreadable" { + countErrMsgs++ + } + } + return countErrMsgs == 1 + }, 2*time.Second, 10*time.Millisecond, "expected exactly one 'Failed to open file - unreadable' error after first poll") + + // Second poll should not add another error-level log for the same path + operator.poll(t.Context()) + + require.Eventually(t, func() bool { + countErrMsgs := 0 + for _, e := range obs.All() { + if e.Level == zapcore.ErrorLevel && e.Message == "Failed to open file - unreadable" { + countErrMsgs++ + } + } + return countErrMsgs == 1 + }, 2*time.Second, 10*time.Millisecond, "expected still exactly one 'Failed to open file - unreadable' error after second poll") + + // Verify the unreadable map still contains the entry (no reinitialization) + require.Len(t, operator.unreadable, 1, "expected unreadable map to still have one entry after second poll") + + // Now make the file readable again and poll; should emit an info message + require.NoError(t, os.Chmod(f.Name(), 0o644)) + operator.poll(t.Context()) + + require.Eventually(t, func() bool { + for _, e := range obs.All() { + if e.Level == zapcore.InfoLevel && e.Message == "Previously unreadable file is now readable" { + return true + } + } + return false + }, 2*time.Second, 10*time.Millisecond, "expected at least one info message when file becomes readable") +} + +// TestNonPermissionErrorsAlwaysLogged verifies that errors other than permission +// errors (e.g., file not found) are always logged and not suppressed. +func TestNonPermissionErrorsAlwaysLogged(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + cfg := NewConfig().includeDir(tempDir) + operator, _ := testManager(t, cfg) + + core, obs := observer.New(zapcore.DebugLevel) + logger := zap.New(core) + set := componenttest.NewNopTelemetrySettings() + set.Logger = logger + + operator.set.Logger = set.Logger + require.NoError(t, operator.Start(testutil.NewUnscopedMockPersister())) + defer func() { + require.NoError(t, operator.Stop()) + }() + + // Directly call makeFingerprint with a non-existent file path + // This simulates a file being deleted after matching but before opening + nonExistentPath := tempDir + "/non_existent_file.log" + + // First call should log an error + fp, file := operator.makeFingerprint(nonExistentPath) + require.Nil(t, fp) + require.Nil(t, file) + + require.Eventually(t, func() bool { + for _, e := range obs.All() { + if e.Level == zapcore.ErrorLevel && e.Message == "Failed to open file" { + return true + } + } + return false + }, 2*time.Second, 10*time.Millisecond, "expected an error log on first call for non-existent file") + + countBeforeSecondCall := 0 + for _, e := range obs.All() { + if e.Level == zapcore.ErrorLevel && e.Message == "Failed to open file" { + countBeforeSecondCall++ + } + } + + // Second call should also log an error (not suppressed like permission errors) + fp, file = operator.makeFingerprint(nonExistentPath) + require.Nil(t, fp) + require.Nil(t, file) + + require.Eventually(t, func() bool { + countAfterSecondCall := 0 + for _, e := range obs.All() { + if e.Level == zapcore.ErrorLevel && e.Message == "Failed to open file" { + countAfterSecondCall++ + } + } + // Should have at least one more error than before + return countAfterSecondCall > countBeforeSecondCall + }, 2*time.Second, 10*time.Millisecond, "expected another error log on second call for non-permission errors") + + // Verify the unreadable map is empty (non-permission errors shouldn't be tracked) + require.Empty(t, operator.unreadable, "expected unreadable map to be empty for non-permission errors") +} From 18e7362e5aa0f46dca1f2dbdc3d18c1eaf12f293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Constan=C3=A7a=20Manteigas?= <113898685+constanca-m@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:24:51 +0100 Subject: [PATCH 47/49] [pkg/stanza/trim] Improve performance of Trailing and Leading (#45281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### Description Improves the performance of `Trailing` and `Leading` functions. You can see it in the benchmarks results here: ``` goos: linux goarch: amd64 pkg: github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza/trim cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz │ Previous code │ This PR │ │ sec/op │ sec/op vs base │ TrimFuncs/leading/small_clean-16 20.345n ± 23% 2.924n ± 18% -85.63% (p=0.000 n=10) TrimFuncs/leading/small_dirty-16 24.65n ± 30% 12.77n ± 10% -48.22% (p=0.000 n=10) TrimFuncs/leading/all_space-16 25.91n ± 13% 13.38n ± 8% -48.35% (p=0.000 n=10) TrimFuncs/leading/large_dirty-16 21.440n ± 15% 8.457n ± 7% -60.56% (p=0.000 n=10) TrimFuncs/trailing/small_clean-16 16.400n ± 8% 2.737n ± 3% -83.31% (p=0.000 n=10) TrimFuncs/trailing/small_dirty-16 18.88n ± 14% 10.37n ± 8% -45.10% (p=0.000 n=10) TrimFuncs/trailing/all_space-16 22.61n ± 5% 12.10n ± 7% -46.50% (p=0.000 n=10) TrimFuncs/trailing/large_dirty-16 19.085n ± 10% 7.271n ± 22% -61.90% (p=0.000 n=10) TrimFuncs/whitespace/small_clean-16 29.895n ± 4% 3.788n ± 3% -87.33% (p=0.000 n=10) TrimFuncs/whitespace/small_dirty-16 41.10n ± 11% 17.20n ± 3% -58.16% (p=0.000 n=10) TrimFuncs/whitespace/all_space-16 24.47n ± 7% 13.22n ± 17% -45.97% (p=0.000 n=10) TrimFuncs/whitespace/large_dirty-16 36.59n ± 15% 12.82n ± 15% -64.95% (p=0.000 n=10) TrimFuncs/nop/small_clean-16 2.071n ± 8% 1.937n ± 7% ~ (p=0.052 n=10) TrimFuncs/nop/small_dirty-16 2.048n ± 7% 1.939n ± 4% ~ (p=0.063 n=10) TrimFuncs/nop/all_space-16 2.346n ± 10% 2.022n ± 4% -13.83% (p=0.000 n=10) TrimFuncs/nop/large_dirty-16 2.355n ± 9% 2.175n ± 12% -7.62% (p=0.023 n=10) WithFunc/baseline_scanlines-16 215.8n ± 11% 219.5n ± 3% ~ (p=0.755 n=10) WithFunc/with_whitespace-16 237.3n ± 12% 237.9n ± 13% ~ (p=0.912 n=10) ToLength/under_limit-16 241.7n ± 12% 267.3n ± 17% +10.57% (p=0.002 n=10) ToLength/over_limit-16 266.9n ± 12% 247.1n ± 20% ~ (p=0.393 n=10) geomean 23.72n 12.33n -48.00% │ Previous code │ This PR │ │ B/op │ B/op vs base │ TrimFuncs/leading/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/leading/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/leading/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/leading/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ WithFunc/baseline_scanlines-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ WithFunc/with_whitespace-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ToLength/under_limit-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ToLength/over_limit-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ Previous code │ This PR │ │ allocs/op │ allocs/op vs base │ TrimFuncs/leading/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/leading/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/leading/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/leading/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/trailing/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/whitespace/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/small_clean-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/small_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/all_space-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ TrimFuncs/nop/large_dirty-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ WithFunc/baseline_scanlines-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ WithFunc/with_whitespace-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ToLength/under_limit-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ ToLength/over_limit-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` --- pkg/stanza/trim/benchmark_test.go | 135 ++++++++++++++++++++++++++++++ pkg/stanza/trim/trim.go | 19 +++-- pkg/stanza/trim/trim_test.go | 70 ++++++++++++++++ 3 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 pkg/stanza/trim/benchmark_test.go diff --git a/pkg/stanza/trim/benchmark_test.go b/pkg/stanza/trim/benchmark_test.go new file mode 100644 index 0000000000000..33ef5ed01226c --- /dev/null +++ b/pkg/stanza/trim/benchmark_test.go @@ -0,0 +1,135 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package trim + +import ( + "bufio" + "strings" + "testing" +) + +// BenchmarkTrimFuncs tests the core trimming logic (Leading, Trailing, Whitespace, Nop) +func BenchmarkTrimFuncs(b *testing.B) { + inputs := []struct { + name string + data []byte + }{ + { + name: "small clean", + data: []byte("simple_log_line"), + }, + { + name: "small dirty", + data: []byte(" \t\r\n simple_log_line \t\r\n "), + }, + { + name: "all space", + data: []byte(" \t\r\n "), + }, + { + name: "large dirty", + data: []byte(strings.Repeat(" \t content \r\n ", 100)), + }, + } + + funcs := []struct { + name string + fn Func + }{ + { + name: "leading", + fn: Leading, + }, + { + name: "trailing", + fn: Trailing, + }, + { + name: "whitespace", + fn: Whitespace, + }, + { + name: "nop", + fn: Nop, + }, + } + + for _, f := range funcs { + for _, input := range inputs { + b.Run(f.name+"/"+input.name, func(b *testing.B) { + b.ReportAllocs() + data := input.data + + for b.Loop() { + _ = f.fn(data) + } + }) + } + } +} + +// BenchmarkWithFunc tests the overhead of wrapping a SplitFunc +func BenchmarkWithFunc(b *testing.B) { + data := []byte("line1\nline2\nline3\n") + + tests := []struct { + name string + fn bufio.SplitFunc + }{ + { + name: "baseline scanlines", + fn: bufio.ScanLines, + }, + { + name: "with whitespace", + fn: WithFunc(bufio.ScanLines, Whitespace), + }, + } + + for _, tc := range tests { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + + split := tc.fn + + for b.Loop() { + _, _, _ = split(data, false) + } + }) + } +} + +// BenchmarkToLength tests the truncation logic +func BenchmarkToLength(b *testing.B) { + tests := []struct { + name string + data []byte + limit int + }{ + { + name: "under limit", + data: []byte("short"), + limit: 10, + }, + { + name: "over limit", + data: []byte(strings.Repeat("long", 100)), + limit: 10, + }, + } + + for _, tc := range tests { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + + // Initialize the split function with the specific limit + split := ToLength(bufio.ScanLines, tc.limit) + data := tc.data + + for b.Loop() { + _, _, _ = split(data, false) + } + }) + } +} diff --git a/pkg/stanza/trim/trim.go b/pkg/stanza/trim/trim.go index 3cf5d4f5c2ce4..8a41381ce58c5 100644 --- a/pkg/stanza/trim/trim.go +++ b/pkg/stanza/trim/trim.go @@ -5,7 +5,6 @@ package trim // import "github.com/open-telemetry/opentelemetry-collector-contri import ( "bufio" - "bytes" ) type Func func([]byte) []byte @@ -45,18 +44,22 @@ func Nop(token []byte) []byte { return token } +func isSpace(c byte) bool { + return c == ' ' || c == '\t' || c == '\r' || c == '\n' +} + func Leading(data []byte) []byte { - token := bytes.TrimLeft(data, "\r\n\t ") - if token == nil { - // TrimLeft sometimes overwrites something with nothing. - // We need to override this behavior in order to preserve empty tokens. - return data + for len(data) > 0 && isSpace(data[0]) { + data = data[1:] } - return token + return data } func Trailing(data []byte) []byte { - return bytes.TrimRight(data, "\r\n\t ") + for len(data) > 0 && isSpace(data[len(data)-1]) { + data = data[:len(data)-1] + } + return data } func Whitespace(data []byte) []byte { diff --git a/pkg/stanza/trim/trim_test.go b/pkg/stanza/trim/trim_test.go index c2a4747b307b1..15fcd4f46f546 100644 --- a/pkg/stanza/trim/trim_test.go +++ b/pkg/stanza/trim/trim_test.go @@ -56,6 +56,13 @@ func TestTrim(t *testing.T) { input: nil, expect: nil, }, + { + name: "trim trailing returns nil when given nil", + preserveLeading: true, + preserveTrailing: false, + input: nil, + expect: nil, + }, { name: "trim leading returns []byte when given []byte", preserveLeading: false, @@ -63,10 +70,32 @@ func TestTrim(t *testing.T) { input: []byte{}, expect: []byte{}, }, + { + name: "all whitespace becomes empty", + preserveLeading: false, + preserveTrailing: false, + input: []byte(" \t\r\n "), + expect: []byte{}, + }, + { + name: "no whitespace remains unchanged", + preserveLeading: false, + preserveTrailing: false, + input: []byte("content"), + expect: []byte("content"), + }, + { + name: "mixed whitespace types", + preserveLeading: false, + preserveTrailing: false, + input: []byte(" \t\r\ncontent \t\r\n"), + expect: []byte("content"), + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + t.Parallel() trimFunc := Config{ PreserveLeading: tc.preserveLeading, PreserveTrailing: tc.preserveTrailing, @@ -76,6 +105,47 @@ func TestTrim(t *testing.T) { } } +func TestIsSpace(t *testing.T) { + testCases := []struct { + name string + b byte + isSpace bool + }{ + { + name: "space", + b: ' ', + isSpace: true, + }, + { + name: "newline", + b: '\n', + isSpace: true, + }, + { + name: "tab", + b: '\t', + isSpace: true, + }, + { + name: "carriage return", + b: '\r', + isSpace: true, + }, + { + name: "not a space", + b: '1', + isSpace: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.isSpace, isSpace(tc.b)) + }) + } +} + func TestWithFunc(t *testing.T) { testCases := []struct { name string From 84c53d0839235a0335a98bab429dbbc64d9235c2 Mon Sep 17 00:00:00 2001 From: Paulo Dias <44772900+paulojmdias@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:01:40 +0000 Subject: [PATCH 48/49] [ci] Ensure github token is available for github cli (#45602) #### Description I enabled the usage of the `gh` CLI in https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/45477, but I forgot to ensure the variable `GH_TOKEN` is configured with the proper value. This will fix that issue, but the workflow should be triggered manually after this merge. #### Link to tracking issue Fixes #45600 Signed-off-by: Paulo Dias --- .github/workflows/update-otel.yaml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-otel.yaml b/.github/workflows/update-otel.yaml index 956a363befd3d..b2f26e89b6ceb 100644 --- a/.github/workflows/update-otel.yaml +++ b/.github/workflows/update-otel.yaml @@ -23,6 +23,11 @@ jobs: with: path: opentelemetry-collector repository: open-telemetry/opentelemetry-collector + - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + id: otelbot-token + with: + app-id: ${{ vars.OTELBOT_APP_ID }} + private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - name: Prepare to update dependencies run: | exec > >(tee log.out) 2>&1 @@ -40,6 +45,8 @@ jobs: echo "LAST_COMMIT=$LAST_COMMIT" echo "BRANCH_NAME=$branch" } >> "$GITHUB_ENV" + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} - name: Gets packages from links with retries uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2 with: @@ -51,11 +58,6 @@ jobs: exec > >(tee -a log.out) 2>&1 cd opentelemetry-collector-contrib make update-otel OTEL_STABLE_VERSION=${{ env.LAST_COMMIT }} OTEL_VERSION=${{ env.LAST_COMMIT }} - - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 - id: otelbot-token - with: - app-id: ${{ vars.OTELBOT_APP_ID }} - private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - name: Push and create PR run: | exec > >(tee -a log.out) 2>&1 From 961984b492c8976768df5ef0e64237dc5249322c Mon Sep 17 00:00:00 2001 From: cjk Date: Fri, 23 Jan 2026 12:00:24 +0000 Subject: [PATCH 49/49] Updating documentation --- .chloggen/add-stored-procedure-columns-to-events.yaml | 2 +- receiver/sqlserverreceiver/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.chloggen/add-stored-procedure-columns-to-events.yaml b/.chloggen/add-stored-procedure-columns-to-events.yaml index b24371d6c0c98..f643b6d439db2 100644 --- a/.chloggen/add-stored-procedure-columns-to-events.yaml +++ b/.chloggen/add-stored-procedure-columns-to-events.yaml @@ -16,7 +16,7 @@ issues: [44656] # These lines will be padded with 2 spaces and then inserted directly into the document. # Use pipe (|) for multiline entries. subtext: Refined query and reported events to include stored procedure information when applicable. - Expanded default samples count from 200 to 250 to account for teh addition of stored procedure events. + Additionally, the maximum number of active queries reported by default has been increased from 200 to 250 to account for record deaggregation introduced by this change, ensuring the effective limit remains consistent with the previous 200-query baseline. # 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. diff --git a/receiver/sqlserverreceiver/README.md b/receiver/sqlserverreceiver/README.md index 791308fc5d1cf..e97c46d4896d2 100644 --- a/receiver/sqlserverreceiver/README.md +++ b/receiver/sqlserverreceiver/README.md @@ -61,7 +61,7 @@ sqlserver: top_query_collection: # this collection exports the most expensive queries as logs lookback_time: 60s # which time window should we look for the top queries max_query_sample_count: 1000 # maximum number query we store in cache for top queries. - top_query_count: 200 # The maximum number of active queries to report in a single run. + top_query_count: 250 # The maximum number of active queries to report in a single run. collection_interval: 60s # collection interval for top query collection specifically query_sample_collection: # this collection exports the currently (relate to the query time) executing queries as logs max_rows_per_query: 100 # the maximum number of samples to return for one single query.