/**
* 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.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kFTDC
#include "mongo/platform/basic.h"
#include "mongo/db/ftdc/controller.h"
#include "mongo/db/client.h"
#include "mongo/db/ftdc/collector.h"
#include "mongo/db/ftdc/util.h"
#include "mongo/db/jsobj.h"
#include "mongo/stdx/condition_variable.h"
#include "mongo/stdx/memory.h"
#include "mongo/stdx/mutex.h"
#include "mongo/stdx/thread.h"
#include "mongo/util/exit.h"
#include "mongo/util/log.h"
#include "mongo/util/time_support.h"
namespace mongo {
void FTDCController::setEnabled(bool enabled) {
stdx::lock_guard lock(_mutex);
_configTemp.enabled = enabled;
}
void FTDCController::setPeriod(Milliseconds millis) {
stdx::lock_guard lock(_mutex);
_configTemp.period = millis;
_condvar.notify_one();
}
void FTDCController::setMaxDirectorySizeBytes(std::uint64_t size) {
stdx::lock_guard lock(_mutex);
_configTemp.maxDirectorySizeBytes = size;
_condvar.notify_one();
}
void FTDCController::setMaxFileSizeBytes(std::uint64_t size) {
stdx::lock_guard lock(_mutex);
_configTemp.maxFileSizeBytes = size;
_condvar.notify_one();
}
void FTDCController::setMaxSamplesPerArchiveMetricChunk(size_t size) {
stdx::lock_guard lock(_mutex);
_configTemp.maxSamplesPerArchiveMetricChunk = size;
_condvar.notify_one();
}
void FTDCController::setMaxSamplesPerInterimMetricChunk(size_t size) {
stdx::lock_guard lock(_mutex);
_configTemp.maxSamplesPerInterimMetricChunk = size;
_condvar.notify_one();
}
void FTDCController::addPeriodicCollector(std::unique_ptr collector) {
{
stdx::lock_guard lock(_mutex);
invariant(_state == State::kNotStarted);
_periodicCollectors.add(std::move(collector));
}
}
void FTDCController::addOnRotateCollector(std::unique_ptr collector) {
{
stdx::lock_guard lock(_mutex);
invariant(_state == State::kNotStarted);
_rotateCollectors.add(std::move(collector));
}
}
BSONObj FTDCController::getMostRecentPeriodicDocument() {
{
stdx::lock_guard lock(_mutex);
return _mostRecentPeriodicDocument.getOwned();
}
}
void FTDCController::start() {
log() << "Initializing full-time diagnostic data capture with directory '"
<< _path.generic_string() << "'";
// Start the thread
_thread = stdx::thread(stdx::bind(&FTDCController::doLoop, this));
{
stdx::lock_guard lock(_mutex);
invariant(_state == State::kNotStarted);
_state = State::kStarted;
}
}
void FTDCController::stop() {
log() << "Shutting down full-time diagnostic data capture";
{
stdx::lock_guard lock(_mutex);
bool started = (_state == State::kStarted);
invariant(_state == State::kNotStarted || _state == State::kStarted);
if (!started) {
_state = State::kDone;
return;
}
_configTemp.enabled = false;
_state = State::kStopRequested;
// Wake up the thread if sleeping so that it will check if we are done
_condvar.notify_one();
}
_thread.join();
_state = State::kDone;
if (_mgr) {
auto s = _mgr->close();
if (!s.isOK()) {
log() << "Failed to close full-time diagnostic data capture file manager: " << s;
}
}
}
void FTDCController::doLoop() {
try {
// Update config
{
stdx::lock_guard lock(_mutex);
_config = _configTemp;
}
Client::initThread("ftdc");
Client* client = &cc();
while (true) {
// Compute the next interval to run regardless of how we were woken up
// Skipping an interval due to a race condition with a config signal is harmless.
auto now = getGlobalServiceContext()->getPreciseClockSource()->now();
// Get next time to run at
auto next_time = FTDCUtil::roundTime(now, _config.period);
// Wait for the next run or signal to shutdown
{
stdx::unique_lock lock(_mutex);
// We ignore spurious wakeups by just doing an iteration of the loop
auto status = _condvar.wait_until(lock, next_time.toSystemTimePoint());
// Are we done running?
if (_state == State::kStopRequested) {
break;
}
// Update the current configuration settings always
// In unit tests, we may never get a signal when the timeout is 1ms on Windows since
// MSVC 2013 converts wait_until(now() + 1ms) into ~ wait_for(0) which means it will
// not wait for the condition variable to be signaled because it uses
// GetFileSystemTime for now which has ~10 ms granularity.
_config = _configTemp;
// if we hit a timeout on the condvar, we need to do another collection
// if we were signalled, then we have a config update only or were asked to stop
if (status == stdx::cv_status::no_timeout) {
continue;
}
}
// TODO: consider only running this thread if we are enabled
// for now, we just keep an idle thread as it is simplier
if (_config.enabled) {
// Delay initialization of FTDCFileManager until we are sure the user has enabled
// FTDC
if (!_mgr) {
auto swMgr =
FTDCFileManager::create(&_config, _path, &_rotateCollectors, client);
_mgr = uassertStatusOK(std::move(swMgr));
}
auto collectSample = _periodicCollectors.collect(client);
Status s = _mgr->writeSampleAndRotateIfNeeded(
client, std::get<0>(collectSample), std::get<1>(collectSample));
uassertStatusOK(s);
// Store a reference to the most recent document from the periodic collectors
{
stdx::lock_guard lock(_mutex);
_mostRecentPeriodicDocument = std::get<0>(collectSample);
}
}
}
} catch (...) {
warning() << "Uncaught exception in '" << exceptionToStatus()
<< "' in full-time diagnostic data capture subsystem. Shutting down the "
"full-time diagnostic data capture subsystem.";
}
}
} // namespace mongo