[Libreoffice-commits] online.git: loolwsd/Admin.hpp loolwsd/Common.hpp loolwsd/LOOLWSD.cpp

Ashod Nakashian ashod.nakashian at collabora.co.uk
Wed Feb 17 15:30:21 UTC 2016


 loolwsd/Admin.hpp   |  262 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 loolwsd/Common.hpp  |    1 
 loolwsd/LOOLWSD.cpp |    5 
 3 files changed, 268 insertions(+)

New commits:
commit 7aeeab46f8edc55a9a53a8ed93e6c8dd2aeab787
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Wed Feb 17 10:27:27 2016 -0500

    loolwsd: Admin manager added with a sample stats command
    
    Change-Id: I7d3c1a5ab573d20d285c1c184cfb88ace17991f8
    Reviewed-on: https://gerrit.libreoffice.org/22430
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>

diff --git a/loolwsd/Admin.hpp b/loolwsd/Admin.hpp
new file mode 100644
index 0000000..858aeb0
--- /dev/null
+++ b/loolwsd/Admin.hpp
@@ -0,0 +1,262 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_ADMIN_HPP
+#define INCLUDED_ADMIN_HPP
+
+#include <cassert>
+#include <condition_variable>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <set>
+
+#include <Poco/Net/WebSocket.h>
+#include <Poco/Buffer.h>
+#include <Poco/Path.h>
+#include <Poco/Process.h>
+#include <Poco/Random.h>
+#include <Poco/StringTokenizer.h>
+#include <Poco/Types.h>
+#include <Poco/Net/HTTPServer.h>
+#include <Poco/Net/HTTPServerParams.h>
+#include <Poco/Net/HTTPServerParams.h>
+#include <Poco/Net/HTTPServerRequest.h>
+#include <Poco/Net/HTTPServerResponse.h>
+
+#include "Common.hpp"
+#include "LOOLProtocol.hpp"
+#include "Util.hpp"
+
+using namespace LOOLProtocol;
+
+using Poco::Exception;
+using Poco::File;
+using Poco::IOException;
+using Poco::Net::HTTPClientSession;
+using Poco::Net::HTTPRequest;
+using Poco::Net::HTTPRequestHandler;
+using Poco::Net::HTTPRequestHandlerFactory;
+using Poco::Net::HTTPResponse;
+using Poco::Net::HTTPServer;
+using Poco::Net::HTTPServerParams;
+using Poco::Net::HTTPServerRequest;
+using Poco::Net::HTTPServerResponse;
+using Poco::Net::ServerSocket;
+using Poco::Net::SocketAddress;
+using Poco::Net::WebSocket;
+using Poco::Net::WebSocketException;
+using Poco::Path;
+using Poco::Process;
+using Poco::Runnable;
+using Poco::StringTokenizer;
+using Poco::Thread;
+using Poco::ThreadPool;
+using Poco::Util::Application;
+using Poco::Util::HelpFormatter;
+using Poco::Util::IncompatibleOptionsException;
+using Poco::Util::MissingOptionException;
+using Poco::Util::Option;
+using Poco::Util::OptionSet;
+using Poco::Util::ServerApplication;
+using Poco::Net::DialogSocket;
+using Poco::FastMutex;
+using Poco::Net::Socket;
+using Poco::ThreadLocal;
+using Poco::Random;
+using Poco::NamedMutex;
+using Poco::ProcessHandle;
+using Poco::URI;
+
+/// Handle admin requests.
+class AdminRequestHandler: public HTTPRequestHandler
+{
+public:
+
+    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+    {
+        assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
+
+        const std::string thread_name = "admin_ws";
+        try
+        {
+#ifdef __linux
+            if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
+                Log::error("Cannot set thread name to " + thread_name + ".");
+#endif
+            Log::debug("Thread [" + thread_name + "] started.");
+
+            auto ws = std::make_shared<WebSocket>(request, response);
+            const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000);
+            int flags = 0;
+            int n = 0;
+            ws->setReceiveTimeout(0);
+            do
+            {
+                char buffer[200000]; //FIXME: Dynamic?
+
+                if (ws->poll(waitTime, Socket::SELECT_READ))
+                {
+                    n = ws->receiveFrame(buffer, sizeof(buffer), flags);
+
+                    if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
+                    {
+                        // Echo back the ping payload as pong.
+                        // Technically, we should send back a PONG control frame.
+                        // However Firefox (probably) or Node.js (possibly) doesn't
+                        // like that and closes the socket when we do.
+                        // Echoing the payload as a normal frame works with Firefox.
+                        ws->sendFrame(buffer, n /*, WebSocket::FRAME_OP_PONG*/);
+                    }
+                    else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
+                    {
+                        // In case we do send pings in the future.
+                    }
+                    else if (n <= 0)
+                    {
+                        // Connection closed.
+                        Log::warn() << "Received " << n
+                                    << " bytes. Connection closed. Flags: "
+                                    << std::hex << flags << Log::end;
+                        break;
+                    }
+                    else
+                    {
+                        assert(n > 0);
+                        const std::string firstLine = getFirstLine(buffer, n);
+                        StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+
+                        if (firstLine == "eof")
+                        {
+                            Log::info("Received EOF. Finishing.");
+                            break;
+                        }
+
+                        if (tokens.count() == 1 && tokens[0] == "stats")
+                        {
+                            //TODO: Collect stats and reply back to admin.
+                            // We need to ask Broker to give us some numbers on docs/clients/etc.
+                            // But we can also collect some memory info using system calls.
+
+                            std::string response;
+
+                            const auto cmd = "pstree -a -c -h -A -p " + std::to_string(getpid());
+                            FILE* fp = popen(cmd.c_str(), "r");
+                            if (fp == nullptr)
+                            {
+                                response = "error: failed to collect stats.";
+                                ws->sendFrame(response.data(), response.size());
+                                continue;
+                            }
+
+                            char buffer[1024];
+                            while (fgets(buffer, sizeof(buffer)-1, fp) != nullptr)
+                            {
+                                response += buffer;
+                                response += "</ BR>\n";
+                            }
+
+                            pclose(fp);
+
+                            ws->sendFrame(response.data(), response.size());
+                        }
+                    }
+                }
+            }
+            while (!TerminationFlag &&
+                   (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
+            Log::debug() << "Finishing AdminProcessor. TerminationFlag: " << TerminationFlag
+                         << ", payload size: " << n
+                         << ", flags: " << std::hex << flags << Log::end;
+        }
+        catch (const WebSocketException& exc)
+        {
+            Log::error("AdminRequestHandler::handleRequest(), WebSocketException: " + exc.message());
+            switch (exc.code())
+            {
+            case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
+                response.set("Sec-WebSocket-Version", WebSocket::WEBSOCKET_VERSION);
+                // fallthrough
+            case WebSocket::WS_ERR_NO_HANDSHAKE:
+            case WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
+            case WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
+                response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
+                response.setContentLength(0);
+                response.send();
+                break;
+            }
+        }
+        catch (const std::exception& exc)
+        {
+            Log::error(std::string("Exception: ") + exc.what());
+        }
+
+        Log::debug("Thread [" + thread_name + "] finished.");
+    }
+};
+
+//TODO: Move to common header.
+class AdminRequestHandlerFactory: public HTTPRequestHandlerFactory
+{
+public:
+    HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
+    {
+        auto logger = Log::info();
+        logger << "Request from " << request.clientAddress().toString() << ": "
+               << request.getMethod() << " " << request.getURI() << " "
+               << request.getVersion();
+
+        for (HTTPServerRequest::ConstIterator it = request.begin(); it != request.end(); ++it)
+        {
+            logger << " / " << it->first << ": " << it->second;
+        }
+
+        logger << Log::end;
+        return new AdminRequestHandler();
+    }
+};
+
+/// An admin command processor.
+class Admin : public Poco::Runnable
+{
+public:
+    Admin(const int brokerPipe) :
+        _srv(new AdminRequestHandlerFactory(), ServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams)
+    {
+        Admin::BrokerPipe = brokerPipe;
+    }
+
+    ~Admin()
+    {
+        Log::info("~Admin dtor.");
+        _srv.stop();
+    }
+
+    static int getBrokerPid() { return Admin::BrokerPipe; }
+
+    void run() override
+    {
+        Log::info("Listening on Admin port " + std::to_string(ADMIN_PORT_NUMBER));
+
+        // Start a server listening on the admin port.
+        _srv.start();
+    }
+
+private:
+    HTTPServer _srv;
+    static int BrokerPipe;
+};
+
+//TODO: Clean up with something more elegant.
+int Admin::BrokerPipe;
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/Common.hpp b/loolwsd/Common.hpp
index daec8cb..40251d3 100644
--- a/loolwsd/Common.hpp
+++ b/loolwsd/Common.hpp
@@ -18,6 +18,7 @@ constexpr int MAX_SESSIONS = 1024;
 
 constexpr int DEFAULT_CLIENT_PORT_NUMBER = 9980;
 constexpr int MASTER_PORT_NUMBER = 9981;
+constexpr int ADMIN_PORT_NUMBER = 9989;
 constexpr int INTERVAL_PROBES = 10;
 constexpr int MAINTENANCE_INTERVAL = 1;
 constexpr int CHILD_TIMEOUT_SECS = 10;
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index b7cb356..03ca11e 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -110,6 +110,7 @@ DEALINGS IN THE SOFTWARE.
 #include "LOOLWSD.hpp"
 #include "QueueHandler.hpp"
 #include "Util.hpp"
+#include "Admin.hpp"
 
 using namespace LOOLProtocol;
 
@@ -989,6 +990,10 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
 
     srv2.start();
 
+    // Start the Admin manager.
+    Admin admin(BrokerWritePipe);
+    threadPool.start(admin);
+
     if ( (BrokerWritePipe = open(pipeLoolwsd.c_str(), O_WRONLY) ) < 0 )
     {
         Log::error("Error: failed to open pipe [" + pipeLoolwsd + "] write only.");


More information about the Libreoffice-commits mailing list