/**
* Copyright (C) 2015 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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 GNU Affero General 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/platform/basic.h"
#include
#include
#include "mongo/base/data_type_validated.h"
#include "mongo/base/init.h"
#include "mongo/bson/bson_validate.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/client.h"
#include "mongo/db/ftdc/collector.h"
#include "mongo/db/ftdc/config.h"
#include "mongo/db/ftdc/constants.h"
#include "mongo/db/ftdc/controller.h"
#include "mongo/db/ftdc/ftdc_test.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/service_context.h"
#include "mongo/stdx/memory.h"
#include "mongo/unittest/temp_dir.h"
#include "mongo/unittest/unittest.h"
namespace mongo {
class FTDCMetricsCollectorMockTee : public FTDCCollectorInterface {
public:
~FTDCMetricsCollectorMockTee() {
ASSERT_TRUE(_state == State::kStarted);
}
void collect(OperationContext* txn, BSONObjBuilder& builder) final {
_state = State::kStarted;
++_counter;
// Generate document to return for collector
generateDocument(builder, _counter);
// Generate an entire document as if the FTDCCollector generates it
{
BSONObjBuilder b2;
b2.appendDate(kFTDCCollectStartField,
getGlobalServiceContext()->getPreciseClockSource()->now());
{
BSONObjBuilder subObjBuilder(b2.subobjStart(name()));
subObjBuilder.appendDate(kFTDCCollectStartField,
getGlobalServiceContext()->getPreciseClockSource()->now());
generateDocument(subObjBuilder, _counter);
subObjBuilder.appendDate(kFTDCCollectEndField,
getGlobalServiceContext()->getPreciseClockSource()->now());
}
b2.appendDate(kFTDCCollectEndField,
getGlobalServiceContext()->getPreciseClockSource()->now());
_docs.emplace_back(b2.obj());
}
if (_counter == _wait) {
_condvar.notify_all();
}
}
std::string name() const final {
return "mock";
}
virtual void generateDocument(BSONObjBuilder& builder, std::uint32_t counter) = 0;
void setSignalOnCount(int c) {
_wait = c;
}
void wait() {
stdx::unique_lock lck(_mutex);
while (_counter < _wait) {
_condvar.wait(lck);
}
}
std::vector& getDocs() {
return _docs;
}
private:
/**
* Private enum to ensure caller uses class correctly.
*/
enum class State {
kNotStarted,
kStarted,
};
// state
State _state{State::kNotStarted};
std::uint32_t _counter{0};
std::vector _docs;
stdx::mutex _mutex;
stdx::condition_variable _condvar;
std::uint32_t _wait{0};
};
class FTDCMetricsCollectorMock2 : public FTDCMetricsCollectorMockTee {
public:
void generateDocument(BSONObjBuilder& builder, std::uint32_t counter) final {
builder.append("name", "joe");
builder.append("key1", (counter * 37));
builder.append("key2", static_cast(counter * static_cast(log10f(counter))));
}
};
class FTDCMetricsCollectorMockRotate : public FTDCMetricsCollectorMockTee {
public:
void generateDocument(BSONObjBuilder& builder, std::uint32_t counter) final {
builder.append("name", "joe");
builder.append("hostinfo", 37);
builder.append("buildinfo", 53);
}
};
// Test a run of the controller and the data it logs to log file
TEST(FTDCControllerTest, TestFull) {
unittest::TempDir tempdir("metrics_testpath");
boost::filesystem::path dir(tempdir.path());
createDirectoryClean(dir);
FTDCConfig config;
config.enabled = true;
config.period = Milliseconds(1);
config.maxFileSizeBytes = FTDCConfig::kMaxFileSizeBytesDefault;
config.maxDirectorySizeBytes = FTDCConfig::kMaxDirectorySizeBytesDefault;
FTDCController c(dir, config);
auto c1 = stdx::make_unique();
auto c2 = stdx::make_unique();
auto c1Ptr = c1.get();
auto c2Ptr = c2.get();
c1Ptr->setSignalOnCount(100);
c.addPeriodicCollector(std::move(c1));
c.addOnRotateCollector(std::move(c2));
c.start();
// Wait for 100 samples to have occured
c1Ptr->wait();
c.stop();
auto docsPeriodic = c1Ptr->getDocs();
ASSERT_GREATER_THAN_OR_EQUALS(docsPeriodic.size(), 100UL);
auto docsRotate = c2Ptr->getDocs();
ASSERT_EQUALS(docsRotate.size(), 1UL);
std::vector allDocs(docsRotate.begin(), docsRotate.end());
allDocs.insert(allDocs.end(), docsPeriodic.begin(), docsPeriodic.end());
auto files = scanDirectory(dir);
ASSERT_EQUALS(files.size(), 1UL);
auto alog = files[0];
ValidateDocumentList(alog, allDocs);
}
// Test we can start and stop the controller in quick succession, make sure it succeeds without
// assert or fault
TEST(FTDCControllerTest, TestStartStop) {
unittest::TempDir tempdir("metrics_testpath");
boost::filesystem::path dir(tempdir.path());
createDirectoryClean(dir);
FTDCConfig config;
config.enabled = false;
config.period = Milliseconds(1);
config.maxFileSizeBytes = FTDCConfig::kMaxFileSizeBytesDefault;
config.maxDirectorySizeBytes = FTDCConfig::kMaxDirectorySizeBytesDefault;
FTDCController c(dir, config);
c.start();
c.stop();
}
// Test we can start the controller as disabled, the directory is empty, and then we can succesfully
// enable it
TEST(FTDCControllerTest, TestStartAsDisabled) {
unittest::TempDir tempdir("metrics_testpath");
boost::filesystem::path dir(tempdir.path());
createDirectoryClean(dir);
FTDCConfig config;
config.enabled = false;
config.period = Milliseconds(1);
config.maxFileSizeBytes = FTDCConfig::kMaxFileSizeBytesDefault;
config.maxDirectorySizeBytes = FTDCConfig::kMaxDirectorySizeBytesDefault;
auto c1 = stdx::make_unique();
auto c1Ptr = c1.get();
FTDCController c(dir, config);
c.addPeriodicCollector(std::move(c1));
c.start();
auto files0 = scanDirectory(dir);
ASSERT_EQUALS(files0.size(), 0UL);
c.setEnabled(true);
c1Ptr->setSignalOnCount(50);
// Wait for 50 samples to have occured
c1Ptr->wait();
c.stop();
auto docsPeriodic = c1Ptr->getDocs();
ASSERT_GREATER_THAN_OR_EQUALS(docsPeriodic.size(), 50UL);
std::vector allDocs(docsPeriodic.begin(), docsPeriodic.end());
auto files = scanDirectory(dir);
ASSERT_EQUALS(files.size(), 1UL);
auto alog = files[0];
ValidateDocumentList(alog, allDocs);
}
} // namespace mongo