summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/auth/lib/commands_lib.js20
-rw-r--r--jstests/core/views/views_all_commands.js1
-rw-r--r--jstests/noPassthrough/global_profiling_filter.js281
-rw-r--r--jstests/replsets/all_commands_downgrading_to_upgraded.js3
-rw-r--r--jstests/replsets/db_reads_while_recovering_all_commands.js1
-rw-r--r--jstests/replsets/tenant_migration_concurrent_writes_on_donor_util.js1
-rw-r--r--jstests/sharding/database_versioning_all_commands.js1
-rw-r--r--jstests/sharding/libs/last_lts_mongos_commands.js1
-rw-r--r--jstests/sharding/libs/mongos_api_params_util.js4
-rw-r--r--jstests/sharding/read_write_concern_defaults_application.js1
-rw-r--r--jstests/sharding/safe_secondary_reads_drop_recreate.js1
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js1
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js1
-rw-r--r--src/mongo/db/catalog/collection_catalog.cpp6
-rw-r--r--src/mongo/db/catalog/collection_catalog.h7
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/dbcommands_d.cpp3
-rw-r--r--src/mongo/db/commands/profile.idl13
-rw-r--r--src/mongo/db/commands/set_profiling_filter_globally_cmd.cpp104
-rw-r--r--src/mongo/db/commands/set_profiling_filter_globally_cmd.h70
-rw-r--r--src/mongo/db/curop.cpp2
-rw-r--r--src/mongo/db/curop.h2
-rw-r--r--src/mongo/db/profile_filter.cpp3
-rw-r--r--src/mongo/db/profile_filter.h8
-rw-r--r--src/mongo/db/query/query_knobs.idl7
-rw-r--r--src/mongo/s/commands/cluster_profile_cmd.cpp3
26 files changed, 542 insertions, 4 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 3ffeac6d8ba..c6c6f868db2 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -5971,6 +5971,26 @@ export const authCommandsLib = {
]
},
{
+ testname: "setProfilingFilterGlobally",
+ command: {setProfilingFilterGlobally: 1, filter: {nreturned: 0}},
+ testcases: [
+ {
+ runOnDb: firstDbName,
+ roles: roles_dbAdminAny,
+ privileges: [{resource: {db: "", collection: ""}, actions: ["enableProfiler"]}],
+ expectFail:
+ true /* the command will fail because the query knob is not turned on */
+ },
+ {
+ runOnDb: firstDbName,
+ privileges: [
+ {resource: {db: firstDbName, collection: ""}, actions: ["enableProfiler"]}
+ ],
+ expectAuthzFailure: true
+ }
+ ]
+ },
+ {
testname: "setParameter",
command: {setParameter: 1, quiet: 1},
testcases: [
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js
index 2c6e8cd6f89..906886c51ba 100644
--- a/jstests/core/views/views_all_commands.js
+++ b/jstests/core/views/views_all_commands.js
@@ -640,6 +640,7 @@ let viewsCommandTests = {
setDefaultRWConcern: {skip: isUnrelated},
setFeatureCompatibilityVersion: {skip: isUnrelated},
setFreeMonitoring: {skip: isUnrelated},
+ setProfilingFilterGlobally: {skip: isUnrelated},
setParameter: {skip: isUnrelated},
setShardVersion: {skip: isUnrelated},
setChangeStreamState: {skip: isUnrelated},
diff --git a/jstests/noPassthrough/global_profiling_filter.js b/jstests/noPassthrough/global_profiling_filter.js
new file mode 100644
index 00000000000..728e553125f
--- /dev/null
+++ b/jstests/noPassthrough/global_profiling_filter.js
@@ -0,0 +1,281 @@
+/*
+ * Test the usage and behavior of the 'setProfilingFilterGlobally' command.
+ *
+ * @tags: [requires_sharding, requires_replication]
+ */
+(function() {
+
+load("jstests/libs/log.js"); // For findMatchingLogLine.
+load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers.
+load("jstests/noPassthrough/libs/server_parameter_helpers.js"); // For setParameter.
+
+// Updates the global profiling filter to 'newFilter' and validates that 'oldFilter' is returned in
+// the response and the change is logged correctly. If `newFilter' is null, unsets the filter.
+function updateProfilingFilterGlobally(db, {oldFilter, newFilter}) {
+ const result = assert.commandWorked(
+ db.runCommand({setProfilingFilterGlobally: 1, filter: newFilter ? newFilter : "unset"}));
+ assert.eq(result.was, oldFilter ? oldFilter : "none");
+
+ const log = assert.commandWorked(db.adminCommand({getLog: "global"})).log;
+ assert(!!findMatchingLogLine(log, {
+ msg: "Profiler settings changed globally",
+ from: {filter: oldFilter ? oldFilter : "none"},
+ to: {filter: newFilter ? newFilter : "none"}
+ }),
+ "expected log line was not found");
+}
+
+(function testQueryKnobMustBeEnabledToUseCommand() {
+ // First make sure we can't run the command with the query knob turned off.
+ const conn = MongoRunner.runMongod({});
+ assert.commandFailedWithCode(
+ conn.adminCommand({setProfilingFilterGlobally: 1, filter: "unset"}), 7283301);
+ MongoRunner.stopMongod(conn);
+})();
+
+(function testCommandOverridesDefaultFromConfigFile() {
+ // Test that setting the global filter overrides the startup configuration.
+ const conn = MongoRunner.runMongod({
+ config: "jstests/libs/config_files/set_profiling_filter.json",
+ setParameter: {internalQueryGlobalProfilingFilter: 1}
+ });
+ updateProfilingFilterGlobally(conn.getDB("test"), {
+ oldFilter: {$expr: {$lt: [{$rand: {}}, {$const: 0.01}]}},
+ newFilter: {$expr: {$lt: [{$rand: {}}, {$const: 0.5}]}}
+ });
+ updateProfilingFilterGlobally(
+ conn.getDB("test"),
+ {oldFilter: {$expr: {$lt: [{$rand: {}}, {$const: 0.5}]}}, newFilter: null});
+ MongoRunner.stopMongod(conn);
+})();
+
+// Run a set of correctness tests for the setProfilingFilterGlobally command on the given
+// connection.
+function runCorrectnessTests(conn) {
+ // mongoS supports slow-query log lines but not profiling. So if we are talking to mongoS, we
+ // will avoid enabling profiling, or making assertions about profiling.
+ const db = conn.getDB("test");
+ const isMongos = FixtureHelpers.isMongos(db);
+
+ const dbs = [conn.getDB("db1"), conn.getDB("db2"), conn.getDB("db3")];
+
+ function initDatabase(db) {
+ db.c.drop();
+ db.c.insert([{x: 25}, {x: 50}]);
+
+ // Fully enable profiling to start.
+ db.setProfilingLevel(isMongos ? 0 : 1, {slowms: -1});
+ }
+
+ // Initialize each database with some basic data and set the profiling level.
+ for (const db of dbs) {
+ initDatabase(db);
+ }
+
+ // Test queries to run.
+ const queries = {
+ query1: {filter: {}, nreturned: 2},
+ query2: {filter: {x: 25}, nreturned: 1},
+ query3: {filter: {x: 1000}, nreturned: 0},
+ };
+
+ // Profile filters matching specific test queries.
+ const profileFilter1 = {filter: {nreturned: {$eq: 2}}, matchesQuery: "query1"};
+ const profileFilter2 = {filter: {nreturned: {$eq: 1}}, matchesQuery: "query2"};
+
+ // Verify that profile filters are applied as expected for both logging and profiling, if
+ // applicable. 'params' must specify three fields:
+ // 'desc': short test description
+ // 'globalFilter': expected global profiling filter setting
+ // 'dbFilters': expected database-specific filter settings
+ function verify(params) {
+ function verifyDatabase(db) {
+ // Create a unique comment for the query.
+ function queryComment(queryName) {
+ return `${params.desc}: ${db.getName()} -> ${queryName}`;
+ }
+
+ // Check the log for the query's profile entry, and system.profile if this is being run
+ // on mongod.
+ function assertQueryProfiled(name, shouldProfile) {
+ const log = assert.commandWorked(db.adminCommand({getLog: "global"})).log;
+ assert.eq(
+ !!findMatchingLogLine(log, {msg: "Slow query", comment: queryComment(name)}),
+ shouldProfile,
+ `expected query${shouldProfile ? "" : " not"} to be logged: ${
+ queryComment(name)}`);
+
+ if (!isMongos) {
+ assert.eq(!!db.system.profile.findOne({'command.comment': queryComment(name)}),
+ shouldProfile,
+ `expected query${shouldProfile ? "" : " not"} to be profiled: ${
+ queryComment(name)}`);
+ }
+ }
+
+ for (const [queryName, query] of Object.entries(queries)) {
+ // First, run the query on the database.
+ const comment = queryComment(queryName);
+ const results = db.c.find(query.filter).comment(comment).itcount();
+ assert.eq(results, query.nreturned, comment);
+
+ // Validate whether or not the query was profiled based on the database's
+ // profile filter. We should profile *unless* there is a filter applicable to
+ // this database that does not match the query.
+ let shouldProfile = true;
+ if (params.dbFilters.hasOwnProperty(db.getName())) {
+ // This database has a database specific filter, does it match the query?
+ shouldProfile = params.dbFilters[db.getName()].matchesQuery === queryName;
+ } else if (params.globalFilter) {
+ // No database filter, but there is a global filter, does it match the
+ // query?
+ shouldProfile = params.globalFilter.matchesQuery == queryName;
+ }
+ assertQueryProfiled(queryName, shouldProfile);
+ }
+ }
+
+ for (const db of dbs) {
+ verifyDatabase(db);
+ }
+
+ // Now create a new database and make sure the global settings are applied correctly.
+ const newDB = conn.getDB("newDB");
+ initDatabase(newDB);
+ verifyDatabase(newDB);
+ newDB.dropDatabase();
+ }
+
+ // Error cases (invalid filters or parameters).
+ assert.commandFailedWithCode(db.runCommand({setProfilingFilterGlobally: 1, filter: null}),
+ ErrorCodes.BadValue);
+ assert.commandFailedWithCode(
+ db.runCommand({setProfilingFilterGlobally: 1, filter: {noSuchField: 1}}), 4910200);
+ assert.commandFailedWithCode(
+ db.runCommand({setProfilingFilterGlobally: 1, filter: {}, writeConcern: {w: "majority"}}),
+ ErrorCodes.InvalidOptions);
+ assert.commandFailedWithCode(
+ db.runCommand(
+ {setProfilingFilterGlobally: 1, filter: {}, readConcern: {level: "majority"}}),
+ ErrorCodes.InvalidOptions);
+
+ // Make sure that behavior is as expected in the default/startup state.
+ (function testDefaultSettingIsNone() {
+ verify({desc: "default setting is none", globalFilter: null, dbFilters: {}});
+ })();
+
+ (function testEmptyFilter() {
+ updateProfilingFilterGlobally(db, {oldFilter: null, newFilter: {}});
+ // Empty filter is always true - effectively none.
+ verify({desc: "empty filter", globalFilter: null, dbFilters: {}});
+ })();
+
+ (function testGlobalFilterSettingAffectsAllDatabases() {
+ updateProfilingFilterGlobally(db, {oldFilter: {}, newFilter: profileFilter1.filter});
+ verify({
+ desc: "setting the global filter affects all databases",
+ globalFilter: profileFilter1,
+ dbFilters: {}
+ });
+ })();
+
+ (function testGlobalFilterUnsetClearsGlobalSetting() {
+ updateProfilingFilterGlobally(db, {oldFilter: profileFilter1.filter, newFilter: null});
+ verify({
+ desc: "unsetting the global filter clears the global setting",
+ globalFilter: null,
+ dbFilters: {}
+ });
+ })();
+
+ (function testGlobalFilterUnsetWhenAlreadyUnsetIsNoop() {
+ updateProfilingFilterGlobally(db, {oldFilter: null, newFilter: null});
+ verify({
+ desc: "unsetting the global filter when already unset is a noop",
+ globalFilter: null,
+ dbFilters: {}
+ });
+ })();
+
+ (function testGlobalFilterSettingOverridesDatabaseSpecificSettings() {
+ let result = assert.commandWorked(db.getSiblingDB("db1").runCommand(
+ {profile: isMongos ? 0 : 1, filter: profileFilter1.filter}));
+ assert(!result.filter);
+ result = assert.commandWorked(db.getSiblingDB("db2").runCommand(
+ {profile: isMongos ? 0 : 1, filter: profileFilter2.filter}));
+ assert(!result.filter);
+ verify({
+ desc: "setting the global filter overrides database specific settings (pre-validate)",
+ globalFilter: null,
+ dbFilters: {db1: profileFilter1, db2: profileFilter2}
+ });
+
+ updateProfilingFilterGlobally(db, {oldFilter: null, newFilter: profileFilter2.filter});
+ verify({
+ desc: "setting the global filter overrides database specific settings",
+ globalFilter: profileFilter2,
+ dbFilters: {}
+ });
+ })();
+
+ (function testGlobalFilterSettingOverridesDatabaseSpecificSettingsEvenWhenNoop() {
+ let result = assert.commandWorked(db.getSiblingDB("db1").runCommand(
+ {profile: isMongos ? 0 : 1, filter: profileFilter1.filter}));
+ assert.eq(result.filter, profileFilter2.filter);
+ verify({
+ desc:
+ "setting the global filter overrides database specific settings even when a noop (pre-validate)",
+ globalFilter: profileFilter2,
+ dbFilters: {db1: profileFilter1}
+ });
+
+ updateProfilingFilterGlobally(
+ db, {oldFilter: profileFilter2.filter, newFilter: profileFilter2.filter});
+ verify({
+ desc: "setting the global filter overrides database specific settings even when a noop",
+ globalFilter: profileFilter2,
+ dbFilters: {}
+ });
+ })();
+
+ (function testGlobalFilterUnsetOverridesDatabaseSpecificSettings() {
+ result = assert.commandWorked(db.getSiblingDB("db1").runCommand(
+ {profile: isMongos ? 0 : 1, filter: profileFilter1.filter}));
+ assert.eq(result.filter, profileFilter2.filter);
+ result = assert.commandWorked(db.getSiblingDB("db3").runCommand(
+ {profile: isMongos ? 0 : 1, filter: profileFilter2.filter}));
+ assert.eq(result.filter, profileFilter2.filter);
+ verify({
+ desc: "unsetting the global filter overrides database specific settings (pre-validate)",
+ globalFilter: profileFilter2,
+ dbFilters: {db1: profileFilter1, db3: profileFilter2}
+ });
+
+ updateProfilingFilterGlobally(db, {oldFilter: profileFilter2.filter, newFilter: null});
+ verify({
+ desc: "unsetting the global filter overrides database specific settings",
+ globalFilter: null,
+ dbFilters: {}
+ });
+ })();
+}
+
+{
+ // Run tests on mongod.
+ const conn = MongoRunner.runMongod({setParameter: {internalQueryGlobalProfilingFilter: 1}});
+ runCorrectnessTests(conn);
+ MongoRunner.stopMongod(conn);
+}
+
+{
+ // Run tests on mongos.
+ const st = ShardingTest({
+ shards: 1,
+ rs: {nodes: 1},
+ config: 1,
+ mongosOptions: {setParameter: {internalQueryGlobalProfilingFilter: 1}}
+ });
+ runCorrectnessTests(st);
+ st.stop();
+}
+})();
diff --git a/jstests/replsets/all_commands_downgrading_to_upgraded.js b/jstests/replsets/all_commands_downgrading_to_upgraded.js
index eba66979eea..322f9198f42 100644
--- a/jstests/replsets/all_commands_downgrading_to_upgraded.js
+++ b/jstests/replsets/all_commands_downgrading_to_upgraded.js
@@ -22,6 +22,8 @@ const fullNs = dbName + "." + collName;
// Pre-written reasons for skipping a test.
const isAnInternalCommand = "internal command";
const isDeprecated = "deprecated command";
+// TODO SERVER-69753 some commands we didn't have time for. Other commands are new in recent
+// releases and don't make sense to test here.
const isNotImplementedYet = "not implemented yet";
let _lsid = UUID();
@@ -1112,6 +1114,7 @@ const allCommands = {
setIndexCommitQuorum: {skip: isNotImplementedYet},
setFeatureCompatibilityVersion: {skip: isNotImplementedYet},
setFreeMonitoring: {skip: isNotImplementedYet},
+ setProfilingFilterGlobally: {skip: isNotImplementedYet},
setParameter: {skip: isNotImplementedYet},
setShardVersion: {skip: isNotImplementedYet},
setChangeStreamState: {skip: isNotImplementedYet},
diff --git a/jstests/replsets/db_reads_while_recovering_all_commands.js b/jstests/replsets/db_reads_while_recovering_all_commands.js
index 756579c593c..8e04bdfd7c0 100644
--- a/jstests/replsets/db_reads_while_recovering_all_commands.js
+++ b/jstests/replsets/db_reads_while_recovering_all_commands.js
@@ -376,6 +376,7 @@ const allCommands = {
setIndexCommitQuorum: {skip: isPrimaryOnly},
setFeatureCompatibilityVersion: {skip: isPrimaryOnly},
setFreeMonitoring: {skip: isPrimaryOnly},
+ setProfilingFilterGlobally: {skip: isNotAUserDataRead},
setParameter: {skip: isNotAUserDataRead},
setShardVersion: {skip: isNotAUserDataRead},
setChangeStreamState: {skip: isNotAUserDataRead},
diff --git a/jstests/replsets/tenant_migration_concurrent_writes_on_donor_util.js b/jstests/replsets/tenant_migration_concurrent_writes_on_donor_util.js
index 74a90d36aca..2d70311da2b 100644
--- a/jstests/replsets/tenant_migration_concurrent_writes_on_donor_util.js
+++ b/jstests/replsets/tenant_migration_concurrent_writes_on_donor_util.js
@@ -653,6 +653,7 @@ export const TenantMigrationConcurrentWriteUtil = {
setDefaultRWConcern: {skip: isNotRunOnUserDatabase},
setFeatureCompatibilityVersion: {skip: isNotRunOnUserDatabase},
setFreeMonitoring: {skip: isNotRunOnUserDatabase},
+ setProfilingFilterGlobally: {skip: isNotRunOnUserDatabase},
setIndexCommitQuorum: {skip: isNotRunOnUserDatabase},
setParameter: {skip: isNotRunOnUserDatabase},
setShardVersion: {skip: isNotRunOnUserDatabase},
diff --git a/jstests/sharding/database_versioning_all_commands.js b/jstests/sharding/database_versioning_all_commands.js
index 475408a4a4e..af745ca4b31 100644
--- a/jstests/sharding/database_versioning_all_commands.js
+++ b/jstests/sharding/database_versioning_all_commands.js
@@ -676,6 +676,7 @@ let testCases = {
setFeatureCompatibilityVersion: {skip: "not on a user database"},
setFreeMonitoring:
{skip: "explicitly fails for mongos, primary mongod only", conditional: true},
+ setProfilingFilterGlobally: {skip: "executes locally on mongos (not sent to any remote node)"},
setParameter: {skip: "executes locally on mongos (not sent to any remote node)"},
setClusterParameter: {skip: "always targets the config server"},
setUserWriteBlockMode: {skip: "executes locally on mongos (not sent to any remote node)"},
diff --git a/jstests/sharding/libs/last_lts_mongos_commands.js b/jstests/sharding/libs/last_lts_mongos_commands.js
index 56e3870df6c..fb00895b0c2 100644
--- a/jstests/sharding/libs/last_lts_mongos_commands.js
+++ b/jstests/sharding/libs/last_lts_mongos_commands.js
@@ -37,6 +37,7 @@ const commandsAddedToMongosSinceLastLTS = [
"rotateCertificates",
"setAllowMigrations",
"setClusterParameter",
+ "setProfilingFilterGlobally", // TODO SERVER-73305
"setUserWriteBlockMode",
"testDeprecation",
"testDeprecationInVersion2",
diff --git a/jstests/sharding/libs/mongos_api_params_util.js b/jstests/sharding/libs/mongos_api_params_util.js
index 24b249a8fab..6b6b7ef3d3a 100644
--- a/jstests/sharding/libs/mongos_api_params_util.js
+++ b/jstests/sharding/libs/mongos_api_params_util.js
@@ -1207,6 +1207,10 @@ let MongosAPIParametersUtil = (function() {
conditional: true
},
{
+ commandName: "setProfilingFilterGlobally",
+ skip: "executes locally on mongos (not sent to any remote node)",
+ },
+ {
commandName: "setParameter",
skip: "executes locally on mongos (not sent to any remote node)"
},
diff --git a/jstests/sharding/read_write_concern_defaults_application.js b/jstests/sharding/read_write_concern_defaults_application.js
index 992bfa42253..81de7608132 100644
--- a/jstests/sharding/read_write_concern_defaults_application.js
+++ b/jstests/sharding/read_write_concern_defaults_application.js
@@ -700,6 +700,7 @@ let testCases = {
setDefaultRWConcern: {skip: "special case (must run after all other commands)"},
setFeatureCompatibilityVersion: {skip: "does not accept read or write concern"},
setFreeMonitoring: {skip: "does not accept read or write concern"},
+ setProfilingFilterGlobally: {skip: "does not accept read or write concern"},
setIndexCommitQuorum: {skip: "does not accept read or write concern"},
setParameter: {skip: "does not accept read or write concern"},
setShardVersion: {skip: "internal command"},
diff --git a/jstests/sharding/safe_secondary_reads_drop_recreate.js b/jstests/sharding/safe_secondary_reads_drop_recreate.js
index 50cbfbf9c26..ad9777bdbd4 100644
--- a/jstests/sharding/safe_secondary_reads_drop_recreate.js
+++ b/jstests/sharding/safe_secondary_reads_drop_recreate.js
@@ -327,6 +327,7 @@ let testCases = {
setIndexCommitQuorum: {skip: "primary only"},
setFeatureCompatibilityVersion: {skip: "primary only"},
setFreeMonitoring: {skip: "primary only"},
+ setProfilingFilterGlobally: {skip: "does not return user data"},
setParameter: {skip: "does not return user data"},
setShardVersion: {skip: "does not return user data"},
setChangeStreamState: {skip: "does not return user data"},
diff --git a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
index 3017f7c76a3..13434a694e9 100644
--- a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
+++ b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
@@ -398,6 +398,7 @@ let testCases = {
setIndexCommitQuorum: {skip: "primary only"},
setFeatureCompatibilityVersion: {skip: "primary only"},
setFreeMonitoring: {skip: "primary only"},
+ setProfilingFilterGlobally: {skip: "does not return user data"},
setParameter: {skip: "does not return user data"},
setShardVersion: {skip: "does not return user data"},
setChangeStreamState: {skip: "does not return user data"},
diff --git a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
index 790774169cb..ef57d920121 100644
--- a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
+++ b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
@@ -334,6 +334,7 @@ let testCases = {
setIndexCommitQuorum: {skip: "primary only"},
setFeatureCompatibilityVersion: {skip: "primary only"},
setFreeMonitoring: {skip: "primary only"},
+ setProfilingFilterGlobally: {skip: "does not return user data"},
setParameter: {skip: "does not return user data"},
setShardVersion: {skip: "does not return user data"},
setChangeStreamState: {skip: "does not return user data"},
diff --git a/src/mongo/db/catalog/collection_catalog.cpp b/src/mongo/db/catalog/collection_catalog.cpp
index 90340ae3812..18098e99dcc 100644
--- a/src/mongo/db/catalog/collection_catalog.cpp
+++ b/src/mongo/db/catalog/collection_catalog.cpp
@@ -1773,6 +1773,12 @@ std::set<TenantId> CollectionCatalog::getAllTenants() const {
return ret;
}
+void CollectionCatalog::setAllDatabaseProfileFilters(std::shared_ptr<ProfileFilter> filter) {
+ for (auto& [_, settings] : _databaseProfileSettings) {
+ settings.filter = filter;
+ }
+}
+
void CollectionCatalog::setDatabaseProfileSettings(
const DatabaseName& dbName, CollectionCatalog::ProfileSettings newProfileSettings) {
_databaseProfileSettings[dbName] = newProfileSettings;
diff --git a/src/mongo/db/catalog/collection_catalog.h b/src/mongo/db/catalog/collection_catalog.h
index 28e397c4617..880d4882157 100644
--- a/src/mongo/db/catalog/collection_catalog.h
+++ b/src/mongo/db/catalog/collection_catalog.h
@@ -95,7 +95,7 @@ public:
struct ProfileSettings {
int level;
- std::shared_ptr<ProfileFilter> filter; // nullable
+ std::shared_ptr<const ProfileFilter> filter; // nullable
ProfileSettings(int level, std::shared_ptr<ProfileFilter> filter)
: level(level), filter(filter) {
@@ -546,6 +546,11 @@ public:
std::set<TenantId> getAllTenants() const;
/**
+ * Updates the profile filter on all databases with non-default settings.
+ */
+ void setAllDatabaseProfileFilters(std::shared_ptr<ProfileFilter> filter);
+
+ /**
* Sets 'newProfileSettings' as the profiling settings for the database 'dbName'.
*/
void setDatabaseProfileSettings(const DatabaseName& dbName, ProfileSettings newProfileSettings);
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index c36a17cfb36..75d1f27815e 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -696,6 +696,7 @@ env.Library(
'profile_common.cpp',
'profile.idl',
'$BUILD_DIR/mongo/db/profile_filter_impl.cpp',
+ 'set_profiling_filter_globally_cmd.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/commands',
diff --git a/src/mongo/db/commands/dbcommands_d.cpp b/src/mongo/db/commands/dbcommands_d.cpp
index c731d1643d0..55564e6c171 100644
--- a/src/mongo/db/commands/dbcommands_d.cpp
+++ b/src/mongo/db/commands/dbcommands_d.cpp
@@ -56,6 +56,7 @@
#include "mongo/db/commands/profile_common.h"
#include "mongo/db/commands/profile_gen.h"
#include "mongo/db/commands/server_status.h"
+#include "mongo/db/commands/set_profiling_filter_globally_cmd.h"
#include "mongo/db/concurrency/exception_util.h"
#include "mongo/db/curop_failpoint_helpers.h"
#include "mongo/db/db_raii.h"
@@ -201,6 +202,8 @@ protected:
} cmdProfile;
+SetProfilingFilterGloballyCmd cmdSetProfilingFilterGlobally;
+
class CmdFileMD5 : public BasicCommand {
public:
CmdFileMD5() : BasicCommand("filemd5") {}
diff --git a/src/mongo/db/commands/profile.idl b/src/mongo/db/commands/profile.idl
index c9930bba124..497e95624fe 100644
--- a/src/mongo/db/commands/profile.idl
+++ b/src/mongo/db/commands/profile.idl
@@ -66,3 +66,16 @@ commands:
an alternative to slowms and sampleRate. The special value 'unset' removes
the filter."
optional: true
+
+ setProfilingFilterGlobally:
+ description: "Parser for the 'setProfilingFilterGlobally' command."
+ command_name: "setProfilingFilterGlobally"
+ cpp_name: SetProfilingFilterGloballyCmdRequest
+ strict: true
+ namespace: ignored
+ api_version: ""
+ fields:
+ filter:
+ type: ObjectOrUnset
+ description: "A query predicate that determines which ops are logged/profiled on a global
+ level. The special value 'unset' removes the filter."
diff --git a/src/mongo/db/commands/set_profiling_filter_globally_cmd.cpp b/src/mongo/db/commands/set_profiling_filter_globally_cmd.cpp
new file mode 100644
index 00000000000..e64844387f9
--- /dev/null
+++ b/src/mongo/db/commands/set_profiling_filter_globally_cmd.cpp
@@ -0,0 +1,104 @@
+/**
+ * Copyright (C) 2023-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/commands/set_profiling_filter_globally_cmd.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/catalog/collection_catalog.h"
+#include "mongo/db/commands/profile_gen.h"
+#include "mongo/db/profile_filter_impl.h"
+#include "mongo/logv2/log.h"
+
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand
+
+
+namespace mongo {
+
+Status SetProfilingFilterGloballyCmd::checkAuthForOperation(OperationContext* opCtx,
+ const DatabaseName& dbName,
+ const BSONObj& cmdObj) const {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+ return authSession->isAuthorizedForActionsOnResource(ResourcePattern::forAnyNormalResource(),
+ ActionType::enableProfiler)
+ ? Status::OK()
+ : Status(ErrorCodes::Unauthorized, "unauthorized");
+}
+
+bool SetProfilingFilterGloballyCmd::run(OperationContext* opCtx,
+ const DatabaseName& dbName,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) {
+ uassert(7283301,
+ str::stream() << getName() << " command requires query knob to be enabled",
+ internalQueryGlobalProfilingFilter.load());
+
+ auto request = SetProfilingFilterGloballyCmdRequest::parse(IDLParserContext(getName()), cmdObj);
+
+ // Save off the old global default setting so that we can log it and return in the result.
+ auto oldDefault = ProfileFilter::getDefault();
+ auto newDefault = [&request] {
+ const auto& filterOrUnset = request.getFilter();
+ if (auto filter = filterOrUnset.obj) {
+ return std::make_shared<ProfileFilterImpl>(*filter);
+ }
+ return std::shared_ptr<ProfileFilterImpl>(nullptr);
+ }();
+
+ // Update the global default.
+ // Note that since this is not done atomically with the collection catalog write, there is a
+ // minor race condition where queries on some databases see the new global default while queries
+ // on other databases see old database-specific settings. This is a temporary state and
+ // shouldn't impact much in practice. We also don't have to worry about races with database
+ // creation, since the global default gets picked up dynamically by queries instead of being
+ // explicitly stored for new databases.
+ ProfileFilter::setDefault(newDefault);
+
+ // Update all existing database settings.
+ CollectionCatalog::write(opCtx, [&](CollectionCatalog& catalog) {
+ catalog.setAllDatabaseProfileFilters(newDefault);
+ });
+
+ // Capture the old setting in the result object.
+ if (oldDefault) {
+ result.append("was", oldDefault->serialize());
+ } else {
+ result.append("was", "none");
+ }
+
+ // Log the change made to server's global profiling settings.
+ LOGV2(72832,
+ "Profiler settings changed globally",
+ "from"_attr = oldDefault ? BSON("filter" << oldDefault->serialize())
+ : BSON("filter"
+ << "none"),
+ "to"_attr = newDefault ? BSON("filter" << newDefault->serialize())
+ : BSON("filter"
+ << "none"));
+ return true;
+}
+} // namespace mongo
diff --git a/src/mongo/db/commands/set_profiling_filter_globally_cmd.h b/src/mongo/db/commands/set_profiling_filter_globally_cmd.h
new file mode 100644
index 00000000000..dce2d74a468
--- /dev/null
+++ b/src/mongo/db/commands/set_profiling_filter_globally_cmd.h
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2023-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/base/status.h"
+#include "mongo/db/catalog/collection_catalog.h"
+#include "mongo/db/commands.h"
+
+namespace mongo {
+
+class SetProfilingFilterGloballyCmdRequest;
+
+/**
+ * Command class implementing functionality for both the mongoD and mongoS
+ * 'setProfilingFilterGlobally' command.
+ */
+class SetProfilingFilterGloballyCmd : public BasicCommand {
+public:
+ SetProfilingFilterGloballyCmd() : BasicCommand("setProfilingFilterGlobally") {}
+
+ AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
+ return AllowedOnSecondary::kAlways;
+ }
+
+ std::string help() const final {
+ return "updates a global filter that determines which operations are eligible for "
+ "logging/profiling";
+ }
+
+ bool supportsWriteConcern(const BSONObj& cmd) const final {
+ return false;
+ }
+
+ Status checkAuthForOperation(OperationContext*,
+ const DatabaseName&,
+ const BSONObj&) const final;
+
+ bool run(OperationContext* opCtx,
+ const DatabaseName& dbName,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) final;
+};
+} // namespace mongo
diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp
index 0e73e03a578..a34985919d0 100644
--- a/src/mongo/db/curop.cpp
+++ b/src/mongo/db/curop.cpp
@@ -425,7 +425,7 @@ static constexpr size_t appendMaxElementSize = 50 * 1024;
bool CurOp::completeAndLogOperation(OperationContext* opCtx,
logv2::LogComponent component,
- std::shared_ptr<ProfileFilter> filter,
+ std::shared_ptr<const ProfileFilter> filter,
boost::optional<size_t> responseLength,
boost::optional<long long> slowMsOverride,
bool forceLog) {
diff --git a/src/mongo/db/curop.h b/src/mongo/db/curop.h
index 915f00bb1d7..9205c16bf46 100644
--- a/src/mongo/db/curop.h
+++ b/src/mongo/db/curop.h
@@ -426,7 +426,7 @@ public:
*/
bool completeAndLogOperation(OperationContext* opCtx,
logv2::LogComponent logComponent,
- std::shared_ptr<ProfileFilter> filter,
+ std::shared_ptr<const ProfileFilter> filter,
boost::optional<size_t> responseLength = boost::none,
boost::optional<long long> slowMsOverride = boost::none,
bool forceLog = false);
diff --git a/src/mongo/db/profile_filter.cpp b/src/mongo/db/profile_filter.cpp
index 30ae469e704..d26086caa20 100644
--- a/src/mongo/db/profile_filter.cpp
+++ b/src/mongo/db/profile_filter.cpp
@@ -32,12 +32,15 @@
namespace mongo {
static std::shared_ptr<ProfileFilter> defaultProfileFilter;
+static Mutex mutex = MONGO_MAKE_LATCH("ProfileFilter::mutex");
std::shared_ptr<ProfileFilter> ProfileFilter::getDefault() {
+ stdx::lock_guard<Latch> lk(mutex);
return defaultProfileFilter;
}
void ProfileFilter::setDefault(std::shared_ptr<ProfileFilter> filter) {
+ stdx::lock_guard<Latch> lk(mutex);
defaultProfileFilter = std::move(filter);
}
diff --git a/src/mongo/db/profile_filter.h b/src/mongo/db/profile_filter.h
index 1702f24a734..209637cf4d6 100644
--- a/src/mongo/db/profile_filter.h
+++ b/src/mongo/db/profile_filter.h
@@ -53,9 +53,15 @@ public:
virtual BSONObj serialize() const = 0;
virtual ~ProfileFilter() = default;
+ /**
+ * Thread-safe getter for the global 'ProfileFilter' default.
+ */
static std::shared_ptr<ProfileFilter> getDefault();
- // Not thread-safe: should only be called during initialization.
+ /**
+ * Thread-safe setter for the global 'ProfileFilter' default. Initially this is set from the
+ * configuration file on startup.
+ */
static void setDefault(std::shared_ptr<ProfileFilter>);
};
diff --git a/src/mongo/db/query/query_knobs.idl b/src/mongo/db/query/query_knobs.idl
index 1ebd2bacaae..64d8b782359 100644
--- a/src/mongo/db/query/query_knobs.idl
+++ b/src/mongo/db/query/query_knobs.idl
@@ -1076,6 +1076,13 @@ server_parameters:
gte: 0
on_update: plan_cache_util::clearSbeCacheOnParameterChange
+ internalQueryGlobalProfilingFilter:
+ description: "Enables the setProfilingFilterGlobally command."
+ set_at: [ startup ]
+ cpp_varname: internalQueryGlobalProfilingFilter
+ cpp_vartype: AtomicWord<bool>
+ default: false
+
# Note for adding additional query knobs:
#
diff --git a/src/mongo/s/commands/cluster_profile_cmd.cpp b/src/mongo/s/commands/cluster_profile_cmd.cpp
index f15e496f22e..8a58b497451 100644
--- a/src/mongo/s/commands/cluster_profile_cmd.cpp
+++ b/src/mongo/s/commands/cluster_profile_cmd.cpp
@@ -33,6 +33,7 @@
#include "mongo/db/commands.h"
#include "mongo/db/commands/profile_common.h"
#include "mongo/db/commands/profile_gen.h"
+#include "mongo/db/commands/set_profiling_filter_globally_cmd.h"
#include "mongo/db/profile_filter_impl.h"
namespace mongo {
@@ -85,5 +86,7 @@ protected:
} profileCmd;
+SetProfilingFilterGloballyCmd setProfilingFilterGloballyCmd;
+
} // namespace
} // namespace mongo