loleaflet/Makefile.am | 1 loleaflet/admin.strings.js | 1 loleaflet/build/adminDeps.js | 6 + loleaflet/dist/admin/admin.html | 1 loleaflet/dist/admin/adminAnalytics.html | 5 - loleaflet/dist/admin/adminHistory.html | 85 +++++++++++++++++++++ loleaflet/dist/admin/adminSettings.html | 1 loleaflet/src/admin/AdminSocketHistory.js | 51 +++++++++++++ wsd/Admin.cpp | 4 + wsd/AdminModel.cpp | 117 +++++++++++++++++++++++++++--- wsd/AdminModel.hpp | 14 ++- wsd/protocol.txt | 34 ++++++++ 12 files changed, 304 insertions(+), 16 deletions(-)
New commits: commit 0bb650e7c48deb65167305d7d11b61f83ea738b1 Author: Marco Viscido <marcovisc...@gmail.com> Date: Mon Apr 17 20:36:04 2017 +0200 keep expired document and query them to get the historical content A Document has its own snapshots set. A snapshot is a string representation of a JSON object. AdminModel keeps also the expired document objects. Query each document object in order to get their own history. Admin accepts an "history" command then returns a json object. An administrator checks the history by dashboard. Change-Id: I73c87eff334cdb5a4a58043b2b66f18a56240b3a Reviewed-on: https://gerrit.libreoffice.org/35926 Reviewed-by: pranavk <pran...@collabora.co.uk> Tested-by: pranavk <pran...@collabora.co.uk> diff --git a/loleaflet/Makefile.am b/loleaflet/Makefile.am index 0a1880ae..a2d4a617 100644 --- a/loleaflet/Makefile.am +++ b/loleaflet/Makefile.am @@ -52,6 +52,7 @@ pot: dist/toolbar/toolbar.js \ src/admin/AdminSocketBase.js \ src/admin/AdminSocketOverview.js \ + src/admin/AdminSocketHistory.js \ src/admin/AdminSocketSettings.js \ src/admin/Util.js \ src/control/Control.CharacterMap.js \ diff --git a/loleaflet/admin.strings.js b/loleaflet/admin.strings.js index fcfb574a..e5116276 100644 --- a/loleaflet/admin.strings.js +++ b/loleaflet/admin.strings.js @@ -8,6 +8,7 @@ l10nstrings.strSettings = _('Settings'); l10nstrings.strOverview = _('Overview'); l10nstrings.strCurrent = _('(current)'); l10nstrings.strAnalytics = _('Analytics'); +l10nstrings.strHistory = _('History'); l10nstrings.strDashboard = _('Dashboard'); l10nstrings.strUsersOnline = _('Users online'); l10nstrings.strDocumentsOpened = _('Documents opened'); diff --git a/loleaflet/build/adminDeps.js b/loleaflet/build/adminDeps.js index 5c2dd5da..f6efdad9 100644 --- a/loleaflet/build/adminDeps.js +++ b/loleaflet/build/adminDeps.js @@ -28,6 +28,12 @@ var adminDeps = { src: ['admin/AdminSocketSettings.js'], desc: 'Socket to handle settings from server', deps: ['AdminCore'] + }, + + AdminSocketHistory: { + src: ['admin/AdminSocketHistory.js'], + desc: 'Socket to query document history.', + deps: ['AdminCore'] } }; diff --git a/loleaflet/dist/admin/admin.html b/loleaflet/dist/admin/admin.html index 0d44ea4e..98a99eb8 100644 --- a/loleaflet/dist/admin/admin.html +++ b/loleaflet/dist/admin/admin.html @@ -58,6 +58,7 @@ <ul class="nav nav-sidebar"> <li class="active"><a href="#"><script>document.write(l10nstrings.strOverview)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li> <li><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li> + <li><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script></a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> diff --git a/loleaflet/dist/admin/adminAnalytics.html b/loleaflet/dist/admin/adminAnalytics.html index a0f7beef..2d7a37db 100644 --- a/loleaflet/dist/admin/adminAnalytics.html +++ b/loleaflet/dist/admin/adminAnalytics.html @@ -56,8 +56,9 @@ <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> - <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li> - <li class="active"><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li> + <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script></a></li> + <li class="active"><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li> + <li><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script></a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> diff --git a/loleaflet/dist/admin/adminHistory.html b/loleaflet/dist/admin/adminHistory.html new file mode 100644 index 00000000..60f53f6a --- /dev/null +++ b/loleaflet/dist/admin/adminHistory.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --> + <meta name="description" content=""> + <meta name="author" content=""> + + <title>LibreOffice Online - Admin console</title> + + <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> + <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> + <![endif]--> + <link rel="localizations" href="/loleaflet/dist/l10n/localizations.json" type="application/vnd.oftn.l10n+json"/> + + </head> + <body> + <script src="/loleaflet/dist/admin-bundle.js"></script> + <script src="/loleaflet/dist/branding.js"></script> + <script>if (brandProductName) {l10nstrings.strProductName = brandProductName}</script> + <script>document.title = l10nstrings.strProductName + ' - ' + l10nstrings.strAdminConsole</script> + <script> + if (window.location.protocol == "https:") { + host = 'wss://' + window.location.host + '/lool/adminws/' + } + else { + host = 'ws://' + window.location.host + '/lool/adminws/' + } + + Admin.History(host) + </script> + +<nav class="navbar navbar-inverse navbar-fixed-top"> + <div class="container-fluid"> + <div class="navbar-header"> + <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> + <span class="sr-only"><script>document.write(l10nstrings.strToggleNavigation)</script></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <a class="navbar-brand" href="#"><script>document.write(l10nstrings.strProductName + ' - ' + l10nstrings.strAdminConsole)</script></a> + </div> + <div id="navbar" class="navbar-collapse collapse"> + <ul class="nav navbar-nav navbar-right"> + <li><a href="adminSettings.html"><script>document.write(l10nstrings.strSettings)</script></a></li> + </ul> + </div> + </div> + </nav> + <div class="container-fluid"> + <div class="row"> + <div class="col-sm-3 col-md-2 sidebar"> + <ul class="nav nav-sidebar"> + <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script></a></li> + <li><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li> + <li class="active"><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li> + </ul> + </div> + + <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> + <h1 class="page-header"><script>document.write(l10nstrings.strHistory)</script> + <button class="pull-right" id="refreshHistory">refresh</button> + </h1> + <pre id="json-doc">Documents:<br/><textarea rows="10" cols="100"></textarea></pre> + <pre id="json-ex-doc">Expired:<br/><textarea rows="10" cols="100"></textarea></pre> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="../../dist/bootstrap/js/bootstrap.min.js"></script> + <!-- Just to make our placeholder images work. Don't actually copy the next line! --> + <script src="../../dist/bootstrap/assets/js/vendor/holder.min.js"></script> + <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> + <script src="../../dist/bootstrap/assets/js/ie10-viewport-bug-workaround.js"></script> + </body> +</html> diff --git a/loleaflet/dist/admin/adminSettings.html b/loleaflet/dist/admin/adminSettings.html index 3b74b80e..bba47f90 100644 --- a/loleaflet/dist/admin/adminSettings.html +++ b/loleaflet/dist/admin/adminSettings.html @@ -58,6 +58,7 @@ <ul class="nav nav-sidebar"> <li><a href="admin.html"><script>document.write(l10nstrings.strOverview)</script> <span class="sr-only"><script>document.write(l10nstrings.strCurrent)</script></span></a></li> <li><a href="adminAnalytics.html"><script>document.write(l10nstrings.strAnalytics)</script></a></li> + <li><a href="adminHistory.html"><script>document.write(l10nstrings.strHistory)</script></a></li> </ul> <hr /> <div style="position:absolute; bottom:0px"> diff --git a/loleaflet/src/admin/AdminSocketHistory.js b/loleaflet/src/admin/AdminSocketHistory.js new file mode 100644 index 00000000..05f9b063 --- /dev/null +++ b/loleaflet/src/admin/AdminSocketHistory.js @@ -0,0 +1,51 @@ +/* + Socket to be intialized on opening the history page in Admin console +*/ +/* global $ nodejson2html Util AdminSocketBase */ +/* eslint no-unused-vars:0 */ +var AdminSocketHistory = AdminSocketBase.extend({ + constructor: function(host) { + this.base(host); + }, + + refreshHistory: function() { + this.socket.send('history'); + }, + + onSocketOpen: function() { + // Base class' onSocketOpen handles authentication + this.base.call(this); + + var socketHistory = this; + $('#refreshHistory').on('click', function () { + return socketHistory.refreshHistory(); + }); + this.refreshHistory(); + }, + + onSocketMessage: function(e) { + //if (e.data == 'InvalidAuthToken' || e.data == 'NotAuthenticated') { + // this.base.call(this); + // this.refreshHistory(); + //} else { + var jsonObj; + try { + jsonObj = JSON.parse(e.data); + var doc = jsonObj['History']['documents']; + var exdoc = jsonObj['History']['expiredDocuments']; + $('#json-doc').find('textarea').html(JSON.stringify(doc)); + $('#json-ex-doc').find('textarea').html(JSON.stringify(exdoc)); + } catch (e) { + $('document').alert(e.message); + } + }, + + onSocketClose: function() { + + } +}); + +Admin.History = function(host) { + return new AdminSocketHistory(host); +}; + diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index a9039f2b..f5b63b1e 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -108,6 +108,10 @@ void AdminSocketHandler::handleMessage(bool /* fin */, WSOpCode /* code */, if (!result.empty()) sendTextFrame(tokens[0] + ' ' + result); } + else if (tokens[0] == "history") + { + sendTextFrame("{ \"History\": " + model.getAllHistory() + "}"); + } else if (tokens[0] == "version") { // Send LOOL version information diff --git a/wsd/AdminModel.cpp b/wsd/AdminModel.cpp index a838cbf3..3429553f 100644 --- a/wsd/AdminModel.cpp +++ b/wsd/AdminModel.cpp @@ -50,10 +50,80 @@ int Document::expireView(const std::string& sessionId) if (--_activeViews == 0) _end = std::time(nullptr); } + this->takeSnapshot(); return _activeViews; } +std::pair<std::time_t, std::string> Document::getSnapshot() const +{ + std::time_t ct = std::time(nullptr); + std::ostringstream oss; + oss << "{"; + oss << "\"creationTime\"" << ":" << ct << ","; + oss << "\"memoryDirty\"" << ":" << this->getMemoryDirty() << ","; + oss << "\"activeViews\"" << ":" << this->getActiveViews() << ","; + + oss << "\"views\"" << ":["; + std::string separator = ""; + for (auto view : this->getViews()) + { + oss << separator << "\""; + if(view.second.isExpired()) + { + oss << "-"; + } + oss << view.first << "\""; + separator = ","; + } + oss << "],"; + + oss << "\"lastActivity\"" << ":" << this->_lastActivity; + oss << "}"; + return std::make_pair(ct, oss.str()); +} + +const std::string Document::getHistory() const +{ + std::ostringstream oss; + oss << "{"; + oss << "\"docKey\"" << ":\"" << this->_docKey << "\","; + oss << "\"filename\"" << ":\"" << this->getFilename() << "\","; + oss << "\"start\"" << ":" << this->_start << ","; + oss << "\"end\"" << ":" << this->_end << ","; + oss << "\"pid\"" << ":" << this->getPid() << ","; + oss << "\"snapshots\"" << ":["; + std::string separator = ""; + for (auto s : _snapshots) + { + oss << separator << s.second; + separator = ","; + } + oss << "]}"; + return oss.str(); +} + +void Document::takeSnapshot() +{ + auto p = this->getSnapshot(); + auto insPoint = _snapshots.upper_bound(p.first); + _snapshots.insert(insPoint, p); +} + +std::string Document::to_string() const +{ + std::ostringstream oss; + std::string encodedFilename; + Poco::URI::encode(this->getFilename(), " ", encodedFilename); + oss << this->getPid() << ' ' + << encodedFilename << ' ' + << this->getActiveViews() << ' ' + << this->getMemoryDirty() << ' ' + << this->getElapsedTime() << ' ' + << this->getIdleTime() << ' '; + return oss.str(); +} + bool Subscriber::notify(const std::string& message) { // If there is no socket, then return false to @@ -106,6 +176,35 @@ void AdminModel::assertCorrectThread() const assert(sameThread); } +AdminModel::~AdminModel() +{ + Log::debug("History:\n\n" + getAllHistory() + "\n"); + Log::info("AdminModel dtor."); +} + +std::string AdminModel::getAllHistory() const +{ + std::ostringstream oss; + oss << "{ \"documents\" : ["; + std::string separator1 = ""; + for (auto d : _documents) + { + oss << separator1; + oss << d.second.getHistory(); + separator1 = ","; + } + oss << "], \"expiredDocuments\" : ["; + separator1 = ""; + for (auto ed : _expiredDocuments) + { + oss << separator1; + oss << ed.second.getHistory(); + separator1 = ","; + } + oss << "]}"; + return oss.str(); +} + std::string AdminModel::query(const std::string& command) { assertCorrectThread(); @@ -281,6 +380,7 @@ void AdminModel::addDocument(const std::string& docKey, Poco::Process::PID pid, const auto ret = _documents.emplace(docKey, Document(docKey, pid, filename)); ret.first->second.addView(sessionId); + ret.first->second.takeSnapshot(); LOG_DBG("Added admin document [" << docKey << "]."); std::string encodedFilename; @@ -329,11 +429,12 @@ void AdminModel::removeDocument(const std::string& docKey, const std::string& se << sessionId; notify(oss.str()); - // TODO: The idea is to only expire the document and keep the history + // 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. + // to the admin console with views. if (docIt->second.expireView(sessionId) == 0) { + _expiredDocuments.emplace(*docIt); _documents.erase(docIt); } } @@ -355,9 +456,11 @@ void AdminModel::removeDocument(const std::string& docKey) { // Notify the subscribers notify(msg + pair.first); + docIt->second.expireView(pair.first); } LOG_DBG("Removed admin document [" << docKey << "]."); + _expiredDocuments.emplace(*docIt); _documents.erase(docIt); } } @@ -413,14 +516,7 @@ std::string AdminModel::getDocuments() const { if (!it.second.isExpired()) { - std::string encodedFilename; - Poco::URI::encode(it.second.getFilename(), " ", encodedFilename); - oss << it.second.getPid() << ' ' - << encodedFilename << ' ' - << it.second.getActiveViews() << ' ' - << it.second.getMemoryDirty() << ' ' - << it.second.getElapsedTime() << ' ' - << it.second.getIdleTime() << " \n "; + oss << it.second.to_string() << "\n "; } } @@ -436,6 +532,7 @@ void AdminModel::updateLastActivityTime(const std::string& docKey) { if (docIt->second.getIdleTime() >= 10) { + docIt->second.takeSnapshot(); // I would like to keep the idle time docIt->second.updateLastActivityTime(); notify("resetidle " + std::to_string(docIt->second.getPid())); } diff --git a/wsd/AdminModel.hpp b/wsd/AdminModel.hpp index 80f0437b..1b37f1a8 100644 --- a/wsd/AdminModel.hpp +++ b/wsd/AdminModel.hpp @@ -77,6 +77,12 @@ public: bool updateMemoryDirty(int dirty); int getMemoryDirty() const { return _memoryDirty; } + std::pair<std::time_t, std::string> getSnapshot() const; + const std::string getHistory() const; + void takeSnapshot(); + + std::string to_string() const; + private: const std::string _docKey; const Poco::Process::PID _pid; @@ -92,6 +98,7 @@ private: std::time_t _start; std::time_t _lastActivity; std::time_t _end = 0; + std::map<std::time_t,std::string> _snapshots; }; /// An Admin session subscriber. @@ -144,10 +151,7 @@ public: LOG_INF("AdminModel ctor."); } - ~AdminModel() - { - LOG_INF("AdminModel dtor."); - } + ~AdminModel(); /// All methods here must be called from the Admin socket-poll void setThreadOwner(const std::thread::id &id) { _owner = id; } @@ -157,6 +161,7 @@ public: void assertCorrectThread() const; std::string query(const std::string& command); + std::string getAllHistory() const; /// Returns memory consumed by all active loolkit processes unsigned getKitsMemoryUsage(); @@ -200,6 +205,7 @@ private: private: std::map<int, Subscriber> _subscribers; std::map<std::string, Document> _documents; + std::map<std::string, Document> _expiredDocuments; /// The last N total memory Dirty size. std::list<unsigned> _memStats; diff --git a/wsd/protocol.txt b/wsd/protocol.txt index e686430b..b2404a70 100644 --- a/wsd/protocol.txt +++ b/wsd/protocol.txt @@ -490,6 +490,40 @@ documents Queries the server for list of opened documents. See `documents` command in admin -> client section for format of the response message +history + + Queries the server for list of opened and expired documents with their + snapshots. Returns a json object and it looks like: + + { "History" : + { + "documents": [ + { + "filename":"hello-world.odt", + "start":1492104619, + "end":1492104680, + "pid":12302, + "snapshots": [ + { + "creationTime":1492104619, + "memoryDirty":0, + "activeViews":1, + "views":["0008", ...], + "lastActivity":1492104619 + }, + { + ... + } + ] + }, + { + ... + } + ], + "expiredDocuments" : [...] + } + } + total_mem Queries for total memory being consumed by the server in kilobytes. _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits