2016-03-31 02:25:35 -05:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
|
|
|
* This file is part of the LibreOffice project.
|
|
|
|
*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "AdminModel.hpp"
|
|
|
|
#include "config.h"
|
|
|
|
|
2016-03-31 02:25:35 -05:00
|
|
|
#include <memory>
|
|
|
|
#include <set>
|
|
|
|
#include <sstream>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <Poco/Net/WebSocket.h>
|
|
|
|
#include <Poco/Process.h>
|
|
|
|
#include <Poco/StringTokenizer.h>
|
2016-04-19 06:07:12 -05:00
|
|
|
#include <Poco/URI.h>
|
2016-03-31 02:25:35 -05:00
|
|
|
|
2016-10-22 09:24:27 -05:00
|
|
|
#include "LOOLProtocol.hpp"
|
loolwsd: include cleanup and organization
A source file (.cpp) must include its own header first.
This insures that the header is self-contained and
doesn't depend on arbitrary (and accidental) includes
before it to compile.
Furthermore, system headers should go next, followed by
C then C++ headers, then libraries (Poco, etc) and, finally,
project headers come last.
This makes sure that headers and included in the same dependency
order to avoid side-effects. For example, Poco should never rely on
anything from our project in the same way that a C header should
never rely on anything in C++, Poco, or project headers.
Also, includes ought to be sorted where possible, to improve
readability and avoid accidental duplicates (of which there
were a few).
Change-Id: I62cc1343e4a091d69195e37ed659dba20cfcb1ef
Reviewed-on: https://gerrit.libreoffice.org/25262
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
Tested-by: Ashod Nakashian <ashnakash@gmail.com>
2016-05-21 09:23:07 -05:00
|
|
|
#include "Log.hpp"
|
2016-04-18 08:29:17 -05:00
|
|
|
#include "Unit.hpp"
|
2016-03-31 02:25:35 -05:00
|
|
|
#include "Util.hpp"
|
|
|
|
|
2016-04-17 21:11:10 -05:00
|
|
|
void Document::addView(const std::string& sessionId)
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-04-14 15:29:31 -05:00
|
|
|
const auto ret = _views.emplace(sessionId, View(sessionId));
|
2016-03-31 02:25:35 -05:00
|
|
|
if (!ret.second)
|
|
|
|
{
|
2016-04-17 21:11:10 -05:00
|
|
|
Log::warn() << "View with SessionID [" + sessionId + "] already exists." << Log::end;
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-04-17 21:11:10 -05:00
|
|
|
++_activeViews;
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-17 21:11:10 -05:00
|
|
|
int Document::expireView(const std::string& sessionId)
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-04-14 15:29:31 -05:00
|
|
|
auto it = _views.find(sessionId);
|
2016-03-31 02:25:35 -05:00
|
|
|
if (it != _views.end())
|
|
|
|
{
|
|
|
|
it->second.expire();
|
2016-04-14 15:29:31 -05:00
|
|
|
|
|
|
|
// If last view, expire the Document also
|
2016-04-15 04:00:22 -05:00
|
|
|
if (--_activeViews == 0)
|
2016-04-14 15:29:31 -05:00
|
|
|
_end = std::time(nullptr);
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
2016-04-14 15:29:31 -05:00
|
|
|
|
2016-04-15 04:00:22 -05:00
|
|
|
return _activeViews;
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Subscriber::notify(const std::string& message)
|
|
|
|
{
|
2016-10-24 22:40:33 -05:00
|
|
|
// If there is no socket, then return false to
|
|
|
|
// signify we're disconnected.
|
2016-03-31 02:25:35 -05:00
|
|
|
auto webSocket = _ws.lock();
|
|
|
|
if (webSocket)
|
|
|
|
{
|
2016-10-24 22:40:33 -05:00
|
|
|
if (_subscriptions.find(LOOLProtocol::getFirstToken(message)) == _subscriptions.end())
|
|
|
|
{
|
|
|
|
// No subscribers for the given message.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-22 09:24:27 -05:00
|
|
|
try
|
|
|
|
{
|
|
|
|
UnitWSD::get().onAdminNotifyMessage(message);
|
|
|
|
webSocket->sendFrame(message.data(), message.length());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex)
|
|
|
|
{
|
|
|
|
Log::error() << "Failed to notify Admin subscriber with message ["
|
|
|
|
<< message << "] due to [" << ex.what() << "]." << Log::end;
|
|
|
|
}
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
2016-10-22 09:24:27 -05:00
|
|
|
|
|
|
|
return false;
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
2016-10-22 09:24:27 -05:00
|
|
|
bool Subscriber::subscribe(const std::string& command)
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
auto ret = _subscriptions.insert(command);
|
|
|
|
return ret.second;
|
|
|
|
}
|
|
|
|
|
2016-10-22 09:24:27 -05:00
|
|
|
void Subscriber::unsubscribe(const std::string& command)
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
_subscriptions.erase(command);
|
|
|
|
}
|
|
|
|
|
2016-04-12 01:09:39 -05:00
|
|
|
std::string AdminModel::query(const std::string& command)
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-10-22 09:24:27 -05:00
|
|
|
const auto token = LOOLProtocol::getFirstToken(command);
|
|
|
|
if (token == "documents")
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
return getDocuments();
|
|
|
|
}
|
2016-10-22 09:24:27 -05:00
|
|
|
else if (token == "active_users_count")
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
return std::to_string(getTotalActiveViews());
|
|
|
|
}
|
2016-10-22 09:24:27 -05:00
|
|
|
else if (token == "active_docs_count")
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
return std::to_string(_documents.size());
|
|
|
|
}
|
2016-10-22 09:24:27 -05:00
|
|
|
else if (token == "mem_stats")
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
return getMemStats();
|
|
|
|
}
|
2016-10-22 09:24:27 -05:00
|
|
|
else if (token == "mem_stats_size")
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
return std::to_string(_memStatsSize);
|
|
|
|
}
|
2016-10-22 09:24:27 -05:00
|
|
|
else if (token == "cpu_stats")
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
return getCpuStats();
|
|
|
|
}
|
2016-10-22 09:24:27 -05:00
|
|
|
else if (token == "cpu_stats_size")
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
|
|
|
return std::to_string(_cpuStatsSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::string("");
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns memory consumed by all active loolkit processes
|
|
|
|
unsigned AdminModel::getTotalMemoryUsage()
|
|
|
|
{
|
|
|
|
unsigned totalMem = 0;
|
|
|
|
for (auto& it: _documents)
|
|
|
|
{
|
2016-10-22 09:24:27 -05:00
|
|
|
if (!it.second.isExpired())
|
|
|
|
{
|
|
|
|
const int mem = Util::getMemoryUsage(it.second.getPid());
|
|
|
|
if (mem > 0)
|
|
|
|
{
|
|
|
|
totalMem += mem;
|
|
|
|
}
|
|
|
|
}
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return totalMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::subscribe(int nSessionId, std::shared_ptr<Poco::Net::WebSocket>& ws)
|
|
|
|
{
|
|
|
|
const auto ret = _subscribers.emplace(nSessionId, Subscriber(nSessionId, ws));
|
|
|
|
if (!ret.second)
|
|
|
|
{
|
|
|
|
Log::warn() << "Subscriber already exists" << Log::end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::subscribe(int nSessionId, const std::string& command)
|
|
|
|
{
|
|
|
|
auto subscriber = _subscribers.find(nSessionId);
|
2016-10-22 09:24:27 -05:00
|
|
|
if (subscriber != _subscribers.end())
|
|
|
|
{
|
|
|
|
subscriber->second.subscribe(command);
|
|
|
|
}
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::unsubscribe(int nSessionId, const std::string& command)
|
|
|
|
{
|
|
|
|
auto subscriber = _subscribers.find(nSessionId);
|
2016-10-22 09:24:27 -05:00
|
|
|
if (subscriber != _subscribers.end())
|
|
|
|
{
|
|
|
|
subscriber->second.unsubscribe(command);
|
|
|
|
}
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::addMemStats(unsigned memUsage)
|
|
|
|
{
|
|
|
|
_memStats.push_back(memUsage);
|
|
|
|
if (_memStats.size() > _memStatsSize)
|
|
|
|
{
|
|
|
|
_memStats.pop_front();
|
|
|
|
}
|
|
|
|
|
2016-10-24 22:40:33 -05:00
|
|
|
notify("mem_stats " + std::to_string(memUsage));
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::addCpuStats(unsigned cpuUsage)
|
|
|
|
{
|
|
|
|
_cpuStats.push_back(cpuUsage);
|
|
|
|
if (_cpuStats.size() > _cpuStatsSize)
|
|
|
|
{
|
|
|
|
_cpuStats.pop_front();
|
|
|
|
}
|
|
|
|
|
2016-10-24 22:40:33 -05:00
|
|
|
notify("cpu_stats " + std::to_string(cpuUsage));
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::setCpuStatsSize(unsigned size)
|
|
|
|
{
|
|
|
|
int wasteValuesLen = _cpuStats.size() - size;
|
|
|
|
while (wasteValuesLen-- > 0)
|
|
|
|
{
|
|
|
|
_cpuStats.pop_front();
|
|
|
|
}
|
|
|
|
_cpuStatsSize = size;
|
|
|
|
|
2016-10-24 22:40:33 -05:00
|
|
|
notify("settings cpu_stats_size=" + std::to_string(_cpuStatsSize));
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::setMemStatsSize(unsigned size)
|
|
|
|
{
|
|
|
|
int wasteValuesLen = _memStats.size() - size;
|
|
|
|
while (wasteValuesLen-- > 0)
|
|
|
|
{
|
|
|
|
_memStats.pop_front();
|
|
|
|
}
|
|
|
|
_memStatsSize = size;
|
|
|
|
|
2016-10-24 22:40:33 -05:00
|
|
|
notify("settings mem_stats_size=" + std::to_string(_memStatsSize));
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdminModel::notify(const std::string& message)
|
|
|
|
{
|
2016-10-24 22:40:33 -05:00
|
|
|
if (!_subscribers.empty())
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-10-24 22:40:33 -05:00
|
|
|
Log::trace("Message to admin console: " + message);
|
|
|
|
for (auto it = std::begin(_subscribers); it != std::end(_subscribers); )
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-10-24 22:40:33 -05:00
|
|
|
if (!it->second.notify(message))
|
|
|
|
{
|
|
|
|
it = _subscribers.erase(it);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
++it;
|
|
|
|
}
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-17 21:11:10 -05:00
|
|
|
void AdminModel::addDocument(const std::string& docKey, Poco::Process::PID pid,
|
|
|
|
const std::string& filename, const std::string& sessionId)
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-04-15 04:00:22 -05:00
|
|
|
const auto ret = _documents.emplace(docKey, Document(docKey, pid, filename));
|
2016-04-14 15:29:31 -05:00
|
|
|
ret.first->second.addView(sessionId);
|
|
|
|
|
|
|
|
// Notify the subscribers
|
2016-10-22 09:24:27 -05:00
|
|
|
const unsigned memUsage = Util::getMemoryUsage(pid);
|
2016-04-14 15:29:31 -05:00
|
|
|
std::ostringstream oss;
|
2016-04-19 06:07:12 -05:00
|
|
|
std::string encodedFilename;
|
|
|
|
Poco::URI::encode(filename, " ", encodedFilename);
|
2016-04-17 21:11:10 -05:00
|
|
|
oss << "adddoc "
|
2016-10-24 22:40:33 -05:00
|
|
|
<< pid << ' '
|
|
|
|
<< encodedFilename << ' '
|
|
|
|
<< sessionId << ' '
|
|
|
|
<< memUsage;
|
2016-04-14 15:29:31 -05:00
|
|
|
notify(oss.str());
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
2016-04-17 21:11:10 -05:00
|
|
|
void AdminModel::removeDocument(const std::string& docKey, const std::string& sessionId)
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-04-15 04:00:22 -05:00
|
|
|
auto docIt = _documents.find(docKey);
|
2016-04-14 15:29:31 -05:00
|
|
|
if (docIt != _documents.end() && !docIt->second.isExpired())
|
2016-03-31 02:25:35 -05:00
|
|
|
{
|
2016-04-14 15:29:31 -05:00
|
|
|
// Notify the subscribers
|
|
|
|
std::ostringstream oss;
|
2016-04-17 21:11:10 -05:00
|
|
|
oss << "rmdoc "
|
2016-10-24 22:40:33 -05:00
|
|
|
<< docIt->second.getPid() << ' '
|
2016-04-14 15:29:31 -05:00
|
|
|
<< sessionId;
|
|
|
|
notify(oss.str());
|
|
|
|
|
2016-03-31 02:25:35 -05:00
|
|
|
// TODO: The idea is to only expire the document and keep the history
|
|
|
|
// of documents open and close, to be able to give a detailed summary
|
|
|
|
// to the admin console with views. For now, just remove the document.
|
2016-04-14 15:29:31 -05:00
|
|
|
if (docIt->second.expireView(sessionId) == 0)
|
|
|
|
{
|
|
|
|
_documents.erase(docIt);
|
|
|
|
}
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-17 21:32:58 -05:00
|
|
|
void AdminModel::removeDocument(const std::string& docKey)
|
|
|
|
{
|
|
|
|
auto docIt = _documents.find(docKey);
|
|
|
|
if (docIt != _documents.end())
|
|
|
|
{
|
|
|
|
for (const auto& pair : docIt->second.getViews())
|
|
|
|
{
|
|
|
|
// Notify the subscribers
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << "rmdoc "
|
2016-10-24 22:40:33 -05:00
|
|
|
<< docIt->second.getPid() << ' '
|
2016-04-17 21:32:58 -05:00
|
|
|
<< pair.first;
|
|
|
|
notify(oss.str());
|
|
|
|
}
|
|
|
|
|
|
|
|
_documents.erase(docIt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-31 02:25:35 -05:00
|
|
|
std::string AdminModel::getMemStats()
|
|
|
|
{
|
2016-10-22 09:24:27 -05:00
|
|
|
std::ostringstream oss;
|
2016-03-31 02:25:35 -05:00
|
|
|
for (auto& i: _memStats)
|
|
|
|
{
|
2016-10-22 09:24:27 -05:00
|
|
|
oss << i << ',';
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
2016-10-22 09:24:27 -05:00
|
|
|
return oss.str();
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string AdminModel::getCpuStats()
|
|
|
|
{
|
2016-10-22 09:24:27 -05:00
|
|
|
std::ostringstream oss;
|
2016-03-31 02:25:35 -05:00
|
|
|
for (auto& i: _cpuStats)
|
|
|
|
{
|
2016-10-22 09:24:27 -05:00
|
|
|
oss << i << ',';
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
2016-10-22 09:24:27 -05:00
|
|
|
return oss.str();
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
unsigned AdminModel::getTotalActiveViews()
|
|
|
|
{
|
|
|
|
unsigned nTotalViews = 0;
|
|
|
|
for (auto& it: _documents)
|
|
|
|
{
|
2016-08-21 07:23:08 -05:00
|
|
|
if (!it.second.isExpired())
|
|
|
|
{
|
|
|
|
nTotalViews += it.second.getActiveViews();
|
|
|
|
}
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nTotalViews;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string AdminModel::getDocuments()
|
|
|
|
{
|
|
|
|
std::ostringstream oss;
|
|
|
|
for (auto& it: _documents)
|
|
|
|
{
|
|
|
|
if (it.second.isExpired())
|
|
|
|
continue;
|
|
|
|
|
2016-08-21 07:23:08 -05:00
|
|
|
const auto sPid = std::to_string(it.second.getPid());
|
|
|
|
const auto sFilename = it.second.getFilename();
|
|
|
|
const auto sViews = std::to_string(it.second.getActiveViews());
|
|
|
|
const auto sMem = std::to_string(Util::getMemoryUsage(it.second.getPid()));
|
|
|
|
const auto sElapsed = std::to_string(it.second.getElapsedTime());
|
2016-03-31 02:25:35 -05:00
|
|
|
|
2016-04-19 06:07:12 -05:00
|
|
|
std::string encodedFilename;
|
|
|
|
Poco::URI::encode(sFilename, " ", encodedFilename);
|
2016-08-21 07:23:08 -05:00
|
|
|
oss << sPid << ' '
|
|
|
|
<< encodedFilename << ' '
|
|
|
|
<< sViews << ' '
|
|
|
|
<< sMem << ' '
|
2016-04-01 02:50:14 -05:00
|
|
|
<< sElapsed << " \n ";
|
2016-03-31 02:25:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return oss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|