From c4a1f194e79a7834a54fdbf63d73c33e434b4825 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sat, 4 Feb 2023 12:43:51 +0100 Subject: Added stats.html --- connectionregistry.cpp | 5 ++ connectionregistry.h | 2 + debian/changelog | 6 +++ html/stats.html | 35 ++++++++++++++ html/stats.js | 96 +++++++++++++++++++++++++++++++++++++++ storage.cpp | 24 +++++++++- storage.h | 7 ++- tests/test-connectionregistry.cpp | 23 ++++++++++ tests/test-storage.cpp | 28 +++++++++--- whiteboard.cpp | 8 ++++ 10 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 html/stats.html create mode 100644 html/stats.js diff --git a/connectionregistry.cpp b/connectionregistry.cpp index 11a538b..412472d 100644 --- a/connectionregistry.cpp +++ b/connectionregistry.cpp @@ -81,6 +81,11 @@ void ConnectionRegistry::dump() const } } +size_t ConnectionRegistry::number_of_connections() const +{ + return m_connections.size(); +} + ConnectionRegistry::RegistryGuard::RegistryGuard(ConnectionRegistry& registry, ConnectionRegistry::connection c): m_registry{registry}, m_connection{c} diff --git a/connectionregistry.h b/connectionregistry.h index 155ab31..25bd3b6 100644 --- a/connectionregistry.h +++ b/connectionregistry.h @@ -27,6 +27,8 @@ public: void dump() const; + size_t number_of_connections() const; + private: // map connection to id std::unordered_map m_connections; diff --git a/debian/changelog b/debian/changelog index 688c98f..7c0064f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +whiteboard (1.6) UNRELEASED; urgency=medium + + * Added stats.html + + -- Roland Reichwein Tue, 31 Jan 2023 18:22:42 +0100 + whiteboard (1.5) unstable; urgency=medium * Move from FCGI to websocket interface diff --git a/html/stats.html b/html/stats.html new file mode 100644 index 0000000..3f7e141 --- /dev/null +++ b/html/stats.html @@ -0,0 +1,35 @@ + + + + + + + Reichwein Whiteboard + + + + + + +
+

Whiteboard

+

Statistics

+ + + + + +
Active Connections:
Number of Documents:
Database Size (gross):Bytes
Database Size (net):Bytes
+
+ Starting up... + +
+
+ Reichwein.IT Whiteboard by https://www.reichwein.it
+
+ + + + diff --git a/html/stats.js b/html/stats.js new file mode 100644 index 0000000..89c674a --- /dev/null +++ b/html/stats.js @@ -0,0 +1,96 @@ +// started on main page load +function init() { + init_stats(); +} + +function set_status(message) +{ + if (message == "") { + document.getElementById("status").textContent = message; + document.getElementById("status").style.display = 'inline'; + } else { + document.getElementById("status").textContent = ""; + document.getElementById("status").style.display = 'none'; + } +} + +var websocket; + +// +// Callbacks for websocket data of different types +// + +function on_stats(numberofdocuments, numberofconnections, dbsizegross, dbsizenet) +{ + document.getElementById("numberofdocuments").textContent = numberofdocuments; + document.getElementById("numberofconnections").textContent = numberofconnections; + document.getElementById("dbsizegross").textContent = dbsizegross; + document.getElementById("dbsizenet").textContent = dbsizenet; +} + +function on_version(version) +{ + document.getElementById("version").textContent = version; +} + +function on_message(e) { + var parser = new DOMParser(); + var xmlDocument = parser.parseFromString(e.data, "text/xml"); + + var type = xmlDocument.getElementsByTagName("type")[0].textContent; + + if (type == "stats") { + on_stats(xmlDocument.getElementsByTagName("numberofdocuments")[0].textContent, + xmlDocument.getElementsByTagName("numberofconnections")[0].textContent, + xmlDocument.getElementsByTagName("dbsizegross")[0].textContent, + xmlDocument.getElementsByTagName("dbsizenet")[0].textContent); + } else if (type == "version") { + on_version(xmlDocument.getElementsByTagName("version")[0].textContent); + } else if (type == "error") { + alert(xmlDocument.getElementsByTagName("message")[0].textContent); + } else { + alert("Unhandled message type: " + e.data + "|" + type); + } +} + +function connect_websocket() { + document.getElementById("reconnect").style.display = 'none'; + set_status("Connecting..."); + var newlocation = location.origin + location.pathname; + newlocation = newlocation.replace(/^http/, 'ws'); + newlocation = newlocation.replace(/stats.html$/, ''); + if (newlocation.slice(-1) != "/") + newlocation += "/"; + newlocation += "websocket"; + + websocket = new WebSocket(newlocation); + + websocket.onmessage = function(e) { on_message(e); }; + + websocket.onopen = function(e) { + websocket.send("getversion"); + websocket.send("getstats"); + set_status(""); // ok + }; + + websocket.onclose = function(e) { + alert("Server connection closed."); + document.getElementById("reconnect").style.display = 'inline'; + }; + + websocket.onerror = function(e) { + alert("Error: Server connection closed."); + document.getElementById("reconnect").style.display = 'inline'; + }; +} + +// button in html +function on_reconnect_click() { + connect_websocket(); +} + +function init_stats() { + set_status("Loading..."); + connect_websocket(); +} + diff --git a/storage.cpp b/storage.cpp index f7f15b1..f7b8b5d 100644 --- a/storage.cpp +++ b/storage.cpp @@ -27,7 +27,9 @@ Storage::Storage(const Config& config): m_stmt_setDocument_new(m_db, "INSERT INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, ?)"), m_stmt_setRevision(m_db, "UPDATE documents SET rev = ? WHERE id = ?"), m_stmt_setCursorPos(m_db, "UPDATE documents SET cursorpos = ? WHERE id = ?"), - m_stmt_setRow(m_db, "INSERT OR REPLACE INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, ?)") + m_stmt_setRow(m_db, "INSERT OR REPLACE INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, ?)"), + m_stmt_getDbSizeGross(m_db, "SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()"), + m_stmt_getDbSizeNet(m_db, "SELECT (page_count - freelist_count) * page_size as size FROM pragma_page_count(), pragma_freelist_count(), pragma_page_size()") { CompiledSQL::Guard g{m_stmt_create}; m_stmt_create.execute(); @@ -46,6 +48,24 @@ uint64_t Storage::getNumberOfDocuments() return m_stmt_getNumberOfDocuments.getColumn(0); } +uint64_t Storage::dbsize_gross() +{ + CompiledSQL::Guard g{m_stmt_getDbSizeGross}; + if (!m_stmt_getDbSizeGross.execute()) + throw std::runtime_error("DB size count (gross) not possible"); + + return m_stmt_getDbSizeGross.getColumn(0); +} + +uint64_t Storage::dbsize_net() +{ + CompiledSQL::Guard g{m_stmt_getDbSizeNet}; + if (!m_stmt_getDbSizeNet.execute()) + throw std::runtime_error("DB size count (net) not possible"); + + return m_stmt_getDbSizeNet.getColumn(0); +} + namespace { uint64_t unixepoch() { @@ -169,7 +189,7 @@ void Storage::setRow(const std::string& id, const std::string& document, int rev throw std::runtime_error("Unable to insert row with id "s + id); } -uint32_t Storage::checksum32(const std::string& s) +uint32_t checksum32(const std::string& s) { uint32_t result{0}; for (unsigned int i = 0; i < s.size(); i++) { diff --git a/storage.h b/storage.h index fe8d060..f997657 100644 --- a/storage.h +++ b/storage.h @@ -16,6 +16,8 @@ public: ~Storage(); uint64_t getNumberOfDocuments(); + uint64_t dbsize_gross(); + uint64_t dbsize_net(); bool exists(const std::string& id); std::string getDocument(const std::string& id); @@ -31,7 +33,6 @@ public: void cleanup(); std::string generate_id(); - uint32_t checksum32(const std::string& s); private: SQLite::Database m_db; @@ -52,5 +53,9 @@ private: CompiledSQL m_stmt_setRevision; CompiledSQL m_stmt_setCursorPos; CompiledSQL m_stmt_setRow; + CompiledSQL m_stmt_getDbSizeGross; + CompiledSQL m_stmt_getDbSizeNet; }; +uint32_t checksum32(const std::string& s); + diff --git a/tests/test-connectionregistry.cpp b/tests/test-connectionregistry.cpp index 1f68dd9..282f397 100644 --- a/tests/test-connectionregistry.cpp +++ b/tests/test-connectionregistry.cpp @@ -142,3 +142,26 @@ TEST_F(ConnectionRegistryTest, test_guard) EXPECT_THROW(cr.delConnection(c), std::exception); } +TEST_F(ConnectionRegistryTest, number_of_connections) +{ + boost::asio::io_context ioc{1}; + + boost::asio::ip::tcp::socket ts0{ioc}; + ConnectionRegistry::connection c0 {std::make_shared(std::move(ts0))}; + + boost::asio::ip::tcp::socket ts1{ioc}; + ConnectionRegistry::connection c1 {std::make_shared(std::move(ts1))}; + + ConnectionRegistry cr{}; + + EXPECT_EQ(cr.number_of_connections(), 0); + cr.addConnection(c0); + EXPECT_EQ(cr.number_of_connections(), 1); + cr.addConnection(c1); + EXPECT_EQ(cr.number_of_connections(), 2); + cr.delConnection(c0); + EXPECT_EQ(cr.number_of_connections(), 1); + cr.delConnection(c1); + EXPECT_EQ(cr.number_of_connections(), 0); +} + diff --git a/tests/test-storage.cpp b/tests/test-storage.cpp index d8259e1..51a6058 100644 --- a/tests/test-storage.cpp +++ b/tests/test-storage.cpp @@ -227,14 +227,28 @@ TEST_F(StorageTest, generate_id) } TEST_F(StorageTest, checksum32) +{ + EXPECT_EQ(checksum32(""), 0); + EXPECT_EQ(checksum32("0"), 48); + EXPECT_EQ(checksum32("\x00"), 0); + EXPECT_EQ(checksum32("123"), 1073741862); + EXPECT_EQ(checksum32("a"), 97); + EXPECT_EQ(checksum32("ab"), 82); + EXPECT_EQ(checksum32("abc"), 1073741898); +} + +TEST_F(StorageTest, db_size) { Storage storage(*m_config); - EXPECT_EQ(storage.checksum32(""), 0); - EXPECT_EQ(storage.checksum32("0"), 48); - EXPECT_EQ(storage.checksum32("\x00"), 0); - EXPECT_EQ(storage.checksum32("123"), 1073741862); - EXPECT_EQ(storage.checksum32("a"), 97); - EXPECT_EQ(storage.checksum32("ab"), 82); - EXPECT_EQ(storage.checksum32("abc"), 1073741898); + auto dbsize_gross{storage.dbsize_gross()}; + auto dbsize_net{storage.dbsize_net()}; + + EXPECT_LE(0, storage.dbsize_net()); + EXPECT_LE(storage.dbsize_net(), storage.dbsize_gross()); + + storage.setDocument("0", "xyz"); + + EXPECT_LE(dbsize_net, storage.dbsize_net()); + EXPECT_LE(dbsize_gross, storage.dbsize_gross()); } diff --git a/whiteboard.cpp b/whiteboard.cpp index 8736726..ce196f4 100644 --- a/whiteboard.cpp +++ b/whiteboard.cpp @@ -233,6 +233,14 @@ std::string Whiteboard::handle_request(Whiteboard::connection& c, const std::str {"type", "version"}, {"version", WHITEBOARD_VERSION } }); + } else if (command == "getstats") { + return make_xml({ + {"type", "stats" }, + {"dbsizegross", std::to_string(m_storage->dbsize_gross()) }, + {"dbsizenet", std::to_string(m_storage->dbsize_net()) }, + {"numberofdocuments", std::to_string(m_storage->getNumberOfDocuments()) }, + {"numberofconnections", std::to_string(m_registry.number_of_connections()) }, + }); } else { throw std::runtime_error("Bad command: "s + command); } -- cgit v1.2.3