diff options
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 |