pxmlw6n2f/Gazebo_Distributed_TCP/gazebo/util/LogRecord.cc

1161 lines
31 KiB
C++

/*
* Copyright (C) 2012 Open Source Robotics Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifdef _WIN32
// Ensure that Winsock2.h is included before Windows.h, which can get
// pulled in by anybody (e.g., Boost).
#include <Winsock2.h>
#include <io.h>
// Seems like W_OK does not exists on Windows.
// Reading access function documentation, the value should be 2
// http://msdn.microsoft.com/en-us/library/1w06ktdy.aspx
// Test for write permission.
#define W_OK 2
#define access _access
#endif
#include <functional>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/insert_linebreaks.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>
#include <boost/date_time.hpp>
#include <boost/filesystem.hpp>
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/copy.hpp>
#include <iomanip>
#include <ignition/math/Rand.hh>
#include "gazebo/common/CommonIface.hh"
#include "gazebo/common/Assert.hh"
#include "gazebo/common/Base64.hh"
#include "gazebo/common/Console.hh"
#include "gazebo/common/Events.hh"
#include "gazebo/common/Exception.hh"
#include "gazebo/common/Time.hh"
#include "gazebo/common/SystemPaths.hh"
#include "gazebo/gazebo_config.h"
#include "gazebo/transport/transport.hh"
#include "gazebo/util/LogRecordPrivate.hh"
#include "gazebo/util/LogRecord.hh"
using namespace gazebo;
using namespace util;
//////////////////////////////////////////////////
LogRecord::LogRecord()
: dataPtr(new LogRecordPrivate)
{
this->dataPtr->pauseState = false;
this->dataPtr->running = false;
this->dataPtr->paused = false;
this->dataPtr->initialized = false;
this->dataPtr->stopThread = false;
this->dataPtr->firstUpdate = true;
this->dataPtr->readyToStart = false;
// Get the user's home directory
#ifndef _WIN32
const char *homePath = common::getEnv("HOME");
#else
const char *homePath = common::getEnv("HOMEPATH");
#endif
GZ_ASSERT(homePath, "HOME environment variable is missing");
if (!homePath)
{
common::SystemPaths *paths = common::SystemPaths::Instance();
this->dataPtr->logBasePath = paths->GetTmpPath() + "/gazebo";
}
else
{
this->dataPtr->logBasePath = boost::filesystem::path(homePath);
}
this->dataPtr->logBasePath /= "/.gazebo/log/";
this->dataPtr->logsEnd = this->dataPtr->logs.end();
this->dataPtr->connections.push_back(
event::Events::ConnectPause(
std::bind(&LogRecord::OnPause, this, std::placeholders::_1)));
}
//////////////////////////////////////////////////
void LogRecord::OnPause(const bool _pause)
{
this->dataPtr->pauseState = _pause;
}
//////////////////////////////////////////////////
LogRecord::~LogRecord()
{
// Stop the write thread.
this->Fini();
}
//////////////////////////////////////////////////
bool LogRecord::Init(const std::string &_subdir)
{
if (_subdir.empty())
{
gzerr << "LogRecord initialization directory is empty." << std::endl;
return false;
}
this->dataPtr->logSubDir = _subdir;
this->ClearLogs();
this->dataPtr->initialized = true;
this->dataPtr->running = false;
this->dataPtr->paused = false;
this->dataPtr->stopThread = false;
this->dataPtr->firstUpdate = true;
this->dataPtr->readyToStart = true;
return true;
}
//////////////////////////////////////////////////
bool LogRecord::Start(const LogRecordParams &_params)
{
this->dataPtr->period = _params.period;
this->dataPtr->filter = _params.filter;
return this->Start(_params.encoding, _params.path);
}
//////////////////////////////////////////////////
bool LogRecord::Start(const std::string &_encoding, const std::string &_path)
{
std::unique_lock<std::mutex> lock(this->dataPtr->controlMutex);
// Make sure ::Init has been called.
if (!this->dataPtr->initialized)
{
gzerr << "LogRecord has not been initialized." << std::endl;
return false;
}
// Check to see if the logger is already started.
if (this->dataPtr->running)
{
/// \TODO replace this with gzlog
gzerr << "LogRecord has already been started" << std::endl;
return false;
}
// Get the current time as an ISO string.
std::string logTimeDir = common::Time::GetWallTimeAsISOString();
// remove ":" if the dir is to be added to env path, e.g. for playback
// with resources
logTimeDir = common::replaceAll(logTimeDir, ":", "");
// Override the default path settings if the _path parameter is set.
if (!_path.empty())
{
this->dataPtr->logBasePath = boost::filesystem::path(_path);
this->dataPtr->logCompletePath = this->dataPtr->logBasePath;
}
else
{
this->dataPtr->logCompletePath = this->dataPtr->logBasePath /
logTimeDir / this->dataPtr->logSubDir;
}
// Create the log directory if necessary
if (!boost::filesystem::exists(this->dataPtr->logCompletePath))
boost::filesystem::create_directories(this->dataPtr->logCompletePath);
if (_encoding != "bz2" && _encoding != "txt" && _encoding != "zlib")
gzthrow("Invalid log encoding[" + _encoding +
"]. Must be one of [bz2, zlib, txt]");
this->dataPtr->encoding = _encoding;
{
std::unique_lock<std::mutex> logLock(this->dataPtr->writeMutex);
this->dataPtr->logsEnd = this->dataPtr->logs.end();
// Start all the logs
for (LogRecordPrivate::Log_M::iterator iter = this->dataPtr->logs.begin();
iter != this->dataPtr->logsEnd; ++iter)
iter->second->Start(this->dataPtr->logCompletePath);
}
this->dataPtr->running = true;
this->dataPtr->paused = false;
this->dataPtr->firstUpdate = true;
this->dataPtr->stopThread = false;
this->dataPtr->readyToStart = false;
this->dataPtr->startTime = this->dataPtr->currTime = common::Time();
// Create a thread to cleanup recording.
this->dataPtr->cleanupThread.reset(new std::thread(
std::bind(&LogRecord::Cleanup, this)));
// Wait for thread to start
this->dataPtr->startThreadCondition.wait(lock);
// Start the update thread if it has not already been started
if (!this->dataPtr->updateThread)
{
std::unique_lock<std::mutex> updateLock(this->dataPtr->updateMutex);
this->dataPtr->updateThread.reset(new std::thread(
std::bind(&LogRecord::RunUpdate, this)));
this->dataPtr->startThreadCondition.wait(updateLock);
}
// Start the writing thread if it has not already been started
if (!this->dataPtr->writeThread)
{
std::unique_lock<std::mutex> writeLock(this->dataPtr->runWriteMutex);
this->dataPtr->writeThread.reset(new std::thread(
std::bind(&LogRecord::RunWrite, this)));
this->dataPtr->startThreadCondition.wait(writeLock);
}
return true;
}
//////////////////////////////////////////////////
const std::string &LogRecord::GetEncoding() const
{
return this->Encoding();
}
//////////////////////////////////////////////////
const std::string &LogRecord::Encoding() const
{
return this->dataPtr->encoding;
}
//////////////////////////////////////////////////
void LogRecord::Fini()
{
{
std::unique_lock<std::mutex> lock(this->dataPtr->controlMutex);
this->dataPtr->cleanupCondition.notify_all();
}
if (this->dataPtr->cleanupThread && this->dataPtr->cleanupThread->joinable())
this->dataPtr->cleanupThread->join();
this->dataPtr->cleanupThread.reset();
std::lock_guard<std::mutex> lock(this->dataPtr->controlMutex);
this->dataPtr->connections.clear();
// Remove all the logs.
this->ClearLogs();
this->dataPtr->logControlSub.reset();
this->dataPtr->logStatusPub.reset();
this->dataPtr->node.reset();
}
//////////////////////////////////////////////////
void LogRecord::Stop()
{
this->dataPtr->running = false;
this->dataPtr->cleanupCondition.notify_all();
if (this->dataPtr->cleanupThread && this->dataPtr->cleanupThread->joinable())
this->dataPtr->cleanupThread->join();
this->dataPtr->cleanupThread.reset();
this->dataPtr->savedModels.clear();
this->dataPtr->savedFiles.clear();
}
//////////////////////////////////////////////////
void LogRecord::ClearLogs()
{
std::lock_guard<std::mutex> logLock(this->dataPtr->writeMutex);
// Delete all the log objects
for (LogRecordPrivate::Log_M::iterator iter = this->dataPtr->logs.begin();
iter != this->dataPtr->logs.end(); ++iter)
{
delete iter->second;
}
this->dataPtr->logs.clear();
this->dataPtr->logsEnd = this->dataPtr->logs.end();
}
//////////////////////////////////////////////////
void LogRecord::SetPaused(const bool _paused)
{
this->dataPtr->paused = _paused;
}
//////////////////////////////////////////////////
bool LogRecord::GetPaused() const
{
return this->Paused();
}
//////////////////////////////////////////////////
bool LogRecord::Paused() const
{
return this->dataPtr->paused;
}
//////////////////////////////////////////////////
double LogRecord::Period() const
{
return this->dataPtr->period;
}
//////////////////////////////////////////////////
void LogRecord::SetPeriod(const double _period)
{
this->dataPtr->period = _period;
}
//////////////////////////////////////////////////
std::string LogRecord::Filter() const
{
return this->dataPtr->filter;
}
//////////////////////////////////////////////////
void LogRecord::SetFilter(const std::string &_filter)
{
this->dataPtr->filter = _filter;
}
//////////////////////////////////////////////////
bool LogRecord::GetRunning() const
{
return this->Running();
}
//////////////////////////////////////////////////
bool LogRecord::Running() const
{
return this->dataPtr->running;
}
//////////////////////////////////////////////////
bool LogRecord::RecordResources() const
{
return this->dataPtr->recordResources;
}
//////////////////////////////////////////////////
void LogRecord::SetRecordResources(const bool _record)
{
this->dataPtr->recordResources = _record;
}
//////////////////////////////////////////////////
void LogRecord::Add(const std::string &_name, const std::string &_filename,
std::function<bool (std::ostringstream &)> _logCallback)
{
std::lock_guard<std::mutex> logLock(this->dataPtr->writeMutex);
// Check to see if the log has already been added.
if (this->dataPtr->logs.find(_name) != this->dataPtr->logs.end())
{
GZ_ASSERT(this->dataPtr->logs.find(_name)->second != NULL,
"Unable to find log");
if (this->dataPtr->logs.find(_name)->second->RelativeFilename() !=
_filename)
{
gzerr << "Attempting to add a duplicate log object named["
<< _name << "] with a filename of [" << _filename << "].\n";
return;
}
else
{
return;
}
}
LogRecordPrivate::Log *newLog;
// Create a new log object
try
{
newLog = new LogRecordPrivate::Log(this, _filename, _logCallback);
}
catch(...)
{
gzthrow("Unable to create log. File permissions are probably bad.");
}
if (this->dataPtr->running)
newLog->Start(this->dataPtr->logCompletePath);
// Add the log to our map
this->dataPtr->logs[_name] = newLog;
// Update the pointer to the end of the log objects list.
this->dataPtr->logsEnd = this->dataPtr->logs.end();
if (!this->dataPtr->node)
{
this->dataPtr->node = transport::NodePtr(new transport::Node());
this->dataPtr->node->Init();
this->dataPtr->logControlSub =
this->dataPtr->node->Subscribe("~/log/control",
&LogRecord::OnLogControl, this);
this->dataPtr->logStatusPub =
this->dataPtr->node->Advertise<msgs::LogStatus>("~/log/status");
}
}
//////////////////////////////////////////////////
bool LogRecord::Remove(const std::string &_name)
{
std::lock_guard<std::mutex> logLock(this->dataPtr->writeMutex);
bool result = false;
LogRecordPrivate::Log_M::iterator iter = this->dataPtr->logs.find(_name);
if (iter != this->dataPtr->logs.end())
{
delete iter->second;
this->dataPtr->logs.erase(iter);
// Update the pointer to the end of the log objects list.
this->dataPtr->logsEnd = this->dataPtr->logs.end();
result = true;
}
return result;
}
//////////////////////////////////////////////////
std::string LogRecord::GetFilename(const std::string &_name) const
{
return this->Filename(_name);
}
//////////////////////////////////////////////////
std::string LogRecord::Filename(const std::string &_name) const
{
std::lock_guard<std::mutex> logLock(this->dataPtr->writeMutex);
std::string result;
LogRecordPrivate::Log_M::const_iterator iter =
this->dataPtr->logs.find(_name);
if (iter != this->dataPtr->logs.end())
{
GZ_ASSERT(iter->second, "Invalid log");
result = iter->second->CompleteFilename();
}
else
result = this->dataPtr->logs.begin()->second->CompleteFilename();
return result;
}
//////////////////////////////////////////////////
unsigned int LogRecord::GetFileSize(const std::string &_name) const
{
return this->FileSize(_name);
}
//////////////////////////////////////////////////
unsigned int LogRecord::FileSize(const std::string &_name) const
{
unsigned int result = 0;
// Get the filename of the specified log object;
std::string filename = this->Filename(_name);
// Get the size of the log file on disk.
if (!filename.empty())
{
// Get the size of the file
if (!filename.empty() && boost::filesystem::exists(filename))
result = boost::filesystem::file_size(filename);
}
// Add in the contents of the write buffer. This is the data that will be
// written to disk soon.
{
std::lock_guard<std::mutex> lock(this->dataPtr->writeMutex);
LogRecordPrivate::Log_M::const_iterator iter =
this->dataPtr->logs.find(_name);
if (iter != this->dataPtr->logs.end())
{
GZ_ASSERT(iter->second, "Log object is NULL");
result += iter->second->BufferSize();
}
}
return result;
}
//////////////////////////////////////////////////
void LogRecord::SetBasePath(const std::string &_path)
{
// Make sure the directory exists
if (!boost::filesystem::exists(_path))
boost::filesystem::create_directories(_path);
// Make sure we have a directory
if (!boost::filesystem::is_directory(_path))
{
gzerr << "Path " << _path << " is not a directory. Please only specify a "
<< "directory for data logging.\n";
return;
}
// Make sure the path is writable.
// Note: This is not cross-platform compatible.
if (access(_path.c_str(), W_OK) != 0)
{
gzerr << "You do no have permission to write into " << _path << "\n";
return;
}
this->dataPtr->logBasePath = _path;
}
//////////////////////////////////////////////////
std::string LogRecord::GetBasePath() const
{
return this->BasePath();
}
//////////////////////////////////////////////////
std::string LogRecord::BasePath() const
{
return this->dataPtr->logBasePath.string();
}
//////////////////////////////////////////////////
bool LogRecord::GetFirstUpdate() const
{
return this->FirstUpdate();
}
//////////////////////////////////////////////////
bool LogRecord::FirstUpdate() const
{
return this->dataPtr->firstUpdate;
}
//////////////////////////////////////////////////
bool LogRecord::SaveModels(const std::set<std::string> &_models)
{
std::set<std::string> diff;
std::set_difference(_models.begin(), _models.end(),
this->dataPtr->savedModels.begin(), this->dataPtr->savedModels.end(),
std::inserter(diff, diff.begin()));
for (auto &model: diff)
{
if (model.empty())
continue;
this->dataPtr->savedModels.insert(model);
bool modelFound = false;
for (const auto &path : common::SystemPaths::Instance()->GetModelPaths())
{
boost::filesystem::path srcModelPath(path);
srcModelPath /= model;
if (boost::filesystem::exists(srcModelPath))
{
modelFound = true;
boost::filesystem::path destModelPath =
this->dataPtr->logCompletePath / model;
if (!gazebo::common::copyDir(srcModelPath, destModelPath))
{
gzerr << "Failed to copy model from '" << srcModelPath.string()
<< "' to '" << destModelPath.string() << "'" << std::endl;
}
break;
}
}
if (!modelFound)
{
gzwarn << "Model: " << model << " not found, "
<< "please check the value of env variable GAZEBO_MODEL_PATH\n";
}
}
return true;
}
//////////////////////////////////////////////////
bool LogRecord::SaveFiles(const std::set<std::string> &_files)
{
if (_files.empty())
return false;
bool saveError = false;
std::set<std::string> diff;
std::set_difference(_files.begin(), _files.end(),
this->dataPtr->savedFiles.begin(),
this->dataPtr->savedFiles.end(),
std::inserter(diff, diff.begin()));
for (auto &file : diff)
{
if (file.empty())
continue;
this->dataPtr->savedFiles.insert(file);
bool fileFound = false;
std::string prefix = "file://";
std::string fileName = file;
boost::filesystem::path srcPath;
if (fileName.compare(0, prefix.size(), prefix) == 0)
{
// strip prefix
fileName = file.substr(prefix.size());
// search in gazebo path
for (const auto &path : common::SystemPaths::Instance()->GetGazeboPaths())
{
auto p = boost::filesystem::path(path) / fileName;
if (common::exists(p.string()))
{
srcPath = path;
fileFound = true;
break;
}
}
}
// if not found in gazebo path or resource has abs path then check local
// filesystem
if (!fileFound || fileName[0] == '/')
{
fileFound = common::exists(fileName);
}
// copy resource
// NOTE: if file is a mesh, e.g. box.dae, it could contain reference to
// to texture files in other directories. A hacky workaround is to copy
// entire model dir
if (fileFound)
{
// HACK! copy entire model dir if mesh
size_t meshIdx = fileName.find("/meshes/");
if (meshIdx != std::string::npos)
{
auto modelPath =
boost::filesystem::path(fileName.substr(0, meshIdx));
srcPath = srcPath / modelPath;
boost::filesystem::path destPath =
this->dataPtr->logCompletePath / modelPath;
boost::system::error_code errorCode;
boost::filesystem::create_directories(destPath, errorCode);
if (errorCode != boost::system::errc::success ||
!gazebo::common::copyDir(srcPath, destPath))
{
gzerr << "Failed to copy model from '" << srcPath.string()
<< "' to '" << destPath.string() << "'" << std::endl;
saveError = true;
}
}
// else copy only the specified file
else
{
srcPath = srcPath / fileName;
boost::filesystem::path destPath =
this->dataPtr->logCompletePath / fileName;
boost::system::error_code errorCode;
boost::filesystem::create_directories(
destPath.parent_path(), errorCode);
if (errorCode == boost::system::errc::success)
boost::filesystem::copy_file(srcPath, destPath, errorCode);
else
{
gzerr << "Failed to copy file from '" << srcPath.string()
<< "' to '" << destPath.string() << "'" << std::endl;
saveError = true;
}
}
}
else
{
gzerr << "File: " << file << " not found!" << std::endl;
saveError = true;
}
}
return !saveError;
}
//////////////////////////////////////////////////
void LogRecord::Notify()
{
if (this->dataPtr->running)
this->dataPtr->updateCondition.notify_all();
}
//////////////////////////////////////////////////
void LogRecord::RunUpdate()
{
std::unique_lock<std::mutex> updateLock(this->dataPtr->updateMutex);
this->dataPtr->startThreadCondition.notify_all();
// This loop will write data to disk.
while (!this->dataPtr->stopThread)
{
// Don't completely lock, just to be safe.
this->dataPtr->updateCondition.wait(updateLock);
if (!this->dataPtr->stopThread)
this->Update();
}
}
//////////////////////////////////////////////////
void LogRecord::Update()
{
if (!this->dataPtr->paused)
{
unsigned int size = 0;
{
std::lock_guard<std::mutex> lock(this->dataPtr->writeMutex);
// Collect all the new log data. This will not write data to disk.
for (this->dataPtr->updateIter = this->dataPtr->logs.begin();
this->dataPtr->updateIter != this->dataPtr->logsEnd;
++this->dataPtr->updateIter)
{
size += this->dataPtr->updateIter->second->Update();
}
}
if (this->dataPtr->firstUpdate)
{
this->dataPtr->firstUpdate = false;
this->dataPtr->startTime = common::Time::GetWallTime();
}
// Signal that new data is available.
if (size > 0)
this->dataPtr->dataAvailableCondition.notify_one();
this->dataPtr->currTime = common::Time::GetWallTime();
// Output the new log status
this->PublishLogStatus();
}
}
//////////////////////////////////////////////////
void LogRecord::RunWrite()
{
// Wait for new data.
std::unique_lock<std::mutex> lock(this->dataPtr->runWriteMutex);
this->dataPtr->startThreadCondition.notify_all();
// This loop will write data to disk.
while (!this->dataPtr->stopThread)
{
this->dataPtr->dataAvailableCondition.wait(lock);
this->Write(false);
}
}
//////////////////////////////////////////////////
void LogRecord::Write(const bool /*_force*/)
{
std::lock_guard<std::mutex> lock(this->dataPtr->writeMutex);
// Collect all the new log data.
for (this->dataPtr->updateIter = this->dataPtr->logs.begin();
this->dataPtr->updateIter != this->dataPtr->logsEnd;
++this->dataPtr->updateIter)
{
this->dataPtr->updateIter->second->Write();
}
}
//////////////////////////////////////////////////
common::Time LogRecord::GetRunTime() const
{
return this->RunTime();
}
//////////////////////////////////////////////////
common::Time LogRecord::RunTime() const
{
return this->dataPtr->currTime - this->dataPtr->startTime;
}
//////////////////////////////////////////////////
LogRecordPrivate::Log::Log(LogRecord *_parent,
const std::string &_relativeFilename,
std::function<bool (std::ostringstream &)> _logCB)
{
this->parent = _parent;
this->logCB = _logCB;
this->relativeFilename = _relativeFilename;
}
//////////////////////////////////////////////////
LogRecordPrivate::Log::~Log()
{
this->Stop();
}
//////////////////////////////////////////////////
unsigned int LogRecordPrivate::Log::Update()
{
std::ostringstream stream;
// Get log data via the callback.
if (this->logCB(stream))
{
std::string data = stream.str();
if (!data.empty())
{
const std::string &encodingLocal = this->parent->Encoding();
this->buffer.append("<chunk encoding='");
this->buffer.append(encodingLocal);
this->buffer.append("'>\n");
this->buffer.append("<![CDATA[");
// Compress the data.
if (encodingLocal == "bz2")
{
std::string str;
// Compress to bzip2
{
boost::iostreams::filtering_ostream out;
out.push(boost::iostreams::bzip2_compressor());
out.push(std::back_inserter(str));
boost::iostreams::copy(boost::make_iterator_range(data), out);
}
// Encode in base64.
Base64Encode(str.c_str(), str.size(), this->buffer);
}
else if (encodingLocal == "zlib")
{
std::string str;
// Compress to zlib
{
boost::iostreams::filtering_ostream out;
out.push(boost::iostreams::zlib_compressor());
out.push(std::back_inserter(str));
boost::iostreams::copy(boost::make_iterator_range(data), out);
}
// Encode in base64.
Base64Encode(str.c_str(), str.size(), this->buffer);
}
else if (encodingLocal == "txt")
this->buffer.append(data);
else
gzerr << "Unknown log file encoding[" << encodingLocal << "]\n";
this->buffer.append("]]>\n");
this->buffer.append("</chunk>\n");
}
}
return this->buffer.size();
}
//////////////////////////////////////////////////
void LogRecordPrivate::Log::ClearBuffer()
{
this->buffer.clear();
}
//////////////////////////////////////////////////
unsigned int LogRecordPrivate::Log::BufferSize()
{
return this->buffer.size();
}
//////////////////////////////////////////////////
std::string LogRecordPrivate::Log::RelativeFilename() const
{
return this->relativeFilename;
}
//////////////////////////////////////////////////
std::string LogRecordPrivate::Log::CompleteFilename() const
{
return this->completePath.string();
}
//////////////////////////////////////////////////
void LogRecordPrivate::Log::Stop()
{
if (this->logFile.is_open())
{
this->Update();
this->Write();
std::string xmlEnd = "</gazebo_log>";
this->logFile.write(xmlEnd.c_str(), xmlEnd.size());
this->logFile.close();
}
this->completePath.clear();
}
//////////////////////////////////////////////////
void LogRecordPrivate::Log::Start(const boost::filesystem::path &_path)
{
// Make the full path for the log file
this->completePath = _path / this->relativeFilename;
// Make sure the file does not exist
if (boost::filesystem::exists(this->completePath))
gzlog << "Filename [" + this->completePath.string() + "], already exists."
<< " The log file will be overwritten.\n";
std::ostringstream stream;
stream << "<?xml version='1.0'?>\n"
<< "<gazebo_log>\n"
<< "<header>\n"
<< "<log_version>" << GZ_LOG_VERSION << "</log_version>\n"
<< "<gazebo_version>" << GAZEBO_VERSION_FULL << "</gazebo_version>\n"
<< "<rand_seed>" << ignition::math::Rand::Seed() << "</rand_seed>\n"
<< "</header>\n";
this->buffer.append(stream.str());
}
//////////////////////////////////////////////////
void LogRecordPrivate::Log::Write()
{
// Make sure the file is open for writing
if (!this->logFile.is_open())
{
// Try to open it...
this->logFile.open(this->completePath.string().c_str(),
std::fstream::out | std::ios::binary);
// Throw an error if we couldn't open the file for writing.
if (!this->logFile.is_open())
gzthrow("Unable to open file for logging:" +
this->completePath.string() + "]");
}
// Check to see if the log file still exists on disk. This will catch the
// case when someone deletes a log file while recording.
if (!boost::filesystem::exists(this->completePath.string().c_str()))
{
gzerr << "Log file[" << this->completePath << "] no longer exists. "
<< "Unable to write log data.\n";
// We have to clear the buffer, or else it may grow indefinitely.
this->buffer.clear();
return;
}
// Write out the contents of the buffer.
this->logFile.write(this->buffer.c_str(), this->buffer.size());
this->logFile.flush();
// Clear the buffer.
this->buffer.clear();
}
//////////////////////////////////////////////////
void LogRecord::OnLogControl(ConstLogControlPtr &_data)
{
if (_data->has_base_path() && !_data->base_path().empty())
this->SetBasePath(_data->base_path());
std::string msgEncoding = "zlib";
if (_data->has_encoding())
msgEncoding = _data->encoding();
if (_data->has_start() && _data->start())
{
this->Start(msgEncoding);
}
else if (_data->has_stop() && _data->stop())
{
this->Stop();
}
else if (_data->has_paused())
{
this->SetPaused(_data->paused());
}
// Output the new log status
this->PublishLogStatus();
}
//////////////////////////////////////////////////
void LogRecord::PublishLogStatus()
{
if (this->dataPtr->logs.empty() || !this->dataPtr->logStatusPub ||
!this->dataPtr->logStatusPub->HasConnections())
return;
/// \todo right now this function will only report on the first log.
msgs::LogStatus msg;
unsigned int size = 0;
// Set the time of the status message
msgs::Set(msg.mutable_sim_time(), this->RunTime());
// Set the log recording base path name
msg.mutable_log_file()->set_base_path(this->BasePath());
// Get the full name of the log file
msg.mutable_log_file()->set_full_path(this->Filename());
// Set the URI of th log file
msg.mutable_log_file()->set_uri(transport::Connection::GetLocalHostname());
// Get the size of the log file
size = this->FileSize();
if (size < 1000)
msg.mutable_log_file()->set_size_units(msgs::LogStatus::LogFile::BYTES);
else if (size < 1e6)
{
msg.mutable_log_file()->set_size(size / 1.0e3);
msg.mutable_log_file()->set_size_units(msgs::LogStatus::LogFile::K_BYTES);
}
else if (size < 1e9)
{
msg.mutable_log_file()->set_size(size / 1.0e6);
msg.mutable_log_file()->set_size_units(msgs::LogStatus::LogFile::M_BYTES);
}
else
{
msg.mutable_log_file()->set_size(size / 1.0e9);
msg.mutable_log_file()->set_size_units(msgs::LogStatus::LogFile::G_BYTES);
}
this->dataPtr->logStatusPub->Publish(msg);
}
//////////////////////////////////////////////////
void LogRecord::Cleanup()
{
std::unique_lock<std::mutex> lock(this->dataPtr->controlMutex);
this->dataPtr->startThreadCondition.notify_all();
// Wait for the cleanup signal
this->dataPtr->cleanupCondition.wait(lock);
bool currentPauseState = this->dataPtr->pauseState;
event::Events::pause(true);
// Reset the flags
this->dataPtr->paused = false;
this->dataPtr->running = false;
this->dataPtr->stopThread = true;
// Kick the update thread
{
std::lock_guard<std::mutex> updateLock(this->dataPtr->updateMutex);
this->dataPtr->updateCondition.notify_all();
}
// Wait for the write thread, if it exists
if (this->dataPtr->updateThread)
this->dataPtr->updateThread->join();
// Kick the write thread
{
std::lock_guard<std::mutex> lock2(this->dataPtr->runWriteMutex);
this->dataPtr->dataAvailableCondition.notify_all();
}
// Wait for the write thread, if it exists
if (this->dataPtr->writeThread)
this->dataPtr->writeThread->join();
this->dataPtr->updateThread.reset();
this->dataPtr->writeThread.reset();
// Update and write one last time to make sure we log all data.
this->Update();
this->Write(true);
// Stop all the logs
for (LogRecordPrivate::Log_M::iterator iter = this->dataPtr->logs.begin();
iter != this->dataPtr->logsEnd; ++iter)
{
iter->second->Stop();
}
// Reset the times
this->dataPtr->startTime = this->dataPtr->currTime = common::Time();
// Output the new log status
this->PublishLogStatus();
event::Events::pause(currentPauseState);
this->dataPtr->readyToStart = true;
}
//////////////////////////////////////////////////
bool LogRecord::IsReadyToStart() const
{
return this->dataPtr->readyToStart;
}
//////////////////////////////////////////////////
unsigned int LogRecord::GetBufferSize() const
{
return this->BufferSize();
}
//////////////////////////////////////////////////
unsigned int LogRecord::BufferSize() const
{
std::lock_guard<std::mutex> lock(this->dataPtr->writeMutex);
unsigned int size = 0;
for (LogRecordPrivate::Log_M::const_iterator iter =
this->dataPtr->logs.begin();
iter != this->dataPtr->logs.end(); ++iter)
{
size += iter->second->BufferSize();
}
return size;
}