diff options
Diffstat (limited to 'src/mongo/db/storage/wiredtiger/wiredtiger_stats_test.cpp')
| -rw-r--r-- | src/mongo/db/storage/wiredtiger/wiredtiger_stats_test.cpp | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_stats_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_stats_test.cpp new file mode 100644 index 00000000000..942d6ce8592 --- /dev/null +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_stats_test.cpp @@ -0,0 +1,327 @@ +/** + * 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/storage/wiredtiger/wiredtiger_stats.h" +#include "mongo/unittest/temp_dir.h" +#include "mongo/unittest/unittest.h" +#include <memory> + +namespace mongo { +namespace { + +#define ASSERT_WT_OK(result) ASSERT_EQ(result, 0) << wiredtiger_strerror(result) + +class WiredTigerStatsTest : public unittest::Test { +protected: + void setUp() override { + openConnectionAndCreateSession(); + // Prepare data to be read by tests. Reading data written within the same transaction does + // not count towards bytes read into cache. + + // Tests in the fixture do up to _maxReads reads. + for (int64_t i = 0; i < _kMaxReads; ++i) { + // Make the write big enough to span different pages. + writeAtKey(std::string(20000, 'a'), i); + } + + // Closing the connection will ensure that tests actually have to read into cache. + closeConnection(); + openConnectionAndCreateSession(); + } + + void tearDown() override { + closeConnection(); + } + + void openConnectionAndCreateSession() { + ASSERT_WT_OK( + wiredtiger_open(_path.path().c_str(), nullptr, "create,statistics=(fast),", &_conn)); + ASSERT_WT_OK(_conn->open_session(_conn, nullptr, "isolation=snapshot", &_session)); + ASSERT_WT_OK(_session->create( + _session, _uri.c_str(), "type=file,key_format=q,value_format=u,log=(enabled=false)")); + } + + void closeConnection() { + ASSERT_EQ(_conn->close(_conn, nullptr), 0); + } + + /** + * Writes some data using WT. Causes the bytesWritten stat to be incremented, and may also + * increment timeWritingMicros. + */ + void write() { + writeAtKey(std::string(_writeKey + 1, 'a'), _writeKey); + ++_writeKey; + } + + /** + * Writes the specified data using WT. Causes the bytesWritten stat to be incremented, and may + * also increment timeWritingMicros. + */ + void write(const std::string& data) { + writeAtKey(data, _writeKey); + ++_writeKey; + } + + /** + * Writes at the specified key to WT. + */ + void writeAtKey(const std::string& data, int64_t key) { + ASSERT_WT_OK(_session->begin_transaction(_session, nullptr)); + + WT_CURSOR* cursor; + ASSERT_WT_OK(_session->open_cursor(_session, _uri.c_str(), nullptr, nullptr, &cursor)); + + cursor->set_key(cursor, key); + + WT_ITEM item{data.data(), data.size()}; + cursor->set_value(cursor, &item); + + ASSERT_WT_OK(cursor->insert(cursor)); + ASSERT_WT_OK(cursor->close(cursor)); + ASSERT_WT_OK(_session->commit_transaction(_session, nullptr)); + + // Without a checkpoint, an operation is not guaranteed to write to disk. + ASSERT_WT_OK(_session->checkpoint(_session, nullptr)); + } + + /** + * Reads at the specified key from WT. + */ + void readAtKey(int64_t key) { + ASSERT_WT_OK(_session->begin_transaction(_session, nullptr)); + + WT_CURSOR* cursor; + ASSERT_WT_OK(_session->open_cursor(_session, _uri.c_str(), nullptr, nullptr, &cursor)); + + cursor->set_key(cursor, key); + ASSERT_WT_OK(cursor->search(cursor)); + + WT_ITEM value; + ASSERT_WT_OK(cursor->get_value(cursor, &value)); + + ASSERT_WT_OK(cursor->close(cursor)); + ASSERT_WT_OK(_session->commit_transaction(_session, nullptr)); + } + + /** + * Reads fixture data from WT. Causes the bytesRead stat to be incremented. May also cause + * timeReadingMicros to be incremented, but not always. This function can only be called up to + * _kMaxReads times within a test. + */ + void read() { + ASSERT_LT(_readKey, _kMaxReads); + readAtKey(_readKey++); + } + + /** + * Reads data written by the test from WT. Causes the bytesRead stat to be incremented. May + * also cause timeReadingMicros to be incremented, but not always. + */ + void readTestWrites() { + for (int64_t i = _kMaxReads; i < _writeKey; i++) { + readAtKey(i); + } + } + + unittest::TempDir _path{"wiredtiger_operation_stats_test"}; + std::string _uri{"table:wiredtiger_operation_stats_test"}; + WT_CONNECTION* _conn; + WT_SESSION* _session; + /* Number of reads the fixture will prepare in setUp(), consequently max amount of times read() + * can be called in a test. */ + static constexpr int64_t _kMaxReads = 2; + /* Next key to be used by read(), must be initialized at 0. */ + int64_t _readKey = 0; + /* Next key to be used by write(), must be initialized >= _kMaxReads. */ + int64_t _writeKey = _kMaxReads; +}; + +TEST_F(WiredTigerStatsTest, EmptySession) { + // Read and write statistics should be empty. Check "data" field does not exist. "wait" fields + // such as the schemaLock might have some value. + auto statsBson = WiredTigerStats{_session}.toBSON(); + ASSERT_FALSE(statsBson.hasField("data")); +} + +TEST_F(WiredTigerStatsTest, SessionWithWrite) { + write(); + + auto statsObj = WiredTigerStats{_session}.toBSON(); + auto dataSection = statsObj["data"]; + ASSERT_EQ(dataSection.type(), BSONType::Object) << statsObj; + + ASSERT(dataSection["bytesWritten"]) << statsObj; + for (auto&& [name, value] : dataSection.Obj()) { + ASSERT_EQ(value.type(), BSONType::NumberLong) << statsObj; + ASSERT_GT(value.numberLong(), 0) << statsObj; + } +} + +TEST_F(WiredTigerStatsTest, SessionWithRead) { + read(); + + auto statsObj = WiredTigerStats{_session}.toBSON(); + + auto dataSection = statsObj["data"]; + ASSERT_EQ(dataSection.type(), BSONType::Object) << statsObj; + + ASSERT(dataSection["bytesRead"]) << statsObj; + for (auto&& [name, value] : dataSection.Obj()) { + ASSERT_EQ(value.type(), BSONType::NumberLong) << statsObj; + ASSERT_GT(value.numberLong(), 0) << statsObj; + } +} + +TEST_F(WiredTigerStatsTest, SessionWithLargeWriteAndLargeRead) { + auto remaining = static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 1; + while (remaining > 0) { + std::string data(1024 * 1024, 'a'); + remaining -= data.size(); + write(data); + } + + auto statsObj = WiredTigerStats{_session}.toBSON(); + ASSERT_GT(statsObj["data"]["bytesWritten"].numberLong(), std::numeric_limits<uint32_t>::max()) + << statsObj; + + // Closing the connection will ensure that tests actually have to read into cache. + closeConnection(); + openConnectionAndCreateSession(); + + readTestWrites(); + + statsObj = WiredTigerStats{_session}.toBSON(); + ASSERT_GT(statsObj["data"]["bytesRead"].numberLong(), std::numeric_limits<uint32_t>::max()) + << statsObj; +} + +TEST_F(WiredTigerStatsTest, OperationsAddToSessionStats) { + std::vector<std::unique_ptr<WiredTigerStats>> operationStats; + + write(); + WiredTigerStats firstWrite(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(firstWrite - WiredTigerStats{})); + read(); + WiredTigerStats firstRead(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(firstRead - firstWrite)); + write(); + WiredTigerStats secondWrite(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(secondWrite - firstRead)); + read(); + WiredTigerStats secondRead(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(secondRead - secondWrite)); + + const WiredTigerStats& fetchedSessionStats = secondRead; + + long long bytesWritten = 0; + long long timeWritingMicros = 0; + long long bytesRead = 0; + long long timeReadingMicros = 0; + + WiredTigerStats addedSessionStats; + + for (auto&& op : operationStats) { + auto statsObj = op->toBSON(); + + bytesWritten += statsObj["data"]["bytesWritten"].numberLong(); + timeWritingMicros += statsObj["data"]["timeWritingMicros"].numberLong(); + bytesRead += statsObj["data"]["bytesRead"].numberLong(); + timeReadingMicros += statsObj["data"]["timeReadingMicros"].numberLong(); + + addedSessionStats += *op; + } + + auto addedObj = addedSessionStats.toBSON(); + auto dataSection = addedObj["data"]; + ASSERT_EQ(dataSection.type(), BSONType::Object) << addedObj; + ASSERT_EQ(dataSection["bytesWritten"].numberLong(), bytesWritten) << addedObj; + ASSERT_EQ(dataSection["timeWritingMicros"].numberLong(), timeWritingMicros) << addedObj; + ASSERT_EQ(dataSection["bytesRead"].numberLong(), bytesRead) << addedObj; + ASSERT_EQ(dataSection["timeReadingMicros"].numberLong(), timeReadingMicros) << addedObj; + + auto fetchedObj = fetchedSessionStats.toBSON(); + auto fetchedDataSection = fetchedObj["data"]; + ASSERT_EQ(fetchedDataSection.type(), BSONType::Object) << fetchedObj; + ASSERT_EQ(fetchedDataSection["bytesWritten"].numberLong(), bytesWritten) << fetchedObj; + ASSERT_EQ(fetchedDataSection["timeWritingMicros"].numberLong(), timeWritingMicros) + << fetchedObj; + ASSERT_EQ(fetchedDataSection["bytesRead"].numberLong(), bytesRead) << fetchedObj; + ASSERT_EQ(fetchedDataSection["timeReadingMicros"].numberLong(), timeReadingMicros) + << fetchedObj; +} + +TEST_F(WiredTigerStatsTest, OperationsSubtractToZero) { + std::vector<std::unique_ptr<WiredTigerStats>> operationStats; + + write(); + WiredTigerStats firstWrite(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(firstWrite - WiredTigerStats{})); + read(); + WiredTigerStats firstRead(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(firstRead - firstWrite)); + write(); + WiredTigerStats secondWrite(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(secondWrite - firstRead)); + read(); + WiredTigerStats secondRead(_session); + operationStats.push_back(std::make_unique<WiredTigerStats>(secondRead - secondWrite)); + + WiredTigerStats& fetchedSessionStats = secondRead; + + // Assert fetchedSessionStats was not zero before checking subtract results in it being zero. + // We ignore the time statistics as those might still be 0 from time to time. + auto preSubtractObj = fetchedSessionStats.toBSON(); + auto preSubtract = preSubtractObj["data"]; + ASSERT_EQ(preSubtract.type(), BSONType::Object) << preSubtractObj; + ASSERT_GT(preSubtract["bytesWritten"].numberLong(), 0) << preSubtractObj; + ASSERT_GT(preSubtract["bytesRead"].numberLong(), 0) << preSubtractObj; + + for (auto&& op : operationStats) { + fetchedSessionStats -= *op; + } + + auto subtractedObj = fetchedSessionStats.toBSON(); + ASSERT_BSONOBJ_EQ(subtractedObj, BSONObj{}); +} + +TEST_F(WiredTigerStatsTest, Clone) { + write(); + + WiredTigerStats stats{_session}; + auto clone = stats.clone(); + + ASSERT_BSONOBJ_EQ(stats.toBSON(), clone->toBSON()); + + stats += *clone; + ASSERT_BSONOBJ_NE(stats.toBSON(), clone->toBSON()); +} + +} // namespace +} // namespace mongo |
