summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/document_source_query_stats.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline/document_source_query_stats.cpp')
-rw-r--r--src/mongo/db/pipeline/document_source_query_stats.cpp301
1 files changed, 0 insertions, 301 deletions
diff --git a/src/mongo/db/pipeline/document_source_query_stats.cpp b/src/mongo/db/pipeline/document_source_query_stats.cpp
deleted file mode 100644
index e4b805f376d..00000000000
--- a/src/mongo/db/pipeline/document_source_query_stats.cpp
+++ /dev/null
@@ -1,301 +0,0 @@
-/**
- * Copyright (C) 2022-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/pipeline/document_source_query_stats.h"
-
-#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQueryStats
-
-#include "mongo/idl/idl_parser.h"
-#include "mongo/logv2/log.h"
-#include "mongo/util/assert_util.h"
-#include "mongo/util/debug_util.h"
-
-namespace mongo {
-namespace {
-Counter64 queryStatsHmacApplicationErrors;
-ServerStatusMetricField<Counter64> displayWriteErrorsMetric("queryStats.numHmacApplicationErrors",
- &queryStatsHmacApplicationErrors);
-} // namespace
-
-REGISTER_DOCUMENT_SOURCE(queryStats,
- DocumentSourceQueryStats::LiteParsed::parse,
- DocumentSourceQueryStats::createFromBson,
- AllowedWithApiStrict::kNeverInVersion1);
-
-namespace {
-
-/**
- * Parse the spec object calling the `ctor` with the TransformAlgorithm enum algorithm and
- * std::string hmacKey arguments.
- */
-template <typename Ctor>
-auto parseSpec(const BSONElement& spec, const Ctor& ctor) {
- uassert(ErrorCodes::FailedToParse,
- str::stream() << DocumentSourceQueryStats::kStageName
- << " value must be an object. Found: " << typeName(spec.type()),
- spec.type() == BSONType::Object);
- BSONObj obj = spec.embeddedObject();
- TransformAlgorithmEnum algorithm = TransformAlgorithmEnum::kNone;
- std::string hmacKey;
- auto parsed = DocumentSourceQueryStatsSpec::parse(DocumentSourceQueryStats::kStageName, obj);
- boost::optional<TransformIdentifiersSpec> transformIdentifiers =
- parsed.getTransformIdentifiers();
-
- if (transformIdentifiers) {
- algorithm = transformIdentifiers->getAlgorithm();
- boost::optional<ConstDataRange> hmacKeyContainer = transformIdentifiers->getHmacKey();
- uassert(ErrorCodes::FailedToParse,
- str::stream() << "The 'hmacKey' parameter of the $queryStats stage must be "
- "specified when applying the hmac-sha-256 algorithm",
- algorithm != TransformAlgorithmEnum::kHmacSha256 ||
- hmacKeyContainer != boost::none);
- hmacKey = std::string(hmacKeyContainer->data(), (size_t)hmacKeyContainer->length());
- }
- return ctor(algorithm, hmacKey);
-}
-
-} // namespace
-
-BSONObj DocumentSourceQueryStats::computeQueryStatsKey(std::shared_ptr<const Key> key) const {
- static const auto sha256HmacStringDataHasher = [](std::string key, const StringData& sd) {
- auto hashed = SHA256Block::computeHmac(
- (const uint8_t*)key.data(), key.size(), (const uint8_t*)sd.rawData(), sd.size());
- return hashed.toString();
- };
-
- auto opts = SerializationOptions{};
- opts.literalPolicy = LiteralSerializationPolicy::kToDebugTypeString;
- if (_algorithm == TransformAlgorithmEnum::kHmacSha256) {
- opts.transformIdentifiers = true;
- opts.transformIdentifiersCallback = [&](StringData sd) {
- return sha256HmacStringDataHasher(_hmacKey, sd);
- };
- }
- return key->toBson(pExpCtx->opCtx, opts);
-}
-
-std::unique_ptr<DocumentSourceQueryStats::LiteParsed> DocumentSourceQueryStats::LiteParsed::parse(
- const NamespaceString& nss, const BSONElement& spec) {
- return parseSpec(spec, [&](TransformAlgorithmEnum algorithm, std::string hmacKey) {
- return std::make_unique<DocumentSourceQueryStats::LiteParsed>(
- spec.fieldName(), algorithm, hmacKey);
- });
-}
-
-boost::intrusive_ptr<DocumentSource> DocumentSourceQueryStats::createFromBson(
- BSONElement spec, const boost::intrusive_ptr<ExpressionContext>& pExpCtx) {
- const NamespaceString& nss = pExpCtx->ns;
-
- uassert(ErrorCodes::InvalidNamespace,
- "$queryStats must be run against the 'admin' database with {aggregate: 1}",
- nss.db() == NamespaceString::kAdminDb && nss.isCollectionlessAggregateNS());
-
- LOGV2_DEBUG_OPTIONS(7808300,
- 1,
- {logv2::LogTruncation::Disabled},
- "Logging invocation $queryStats",
- "commandSpec"_attr =
- spec.Obj().redact(BSONObj::RedactLevel::sensitiveOnly));
- return parseSpec(spec, [&](TransformAlgorithmEnum algorithm, std::string hmacKey) {
- return new DocumentSourceQueryStats(pExpCtx, algorithm, hmacKey);
- });
-}
-
-Value DocumentSourceQueryStats::serialize(const SerializationOptions& opts) const {
- auto hmacKey = opts.serializeLiteral(
- BSONBinData(_hmacKey.c_str(), _hmacKey.size(), BinDataType::Sensitive));
- if (opts.literalPolicy == LiteralSerializationPolicy::kToRepresentativeParseableValue) {
- // The default shape for a BinData under this policy is empty and has sub-type 0 (general).
- // This doesn't quite work for us since we assert when we parse that it is at least 32 bytes
- // and also is sub-type 8 (sensitive).
- hmacKey =
- Value(BSONBinData("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 32, BinDataType::Sensitive));
- }
- return Value{
- Document{{kStageName,
- _transformIdentifiers
- ? Document{{"transformIdentifiers",
- Document{{"algorithm", TransformAlgorithm_serializer(_algorithm)},
- {"hmacKey", hmacKey}}}}
- : Document{}}}};
-}
-
-DocumentSource::GetNextResult DocumentSourceQueryStats::doGetNext() {
- const auto shouldLog = _algorithm != TransformAlgorithmEnum::kNone;
- /**
- * When a CopiedPartition is present (loaded) and contains more elements (QueryStatsEntry), we
- * can process and return the next element in the _currentCopiedPartition.
- *
- * When the current CopiedPartition is exhausted (emptied), we move on to the next
- * partition. Once we have iterated to the end of the valid partitions, we are done iteratiing
- * over all the queryStatsStore entries.
- *
- * We iterate over a copied container (CopiedParitition) containing the entries in
- * the partition to reduce the time under which the partition lock is held.
- */
- auto& queryStatsStore = getQueryStatsStore(getContext()->opCtx);
-
- while (_currentCopiedPartition.isValidPartitionId(queryStatsStore.numPartitions())) {
- if (!_currentCopiedPartition.isLoaded()) {
- _currentCopiedPartition.load(queryStatsStore);
- }
- // CopiedPartition::load() will throw if any errors occur.
- // Safe to assume _currentCopiedPartition is now loaded.
-
- // Exhaust all elements in the current copied partition.
- // Use a while loop here to handle cases where toDocument() may fail for a specific
- // QueryStatsEntry, in which case we suppress the thrown exception and continue
- // iterating to the next available entry.
- while (!_currentCopiedPartition.empty()) {
- auto& statsEntries = _currentCopiedPartition.statsEntries;
- const auto& queryStatsEntry = statsEntries.front();
- ON_BLOCK_EXIT([&statsEntries]() { statsEntries.pop_front(); });
- if (auto doc =
- toDocument(_currentCopiedPartition.getReadTimestamp(), queryStatsEntry)) {
- if (shouldLog) {
- LOGV2_DEBUG_OPTIONS(7808301,
- 3,
- {logv2::LogTruncation::Disabled},
- "Logging all outputs of $queryStats",
- "thisOutput"_attr = *doc);
- }
- return std::move(*doc);
- }
- }
- // Once we have exhausted entries in this partition, move on to the next partition.
- _currentCopiedPartition.incrementPartitionId();
- }
-
- if (shouldLog) {
- LOGV2_DEBUG_OPTIONS(
- 7808302, 3, {logv2::LogTruncation::Disabled}, "Finished logging output of $queryStats");
- }
- return DocumentSource::GetNextResult::makeEOF();
-}
-
-boost::optional<Document> DocumentSourceQueryStats::toDocument(
- const Date_t& partitionReadTime, const QueryStatsEntry& queryStatsEntry) const {
- const auto& key = queryStatsEntry.key;
- try {
- auto queryStatsKey = computeQueryStatsKey(key);
- // We use the representative shape to generate the key hash. This avoids returning duplicate
- // hashes if we have bugs that cause two different representative shapes to re-parse into
- // the same debug shape.
- auto representativeShapeKey = key->toBson(
- pExpCtx->opCtx, SerializationOptions::kRepresentativeQueryShapeSerializeOptions);
-
- // This SHA256 version of the hash is output to aid in data analytics use cases. In these
- // cases, we often care about comparing hashes from different hosts, potentially on
- // different versions and platforms. The thinking here is that the SHA256 algorithm is more
- // stable across these different environments than the quicker 'absl::HashOf'
- // implementation.
- auto hash = SHA256Block::computeHash((const uint8_t*)representativeShapeKey.objdata(),
- representativeShapeKey.objsize())
- .toString();
- return Document{{"key", std::move(queryStatsKey)},
- {"keyHash", hash},
- {"metrics", queryStatsEntry.toBSON()},
- {"asOf", partitionReadTime}};
- } catch (const DBException& ex) {
- queryStatsHmacApplicationErrors.increment();
- const auto& hash = absl::Hash<query_stats::Key>{}(*key);
- const auto queryShape = key->universalComponents()._queryShape->toBson(
- pExpCtx->opCtx, SerializationOptions::kRepresentativeQueryShapeSerializeOptions);
- LOGV2_DEBUG(7349403,
- 3,
- "Error encountered when applying hmac to query shape, will not publish "
- "queryStats for this entry.",
- "status"_attr = ex.toStatus(),
- "hash"_attr = hash,
- "debugQueryShape"_attr = queryShape);
-
- if (kDebugBuild || internalQueryStatsErrorsAreCommandFatal.load()) {
- auto keyString = std::to_string(hash);
- tasserted(7349401,
- str::stream() << "Was not able to re-parse queryStats key when "
- "reading queryStats.Status "
- << ex.toString() << " Hash: " << keyString
- << " Query Shape: " << queryShape.toString());
- }
- }
- return {};
-}
-
-/**
- * Loads the current CopiedPartition with copies of the QueryStatsEntries located in partition of
- * cache corresponding to the partitionId of the current CopiedPartition. This ensures that the
- * partition mutex is only held for the duration of copying.
- */
-void DocumentSourceQueryStats::CopiedPartition::load(QueryStatsStore& queryStatsStore) {
- tassert(7932100,
- "Attempted to load invalid partition.",
- _partitionId < queryStatsStore.numPartitions());
- tassert(7932101, "Partition was already loaded.", !isLoaded());
- // 'statsEntries' should be empty, clear just in case.
- statsEntries.clear();
-
- // Capture the time at which reading the partition begins.
- _readTimestamp = Date_t::now();
- {
- // We only keep the partition (which holds a lock)
- // for the time needed to collect the metrics (QueryStatsEntry)
- const auto partition = queryStatsStore.getPartition(_partitionId);
-
- // Note the intentional copy of QueryStatsEntry.
- // This will give us a snapshot of all the metrics we want to report.
- for (auto&& [hash, metrics] : *partition) {
- statsEntries.push_back(metrics);
- }
- }
- _isLoaded = true;
-}
-
-bool DocumentSourceQueryStats::CopiedPartition::isLoaded() const {
- return _isLoaded;
-}
-
-void DocumentSourceQueryStats::CopiedPartition::incrementPartitionId() {
- // Ensure loaded state is reset when partitionId is incremented.
- ++_partitionId;
- _isLoaded = false;
-}
-
-bool DocumentSourceQueryStats::CopiedPartition::isValidPartitionId(
- QueryStatsStore::PartitionId maxNumPartitions) const {
- return _partitionId < maxNumPartitions;
-}
-
-const Date_t& DocumentSourceQueryStats::CopiedPartition::getReadTimestamp() const {
- return _readTimestamp;
-}
-
-bool DocumentSourceQueryStats::CopiedPartition::empty() const {
- return statsEntries.empty();
-}
-} // namespace mongo