Skip to content

Commit

Permalink
Fix large number of active session history rows (#31296)
Browse files Browse the repository at this point in the history
  • Loading branch information
nenadnoveljic authored Nov 22, 2024
1 parent f7dde14 commit bd961d3
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 122 deletions.
133 changes: 14 additions & 119 deletions pkg/collector/corechecks/oracle/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,121 +20,6 @@ import (
"github.com/DataDog/datadog-agent/pkg/util/log"
)

// ActivitySnapshot is a payload containing database activity samples. It is parsed from the intake payload.
// easyjson:json
type ActivitySnapshot struct {
Metadata
// Tags should be part of the common Metadata struct but because Activity payloads use a string array
// and samples use a comma-delimited list of tags in a single string, both flavors have to be handled differently
Tags []string `json:"ddtags,omitempty"`
CollectionInterval float64 `json:"collection_interval,omitempty"`
OracleActivityRows []OracleActivityRow `json:"oracle_activity,omitempty"`
}

//nolint:revive // TODO(DBM) Fix revive linter
type RowMetadata struct {
Commands []string `json:"dd_commands,omitempty"`
Tables []string `json:"dd_tables,omitempty"`
Comments []string `json:"dd_comments,omitempty"`
QueryTruncated string `json:"query_truncated,omitempty"`
}

// Metadata contains the metadata fields common to all events processed
type Metadata struct {
Timestamp float64 `json:"timestamp,omitempty"`
Host string `json:"host,omitempty"`
Source string `json:"ddsource,omitempty"`
DBMType string `json:"dbm_type,omitempty"`
DDAgentVersion string `json:"ddagentversion,omitempty"`
}

//nolint:revive // TODO(DBM) Fix revive linter
type OracleSQLRow struct {
SQLID string `json:"sql_id,omitempty"`
ForceMatchingSignature uint64 `json:"force_matching_signature,omitempty"`
SQLPlanHashValue uint64 `json:"sql_plan_hash_value,omitempty"`
SQLExecStart string `json:"sql_exec_start,omitempty"`
}

//nolint:revive // TODO(DBM) Fix revive linter
type OracleActivityRow struct {
Now string `json:"now"`
SessionID uint64 `json:"sid,omitempty"`
SessionSerial uint64 `json:"serial,omitempty"`
User string `json:"user,omitempty"`
Status string `json:"status"`
OsUser string `json:"os_user,omitempty"`
Process string `json:"process,omitempty"`
Client string `json:"client,omitempty"`
Port string `json:"port,omitempty"`
Program string `json:"program,omitempty"`
Type string `json:"type,omitempty"`
OracleSQLRow
Module string `json:"module,omitempty"`
Action string `json:"action,omitempty"`
ClientInfo string `json:"client_info,omitempty"`
LogonTime string `json:"logon_time,omitempty"`
ClientIdentifier string `json:"client_identifier,omitempty"`
BlockingInstance uint64 `json:"blocking_instance,omitempty"`
BlockingSession uint64 `json:"blocking_session,omitempty"`
FinalBlockingInstance uint64 `json:"final_blocking_instance,omitempty"`
FinalBlockingSession uint64 `json:"final_blocking_session,omitempty"`
WaitEvent string `json:"wait_event,omitempty"`
WaitEventClass string `json:"wait_event_class,omitempty"`
WaitTimeMicro uint64 `json:"wait_time_micro,omitempty"`
Statement string `json:"statement,omitempty"`
PdbName string `json:"pdb_name,omitempty"`
CdbName string `json:"cdb_name,omitempty"`
QuerySignature string `json:"query_signature,omitempty"`
CommandName string `json:"command_name,omitempty"`
PreviousSQL bool `json:"previous_sql,omitempty"`
OpFlags uint64 `json:"op_flags,omitempty"`
RowMetadata
}

//nolint:revive // TODO(DBM) Fix revive linter
type OracleActivityRowDB struct {
Now string `db:"NOW"`
UtcMs float64 `db:"UTC_MS"`
SessionID uint64 `db:"SID"`
SessionSerial uint64 `db:"SERIAL#"`
User sql.NullString `db:"USERNAME"`
Status string `db:"STATUS"`
OsUser sql.NullString `db:"OSUSER"`
Process sql.NullString `db:"PROCESS"`
Client sql.NullString `db:"MACHINE"`
Port sql.NullInt64 `db:"PORT"`
Program sql.NullString `db:"PROGRAM"`
Type sql.NullString `db:"TYPE"`
SQLID sql.NullString `db:"SQL_ID"`
ForceMatchingSignature *string `db:"FORCE_MATCHING_SIGNATURE"`
SQLPlanHashValue *uint64 `db:"SQL_PLAN_HASH_VALUE"`
SQLExecStart sql.NullString `db:"SQL_EXEC_START"`
SQLAddress sql.NullString `db:"SQL_ADDRESS"`
PrevSQLID sql.NullString `db:"PREV_SQL_ID"`
PrevForceMatchingSignature *string `db:"PREV_FORCE_MATCHING_SIGNATURE"`
PrevSQLPlanHashValue *uint64 `db:"PREV_SQL_PLAN_HASH_VALUE"`
PrevSQLExecStart sql.NullString `db:"PREV_SQL_EXEC_START"`
PrevSQLAddress sql.NullString `db:"PREV_SQL_ADDRESS"`
Module sql.NullString `db:"MODULE"`
Action sql.NullString `db:"ACTION"`
ClientInfo sql.NullString `db:"CLIENT_INFO"`
LogonTime sql.NullString `db:"LOGON_TIME"`
ClientIdentifier sql.NullString `db:"CLIENT_IDENTIFIER"`
OpFlags uint64 `db:"OP_FLAGS"`
BlockingInstance *uint64 `db:"BLOCKING_INSTANCE"`
BlockingSession *uint64 `db:"BLOCKING_SESSION"`
FinalBlockingInstance *uint64 `db:"FINAL_BLOCKING_INSTANCE"`
FinalBlockingSession *uint64 `db:"FINAL_BLOCKING_SESSION"`
WaitEvent sql.NullString `db:"EVENT"`
WaitEventClass sql.NullString `db:"WAIT_CLASS"`
WaitTimeMicro *uint64 `db:"WAIT_TIME_MICRO"`
Statement sql.NullString `db:"SQL_FULLTEXT"`
PrevSQLFullText sql.NullString `db:"PREV_SQL_FULLTEXT"`
PdbName sql.NullString `db:"PDB_NAME"`
CommandName sql.NullString `db:"COMMAND_NAME"`
}

// Converts sql types to Go native types
func (c *Check) getSQLRow(SQLID sql.NullString, forceMatchingSignature *string, SQLPlanHashValue *uint64, SQLExecStart sql.NullString) (OracleSQLRow, error) {
SQLRow := OracleSQLRow{}
Expand Down Expand Up @@ -216,9 +101,15 @@ func sendPayload(c *Check, sessionRows []OracleActivityRow, timestamp float64) e
//nolint:revive // TODO(DBM) Fix revive linter
func (c *Check) SampleSession() error {
activeSessionHistory := c.config.QuerySamples.ActiveSessionHistory
if activeSessionHistory && c.lastSampleId == 0 {
err := getWrapper(c, &c.lastSampleId, "SELECT /* DD */ MAX(sample_id) FROM v$active_session_history")
return err
if activeSessionHistory && c.lastSampleID == 0 {
err := getWrapper(c, &c.lastSampleID, "SELECT /* DD */ NVL(MAX(sample_id),0) FROM v$active_session_history")
if err != nil {
return err
}
if c.lastSampleID == 0 {
log.Infof("%s no active session history samples found", c.logPrompt)
return nil
}
}
start := time.Now()
copy(c.lastOracleActivityRows, []OracleActivityRow{})
Expand Down Expand Up @@ -250,7 +141,7 @@ AND status = 'ACTIVE'`)

var err error
if activeSessionHistory {
err = selectWrapper(c, &sessionSamples, activityQuery, maxSQLTextLength, c.lastSampleId)
err = selectWrapper(c, &sessionSamples, activityQuery, maxSQLTextLength, c.lastSampleID)
} else {
err = selectWrapper(c, &sessionSamples, activityQuery, maxSQLTextLength, maxSQLTextLength)
}
Expand All @@ -273,6 +164,10 @@ AND status = 'ACTIVE'`)
for _, sample := range sessionSamples {
var sessionRow OracleActivityRow

if activeSessionHistory && sample.SampleID > c.lastSampleID {
c.lastSampleID = sample.SampleID
}

sessionRow.Now = sample.Now
if lastNow != sessionRow.Now && lastNow != "" {
err = sendPayload(c, sessionRows, sample.UtcMs)
Expand Down
44 changes: 44 additions & 0 deletions pkg/collector/corechecks/oracle/activity_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,47 @@ func TestIdleWaits(t *testing.T) {
assert.NotEqual(t, r.WaitEventClass, "Idle", "Idle wait class not sampled")
}
}

func TestActiveSessionHistory(t *testing.T) {
c, _ := newDefaultCheck(t, "", "")
defer c.Teardown()

var count uint64
getWrapper(&c, &count, "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE '%Enterprise%Edition%'")
if count == 0 {
t.Skip("Active Session History is only available in Oracle Enterprise Edition")
}
c.dbmEnabled = true
c.config.QuerySamples.ActiveSessionHistory = true

err := c.Run()
assert.NoError(t, err, "check run")

err = c.SampleSession()
require.NoError(t, err, "activity sample failed")
prevSamplId := c.lastSampleID

conn, err := getSysConnection(t)
require.NoError(t, err)

tx, err := conn.Begin()
require.NoError(t, err)
_, err = tx.Exec("INSERT INTO t VALUES (1)")
require.NoError(t, err)

longRunningStatement := `DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
l NUMBER;
BEGIN
execute immediate 'ALTER SESSION SET DDL_LOCK_TIMEOUT = 3';
execute immediate 'alter table t add n2 number';
ROLLBACK;
END;`
_, err = tx.Exec(longRunningStatement)
require.Error(t, err)

err = c.SampleSession()
require.NoError(t, err, "activity sample failed")

assert.Greater(t, c.lastSampleID, prevSamplId, "sample id should have increased")
}
1 change: 1 addition & 0 deletions pkg/collector/corechecks/oracle/activity_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ AND s.command = comm.command_type(+)`
const activityQueryActiveSessionHistory = `SELECT /*+ push_pred(sq) */ /* DD_ACTIVITY_SAMPLING */
cast (sample_time as date) now,
(CAST(sample_time_utc AS DATE) - TO_DATE('1970-01-01', 'YYYY-MM-DD')) * 86400000 as utc_ms,
s.sample_id sample_id,
s.session_id sid,
s.session_serial# serial#,
sess.username,
Expand Down
126 changes: 126 additions & 0 deletions pkg/collector/corechecks/oracle/activity_structs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build oracle

package oracle

import "database/sql"

// ActivitySnapshot is a payload containing database activity samples. It is parsed from the intake payload.
// easyjson:json
type ActivitySnapshot struct {
Metadata
// Tags should be part of the common Metadata struct but because Activity payloads use a string array
// and samples use a comma-delimited list of tags in a single string, both flavors have to be handled differently
Tags []string `json:"ddtags,omitempty"`
CollectionInterval float64 `json:"collection_interval,omitempty"`
OracleActivityRows []OracleActivityRow `json:"oracle_activity,omitempty"`
}

//nolint:revive // TODO(DBM) Fix revive linter
type RowMetadata struct {
Commands []string `json:"dd_commands,omitempty"`
Tables []string `json:"dd_tables,omitempty"`
Comments []string `json:"dd_comments,omitempty"`
QueryTruncated string `json:"query_truncated,omitempty"`
}

// Metadata contains the metadata fields common to all events processed
type Metadata struct {
Timestamp float64 `json:"timestamp,omitempty"`
Host string `json:"host,omitempty"`
Source string `json:"ddsource,omitempty"`
DBMType string `json:"dbm_type,omitempty"`
DDAgentVersion string `json:"ddagentversion,omitempty"`
}

//nolint:revive // TODO(DBM) Fix revive linter
type OracleSQLRow struct {
SQLID string `json:"sql_id,omitempty"`
ForceMatchingSignature uint64 `json:"force_matching_signature,omitempty"`
SQLPlanHashValue uint64 `json:"sql_plan_hash_value,omitempty"`
SQLExecStart string `json:"sql_exec_start,omitempty"`
}

//nolint:revive // TODO(DBM) Fix revive linter
type OracleActivityRow struct {
Now string `json:"now"`
SessionID uint64 `json:"sid,omitempty"`
SessionSerial uint64 `json:"serial,omitempty"`
User string `json:"user,omitempty"`
Status string `json:"status"`
OsUser string `json:"os_user,omitempty"`
Process string `json:"process,omitempty"`
Client string `json:"client,omitempty"`
Port string `json:"port,omitempty"`
Program string `json:"program,omitempty"`
Type string `json:"type,omitempty"`
OracleSQLRow
Module string `json:"module,omitempty"`
Action string `json:"action,omitempty"`
ClientInfo string `json:"client_info,omitempty"`
LogonTime string `json:"logon_time,omitempty"`
ClientIdentifier string `json:"client_identifier,omitempty"`
BlockingInstance uint64 `json:"blocking_instance,omitempty"`
BlockingSession uint64 `json:"blocking_session,omitempty"`
FinalBlockingInstance uint64 `json:"final_blocking_instance,omitempty"`
FinalBlockingSession uint64 `json:"final_blocking_session,omitempty"`
WaitEvent string `json:"wait_event,omitempty"`
WaitEventClass string `json:"wait_event_class,omitempty"`
WaitTimeMicro uint64 `json:"wait_time_micro,omitempty"`
Statement string `json:"statement,omitempty"`
PdbName string `json:"pdb_name,omitempty"`
CdbName string `json:"cdb_name,omitempty"`
QuerySignature string `json:"query_signature,omitempty"`
CommandName string `json:"command_name,omitempty"`
PreviousSQL bool `json:"previous_sql,omitempty"`
OpFlags uint64 `json:"op_flags,omitempty"`
RowMetadata
}

//nolint:revive // TODO(DBM) Fix revive linter
type OracleActivityRowDB struct {
SampleID uint64 `db:"SAMPLE_ID"`
Now string `db:"NOW"`
UtcMs float64 `db:"UTC_MS"`
SessionID uint64 `db:"SID"`
SessionSerial uint64 `db:"SERIAL#"`
User sql.NullString `db:"USERNAME"`
Status string `db:"STATUS"`
OsUser sql.NullString `db:"OSUSER"`
Process sql.NullString `db:"PROCESS"`
Client sql.NullString `db:"MACHINE"`
Port sql.NullInt64 `db:"PORT"`
Program sql.NullString `db:"PROGRAM"`
Type sql.NullString `db:"TYPE"`
SQLID sql.NullString `db:"SQL_ID"`
ForceMatchingSignature *string `db:"FORCE_MATCHING_SIGNATURE"`
SQLPlanHashValue *uint64 `db:"SQL_PLAN_HASH_VALUE"`
SQLExecStart sql.NullString `db:"SQL_EXEC_START"`
SQLAddress sql.NullString `db:"SQL_ADDRESS"`
PrevSQLID sql.NullString `db:"PREV_SQL_ID"`
PrevForceMatchingSignature *string `db:"PREV_FORCE_MATCHING_SIGNATURE"`
PrevSQLPlanHashValue *uint64 `db:"PREV_SQL_PLAN_HASH_VALUE"`
PrevSQLExecStart sql.NullString `db:"PREV_SQL_EXEC_START"`
PrevSQLAddress sql.NullString `db:"PREV_SQL_ADDRESS"`
Module sql.NullString `db:"MODULE"`
Action sql.NullString `db:"ACTION"`
ClientInfo sql.NullString `db:"CLIENT_INFO"`
LogonTime sql.NullString `db:"LOGON_TIME"`
ClientIdentifier sql.NullString `db:"CLIENT_IDENTIFIER"`
OpFlags uint64 `db:"OP_FLAGS"`
BlockingInstance *uint64 `db:"BLOCKING_INSTANCE"`
BlockingSession *uint64 `db:"BLOCKING_SESSION"`
FinalBlockingInstance *uint64 `db:"FINAL_BLOCKING_INSTANCE"`
FinalBlockingSession *uint64 `db:"FINAL_BLOCKING_SESSION"`
WaitEvent sql.NullString `db:"EVENT"`
WaitEventClass sql.NullString `db:"WAIT_CLASS"`
WaitTimeMicro *uint64 `db:"WAIT_TIME_MICRO"`
Statement sql.NullString `db:"SQL_FULLTEXT"`
PrevSQLFullText sql.NullString `db:"PREV_SQL_FULLTEXT"`
PdbName sql.NullString `db:"PDB_NAME"`
CommandName sql.NullString `db:"COMMAND_NAME"`
}
2 changes: 1 addition & 1 deletion pkg/collector/corechecks/oracle/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ type Check struct {
openMode string
legacyIntegrationCompatibilityMode bool
clock clock.Clock
lastSampleId uint64
lastSampleID uint64
}

type vDatabase struct {
Expand Down
3 changes: 1 addition & 2 deletions pkg/collector/corechecks/oracle/sql/user/grants.sql
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ declare
'v_$dataguard_stats',
'v_$transaction',
'v_$locked_object',
'v_$active_session_history',
'dba_objects',
'cdb_data_files',
'dba_data_files'
Expand All @@ -56,9 +57,7 @@ begin
command := 'grant select on ' || array(i) || ' to &&user with grant option';
end if;
begin
dbms_output.disable;
execute immediate command;
dbms_output.enable;
exception
when others then
null;
Expand Down
Loading

0 comments on commit bd961d3

Please sign in to comment.