|  | /* | 
|  | *  Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "webrtc/system_wrappers/interface/data_log.h" | 
|  |  | 
|  | #include <assert.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <list> | 
|  |  | 
|  | #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" | 
|  | #include "webrtc/system_wrappers/interface/event_wrapper.h" | 
|  | #include "webrtc/system_wrappers/interface/file_wrapper.h" | 
|  | #include "webrtc/system_wrappers/interface/rw_lock_wrapper.h" | 
|  | #include "webrtc/system_wrappers/interface/thread_wrapper.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | DataLogImpl::CritSectScopedPtr DataLogImpl::crit_sect_( | 
|  | CriticalSectionWrapper::CreateCriticalSection()); | 
|  |  | 
|  | DataLogImpl* DataLogImpl::instance_ = NULL; | 
|  |  | 
|  | // A Row contains cells, which are indexed by the column names as std::string. | 
|  | // The string index is treated in a case sensitive way. | 
|  | class Row { | 
|  | public: | 
|  | Row(); | 
|  | ~Row(); | 
|  |  | 
|  | // Inserts a Container into the cell of the column specified with | 
|  | // column_name. | 
|  | // column_name is treated in a case sensitive way. | 
|  | int InsertCell(const std::string& column_name, | 
|  | const Container* value_container); | 
|  |  | 
|  | // Converts the value at the column specified by column_name to a string | 
|  | // stored in value_string. | 
|  | // column_name is treated in a case sensitive way. | 
|  | void ToString(const std::string& column_name, std::string* value_string); | 
|  |  | 
|  | private: | 
|  | // Collection of containers indexed by column name as std::string | 
|  | typedef std::map<std::string, const Container*> CellMap; | 
|  |  | 
|  | CellMap                   cells_; | 
|  | CriticalSectionWrapper*   cells_lock_; | 
|  | }; | 
|  |  | 
|  | // A LogTable contains multiple rows, where only the latest row is active for | 
|  | // editing. The rows are defined by the ColumnMap, which contains the name of | 
|  | // each column and the length of the column (1 for one-value-columns and greater | 
|  | // than 1 for multi-value-columns). | 
|  | class LogTable { | 
|  | public: | 
|  | LogTable(); | 
|  | ~LogTable(); | 
|  |  | 
|  | // Adds the column with name column_name to the table. The column will be a | 
|  | // multi-value-column if multi_value_length is greater than 1. | 
|  | // column_name is treated in a case sensitive way. | 
|  | int AddColumn(const std::string& column_name, int multi_value_length); | 
|  |  | 
|  | // Buffers the current row while it is waiting to be written to file, | 
|  | // which is done by a call to Flush(). A new row is available when the | 
|  | // function returns | 
|  | void NextRow(); | 
|  |  | 
|  | // Inserts a Container into the cell of the column specified with | 
|  | // column_name. | 
|  | // column_name is treated in a case sensitive way. | 
|  | int InsertCell(const std::string& column_name, | 
|  | const Container* value_container); | 
|  |  | 
|  | // Creates a log file, named as specified in the string file_name, to | 
|  | // where the table will be written when calling Flush(). | 
|  | int CreateLogFile(const std::string& file_name); | 
|  |  | 
|  | // Write all complete rows to file. | 
|  | // May not be called by two threads simultaneously (doing so may result in | 
|  | // a race condition). Will be called by the file_writer_thread_ when that | 
|  | // thread is running. | 
|  | void Flush(); | 
|  |  | 
|  | private: | 
|  | // Collection of multi_value_lengths indexed by column name as std::string | 
|  | typedef std::map<std::string, int> ColumnMap; | 
|  | typedef std::list<Row*> RowList; | 
|  |  | 
|  | ColumnMap               columns_; | 
|  | RowList                 rows_[2]; | 
|  | RowList*                rows_history_; | 
|  | RowList*                rows_flush_; | 
|  | Row*                    current_row_; | 
|  | FileWrapper*            file_; | 
|  | bool                    write_header_; | 
|  | CriticalSectionWrapper* table_lock_; | 
|  | }; | 
|  |  | 
|  | Row::Row() | 
|  | : cells_(), | 
|  | cells_lock_(CriticalSectionWrapper::CreateCriticalSection()) { | 
|  | } | 
|  |  | 
|  | Row::~Row() { | 
|  | for (CellMap::iterator it = cells_.begin(); it != cells_.end();) { | 
|  | delete it->second; | 
|  | // For maps all iterators (except the erased) are valid after an erase | 
|  | cells_.erase(it++); | 
|  | } | 
|  | delete cells_lock_; | 
|  | } | 
|  |  | 
|  | int Row::InsertCell(const std::string& column_name, | 
|  | const Container* value_container) { | 
|  | CriticalSectionScoped synchronize(cells_lock_); | 
|  | assert(cells_.count(column_name) == 0); | 
|  | if (cells_.count(column_name) > 0) | 
|  | return -1; | 
|  | cells_[column_name] = value_container; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void Row::ToString(const std::string& column_name, | 
|  | std::string* value_string) { | 
|  | CriticalSectionScoped synchronize(cells_lock_); | 
|  | const Container* container = cells_[column_name]; | 
|  | if (container == NULL) { | 
|  | *value_string = "NaN,"; | 
|  | return; | 
|  | } | 
|  | container->ToString(value_string); | 
|  | } | 
|  |  | 
|  | LogTable::LogTable() | 
|  | : columns_(), | 
|  | rows_(), | 
|  | rows_history_(&rows_[0]), | 
|  | rows_flush_(&rows_[1]), | 
|  | current_row_(new Row), | 
|  | file_(FileWrapper::Create()), | 
|  | write_header_(true), | 
|  | table_lock_(CriticalSectionWrapper::CreateCriticalSection()) { | 
|  | } | 
|  |  | 
|  | LogTable::~LogTable() { | 
|  | for (RowList::iterator row_it = rows_history_->begin(); | 
|  | row_it != rows_history_->end();) { | 
|  | delete *row_it; | 
|  | row_it = rows_history_->erase(row_it); | 
|  | } | 
|  | for (ColumnMap::iterator col_it = columns_.begin(); | 
|  | col_it != columns_.end();) { | 
|  | // For maps all iterators (except the erased) are valid after an erase | 
|  | columns_.erase(col_it++); | 
|  | } | 
|  | if (file_ != NULL) { | 
|  | file_->Flush(); | 
|  | file_->CloseFile(); | 
|  | delete file_; | 
|  | } | 
|  | delete current_row_; | 
|  | delete table_lock_; | 
|  | } | 
|  |  | 
|  | int LogTable::AddColumn(const std::string& column_name, | 
|  | int multi_value_length) { | 
|  | assert(multi_value_length > 0); | 
|  | if (!write_header_) { | 
|  | // It's not allowed to add new columns after the header | 
|  | // has been written. | 
|  | assert(false); | 
|  | return -1; | 
|  | } else { | 
|  | CriticalSectionScoped synchronize(table_lock_); | 
|  | if (write_header_) | 
|  | columns_[column_name] = multi_value_length; | 
|  | else | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void LogTable::NextRow() { | 
|  | CriticalSectionScoped sync_rows(table_lock_); | 
|  | rows_history_->push_back(current_row_); | 
|  | current_row_ = new Row; | 
|  | } | 
|  |  | 
|  | int LogTable::InsertCell(const std::string& column_name, | 
|  | const Container* value_container) { | 
|  | CriticalSectionScoped synchronize(table_lock_); | 
|  | assert(columns_.count(column_name) > 0); | 
|  | if (columns_.count(column_name) == 0) | 
|  | return -1; | 
|  | return current_row_->InsertCell(column_name, value_container); | 
|  | } | 
|  |  | 
|  | int LogTable::CreateLogFile(const std::string& file_name) { | 
|  | if (file_name.length() == 0) | 
|  | return -1; | 
|  | if (file_->Open()) | 
|  | return -1; | 
|  | file_->OpenFile(file_name.c_str(), | 
|  | false,  // Open with read/write permissions | 
|  | false,  // Don't wraparound and write at the beginning when | 
|  | // the file is full | 
|  | true);  // Open as a text file | 
|  | if (file_ == NULL) | 
|  | return -1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void LogTable::Flush() { | 
|  | ColumnMap::iterator column_it; | 
|  | bool commit_header = false; | 
|  | if (write_header_) { | 
|  | CriticalSectionScoped synchronize(table_lock_); | 
|  | if (write_header_) { | 
|  | commit_header = true; | 
|  | write_header_ = false; | 
|  | } | 
|  | } | 
|  | if (commit_header) { | 
|  | for (column_it = columns_.begin(); | 
|  | column_it != columns_.end(); ++column_it) { | 
|  | if (column_it->second > 1) { | 
|  | file_->WriteText("%s[%u],", column_it->first.c_str(), | 
|  | column_it->second); | 
|  | for (int i = 1; i < column_it->second; ++i) | 
|  | file_->WriteText(","); | 
|  | } else { | 
|  | file_->WriteText("%s,", column_it->first.c_str()); | 
|  | } | 
|  | } | 
|  | if (columns_.size() > 0) | 
|  | file_->WriteText("\n"); | 
|  | } | 
|  |  | 
|  | // Swap the list used for flushing with the list containing the row history | 
|  | // and clear the history. We also create a local pointer to the new | 
|  | // list used for flushing to avoid race conditions if another thread | 
|  | // calls this function while we are writing. | 
|  | // We don't want to block the list while we're writing to file. | 
|  | { | 
|  | CriticalSectionScoped synchronize(table_lock_); | 
|  | RowList* tmp = rows_flush_; | 
|  | rows_flush_ = rows_history_; | 
|  | rows_history_ = tmp; | 
|  | rows_history_->clear(); | 
|  | } | 
|  |  | 
|  | // Write all complete rows to file and delete them | 
|  | for (RowList::iterator row_it = rows_flush_->begin(); | 
|  | row_it != rows_flush_->end();) { | 
|  | for (column_it = columns_.begin(); | 
|  | column_it != columns_.end(); ++column_it) { | 
|  | std::string row_string; | 
|  | (*row_it)->ToString(column_it->first, &row_string); | 
|  | file_->WriteText("%s", row_string.c_str()); | 
|  | } | 
|  | if (columns_.size() > 0) | 
|  | file_->WriteText("\n"); | 
|  | delete *row_it; | 
|  | row_it = rows_flush_->erase(row_it); | 
|  | } | 
|  | } | 
|  |  | 
|  | int DataLog::CreateLog() { | 
|  | return DataLogImpl::CreateLog(); | 
|  | } | 
|  |  | 
|  | void DataLog::ReturnLog() { | 
|  | return DataLogImpl::ReturnLog(); | 
|  | } | 
|  |  | 
|  | std::string DataLog::Combine(const std::string& table_name, int table_id) { | 
|  | std::stringstream ss; | 
|  | std::string combined_id = table_name; | 
|  | std::string number_suffix; | 
|  | ss << "_" << table_id; | 
|  | ss >> number_suffix; | 
|  | combined_id += number_suffix; | 
|  | std::transform(combined_id.begin(), combined_id.end(), combined_id.begin(), | 
|  | ::tolower); | 
|  | return combined_id; | 
|  | } | 
|  |  | 
|  | int DataLog::AddTable(const std::string& table_name) { | 
|  | DataLogImpl* data_log = DataLogImpl::StaticInstance(); | 
|  | if (data_log == NULL) | 
|  | return -1; | 
|  | return data_log->AddTable(table_name); | 
|  | } | 
|  |  | 
|  | int DataLog::AddColumn(const std::string& table_name, | 
|  | const std::string& column_name, | 
|  | int multi_value_length) { | 
|  | DataLogImpl* data_log = DataLogImpl::StaticInstance(); | 
|  | if (data_log == NULL) | 
|  | return -1; | 
|  | return data_log->DataLogImpl::StaticInstance()->AddColumn(table_name, | 
|  | column_name, | 
|  | multi_value_length); | 
|  | } | 
|  |  | 
|  | int DataLog::NextRow(const std::string& table_name) { | 
|  | DataLogImpl* data_log = DataLogImpl::StaticInstance(); | 
|  | if (data_log == NULL) | 
|  | return -1; | 
|  | return data_log->DataLogImpl::StaticInstance()->NextRow(table_name); | 
|  | } | 
|  |  | 
|  | DataLogImpl::DataLogImpl() | 
|  | : counter_(1), | 
|  | tables_(), | 
|  | flush_event_(EventWrapper::Create()), | 
|  | file_writer_thread_(NULL), | 
|  | tables_lock_(RWLockWrapper::CreateRWLock()) { | 
|  | } | 
|  |  | 
|  | DataLogImpl::~DataLogImpl() { | 
|  | StopThread(); | 
|  | Flush();  // Write any remaining rows | 
|  | delete file_writer_thread_; | 
|  | delete flush_event_; | 
|  | for (TableMap::iterator it = tables_.begin(); it != tables_.end();) { | 
|  | delete static_cast<LogTable*>(it->second); | 
|  | // For maps all iterators (except the erased) are valid after an erase | 
|  | tables_.erase(it++); | 
|  | } | 
|  | delete tables_lock_; | 
|  | } | 
|  |  | 
|  | int DataLogImpl::CreateLog() { | 
|  | CriticalSectionScoped synchronize(crit_sect_.get()); | 
|  | if (instance_ == NULL) { | 
|  | instance_ = new DataLogImpl(); | 
|  | return instance_->Init(); | 
|  | } else { | 
|  | ++instance_->counter_; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int DataLogImpl::Init() { | 
|  | file_writer_thread_ = ThreadWrapper::CreateThread( | 
|  | DataLogImpl::Run, | 
|  | instance_, | 
|  | kHighestPriority, | 
|  | "DataLog"); | 
|  | if (file_writer_thread_ == NULL) | 
|  | return -1; | 
|  | unsigned int thread_id = 0; | 
|  | bool success = file_writer_thread_->Start(thread_id); | 
|  | if (!success) | 
|  | return -1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | DataLogImpl* DataLogImpl::StaticInstance() { | 
|  | return instance_; | 
|  | } | 
|  |  | 
|  | void DataLogImpl::ReturnLog() { | 
|  | CriticalSectionScoped synchronize(crit_sect_.get()); | 
|  | if (instance_ && instance_->counter_ > 1) { | 
|  | --instance_->counter_; | 
|  | return; | 
|  | } | 
|  | delete instance_; | 
|  | instance_ = NULL; | 
|  | } | 
|  |  | 
|  | int DataLogImpl::AddTable(const std::string& table_name) { | 
|  | WriteLockScoped synchronize(*tables_lock_); | 
|  | // Make sure we don't add a table which already exists | 
|  | if (tables_.count(table_name) > 0) | 
|  | return -1; | 
|  | tables_[table_name] = new LogTable(); | 
|  | if (tables_[table_name]->CreateLogFile(table_name + ".txt") == -1) | 
|  | return -1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int DataLogImpl::AddColumn(const std::string& table_name, | 
|  | const std::string& column_name, | 
|  | int multi_value_length) { | 
|  | ReadLockScoped synchronize(*tables_lock_); | 
|  | if (tables_.count(table_name) == 0) | 
|  | return -1; | 
|  | return tables_[table_name]->AddColumn(column_name, multi_value_length); | 
|  | } | 
|  |  | 
|  | int DataLogImpl::InsertCell(const std::string& table_name, | 
|  | const std::string& column_name, | 
|  | const Container* value_container) { | 
|  | ReadLockScoped synchronize(*tables_lock_); | 
|  | assert(tables_.count(table_name) > 0); | 
|  | if (tables_.count(table_name) == 0) | 
|  | return -1; | 
|  | return tables_[table_name]->InsertCell(column_name, value_container); | 
|  | } | 
|  |  | 
|  | int DataLogImpl::NextRow(const std::string& table_name) { | 
|  | ReadLockScoped synchronize(*tables_lock_); | 
|  | if (tables_.count(table_name) == 0) | 
|  | return -1; | 
|  | tables_[table_name]->NextRow(); | 
|  | if (file_writer_thread_ == NULL) { | 
|  | // Write every row to file as they get complete. | 
|  | tables_[table_name]->Flush(); | 
|  | } else { | 
|  | // Signal a complete row | 
|  | flush_event_->Set(); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void DataLogImpl::Flush() { | 
|  | ReadLockScoped synchronize(*tables_lock_); | 
|  | for (TableMap::iterator it = tables_.begin(); it != tables_.end(); ++it) { | 
|  | it->second->Flush(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool DataLogImpl::Run(void* obj) { | 
|  | static_cast<DataLogImpl*>(obj)->Process(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void DataLogImpl::Process() { | 
|  | // Wait for a row to be complete | 
|  | flush_event_->Wait(WEBRTC_EVENT_INFINITE); | 
|  | Flush(); | 
|  | } | 
|  |  | 
|  | void DataLogImpl::StopThread() { | 
|  | if (file_writer_thread_ != NULL) { | 
|  | file_writer_thread_->SetNotAlive(); | 
|  | flush_event_->Set(); | 
|  | // Call Stop() repeatedly, waiting for the Flush() call in Process() to | 
|  | // finish. | 
|  | while (!file_writer_thread_->Stop()) continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |