Makefile.am | 2 test/helpers.hpp | 5 + tools/Replay.hpp | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/Stress.cpp | 198 +------------------------------------------ wsd/LOOLWSD.cpp | 48 ++++++++++ wsd/LOOLWSD.hpp | 1 wsd/TraceFile.hpp | 6 + 7 files changed, 309 insertions(+), 196 deletions(-)
New commits: commit 6dd581f63532c81375196b2e85e0dd244480d2b7 Author: Jan Holesovsky <ke...@collabora.com> Date: Tue Feb 7 21:00:23 2017 +0100 fuzzer; Added --fuzz param that takes a looltrace file as input. To perform one fuzzing iteration, use something like: ./loolwsd_fuzzer --config-file=loolwsd.xml --o:lo_template_path="/opt/libreoffice/instdir" --o:storage.filesystem[@allow]=true --fuzz=/tmp/looltrace Change-Id: I27210d55a65f75e7d51e05c2f5f999acb758c4b1 diff --git a/Makefile.am b/Makefile.am index e4a2af1..651057b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,7 +32,7 @@ AM_LDFLAGS = -pthread -Wl,-E loolforkit_LDFLAGS = -pthread -Wl,-E,-rpath,/snap/loolwsd/current/usr/lib loolmount_LDFLAGS = -pthread -Wl,-E,-rpath,/snap/loolwsd/current/usr/lib -loolwsd_fuzzer_CPPFLAGS = -DKIT_IN_PROCESS=1 $(AM_CPPFLAGS) +loolwsd_fuzzer_CPPFLAGS = -DKIT_IN_PROCESS=1 -DFUZZER=1 -DTDOC=\"$(abs_top_srcdir)/test/data\" $(AM_CPPFLAGS) AM_ETAGSFLAGS = --c++-kinds=+p --fields=+iaS --extra=+q -R --totals=yes * AM_CTAGSFLAGS = $(AM_ETAGSFLAGS) diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 5d7dd4e..12f91c0 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -121,6 +121,10 @@ #include <Kit.hpp> #endif +#ifdef FUZZER +#include <tools/Replay.hpp> +#endif + #include "common/SigUtil.hpp" using namespace LOOLProtocol; @@ -152,6 +156,7 @@ using Poco::ProcessHandle; using Poco::StreamCopier; using Poco::StringTokenizer; using Poco::TemporaryFile; +using Poco::Thread; using Poco::ThreadPool; using Poco::URI; using Poco::Util::Application; @@ -1697,6 +1702,9 @@ std::atomic<int> LOOLWSD::ForKitWritePipe(-1); std::atomic<int> LOOLWSD::ForKitProcId(-1); bool LOOLWSD::NoCapsForKit = false; #endif +#ifdef FUZZER +std::string LOOLWSD::FuzzFileName = ""; +#endif std::string LOOLWSD::Cache = LOOLWSD_CACHEDIR; std::string LOOLWSD::SysTemplate; std::string LOOLWSD::LoTemplate; @@ -2049,7 +2057,7 @@ void LOOLWSD::defineOptions(OptionSet& optionSet) " must not be " + std::to_string(MasterPortNumber) + ".") .required(false) .repeatable(false) - .argument("port number")); + .argument("port_number")); optionSet.addOption(Option("disable-ssl", "", "Disable SSL security layer.") .required(false) @@ -2080,6 +2088,13 @@ void LOOLWSD::defineOptions(OptionSet& optionSet) .repeatable(false) .argument("seconds")); #endif + +#ifdef FUZZER + optionSet.addOption(Option("fuzz", "", "Read input from the specified file for fuzzing.") + .required(false) + .repeatable(false) + .argument("trace_file_name")); +#endif } void LOOLWSD::handleOption(const std::string& optionName, @@ -2125,6 +2140,11 @@ void LOOLWSD::handleOption(const std::string& optionName, if (masterPort) MasterPortNumber = std::stoi(masterPort); #endif + +#ifdef FUZZER + if (optionName == "fuzz") + FuzzFileName = value; +#endif } void LOOLWSD::displayHelp() @@ -2282,6 +2302,10 @@ bool LOOLWSD::createForKit() #endif } +#ifdef FUZZER +std::mutex Connection::Mutex; +#endif + int LOOLWSD::main(const std::vector<std::string>& /*args*/) { SigUtil::setFatalSignals(); @@ -2432,7 +2456,27 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/) // Make sure we have sufficient reserves. if (prespawnChildren()) { - // Nothing more to do this round. + // Nothing more to do this round, unless we are fuzzing +#if FUZZER + if (!FuzzFileName.empty()) + { + std::unique_ptr<Replay> replay(new Replay( +#if ENABLE_SSL + "https://127.0.0.1:" + std::to_string(ClientPortNumber), +#else + "http://127.0.0.1:" + std::to_string(ClientPortNumber), +#endif + FuzzFileName)); + + std::unique_ptr<Thread> replayThread(new Thread()); + replayThread->start(*replay); + + // block until the replay finishes + replayThread->join(); + + TerminationFlag = true; + } +#endif } else if (!std::getenv("LOOL_NO_AUTOSAVE") && std::chrono::duration_cast<std::chrono::seconds> diff --git a/wsd/LOOLWSD.hpp b/wsd/LOOLWSD.hpp index ab0b479..7438c80 100644 --- a/wsd/LOOLWSD.hpp +++ b/wsd/LOOLWSD.hpp @@ -38,6 +38,7 @@ public: static bool NoCapsForKit; static std::atomic<int> ForKitWritePipe; static std::atomic<int> ForKitProcId; + static std::string FuzzFileName; static std::string Cache; static std::string ConfigFile; static std::string SysTemplate; commit 964ae25ccf038e92454ead354ee8484d493b738a Author: Jan Holesovsky <ke...@collabora.com> Date: Tue Feb 7 17:16:59 2017 +0100 fuzzer: Factor out the replay functionality to a separate file. Change-Id: Ief946b1703ef1ca0b17de3467dce66b4c3da2601 diff --git a/test/helpers.hpp b/test/helpers.hpp index cc8205e..5b9b498 100644 --- a/test/helpers.hpp +++ b/test/helpers.hpp @@ -7,6 +7,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef INCLUDED_TEST_HELPERS_HPP +#define INCLUDED_TEST_HELPERS_HPP + #include "config.h" #include <algorithm> @@ -570,4 +573,6 @@ inline void sendText(std::shared_ptr<LOOLWebSocket>& socket, const std::string& } +#endif + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/Replay.hpp b/tools/Replay.hpp new file mode 100644 index 0000000..d5ed00b --- /dev/null +++ b/tools/Replay.hpp @@ -0,0 +1,245 @@ +/* -*- 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_REPLAY_HPP +#define INCLUDED_REPLAY_HPP + +#include <Poco/Net/HTTPRequest.h> +#include <Poco/Net/HTTPResponse.h> + +#include <LOOLWebSocket.hpp> + +#include "TraceFile.hpp" +#include <test/helpers.hpp> + +/// Connection class with WSD. +class Connection +{ +public: + static + std::shared_ptr<Connection> create(const std::string& serverURI, const std::string& documentURL, const std::string& sessionId) + { + Poco::URI uri(serverURI); + + std::unique_lock<std::mutex> lock(Mutex); + + // Load a document and get its status. + std::cout << "NewSession [" << sessionId << "]: " << uri.toString() << "... "; + + std::string encodedUri; + Poco::URI::encode(documentURL, ":/?", encodedUri); + Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, "/lool/" + encodedUri + "/ws"); + Poco::Net::HTTPResponse response; + auto ws = helpers::connectLOKit(uri, request, response, sessionId + ' '); + std::cout << "Connected.\n"; + return std::shared_ptr<Connection>(new Connection(documentURL, sessionId, ws)); + } + + const std::string& getName() const { return _name; } + std::shared_ptr<LOOLWebSocket> getWS() const { return _ws; }; + + /// Send a command to the server. + void send(const std::string& data) const + { + helpers::sendTextFrame(_ws, data, _name); + } + + /// Poll socket until expected prefix is fetched, or timeout. + std::vector<char> recv(const std::string& prefix) + { + return helpers::getResponseMessage(_ws, prefix, _name); + } + + /// Request loading the document and wait for completion. + bool load() + { + send("load url=" + _documentURL); + return helpers::isDocumentLoaded(_ws, _name); + } + +private: + Connection(const std::string& documentURL, const std::string& sessionId, std::shared_ptr<LOOLWebSocket>& ws) : + _documentURL(documentURL), + _sessionId(sessionId), + _name(sessionId + ' '), + _ws(ws) + { + } + +private: + const std::string _documentURL; + const std::string _sessionId; + const std::string _name; + std::shared_ptr<LOOLWebSocket> _ws; + static std::mutex Mutex; +}; + +/// Main thread class to replay a trace file. +class Replay : public Poco::Runnable +{ +public: + + Replay(const std::string& serverUri, const std::string& uri, bool ignoreTiming = true) : + _serverUri(serverUri), + _uri(uri), + _ignoreTiming(ignoreTiming) + { + } + + void run() override + { + try + { + replay(); + } + catch (const Poco::Exception &e) + { + std::cout << "Error: " << e.name() << ' ' << e.message() << std::endl; + } + catch (const std::exception &e) + { + std::cout << "Error: " << e.what() << std::endl; + } + } + +protected: + + void replay() + { + TraceFileReader traceFile(_uri); + + auto epochFile(traceFile.getEpoch()); + auto epochCurrent(std::chrono::steady_clock::now()); + + for (;;) + { + const auto rec = traceFile.getNextRecord(); + if (rec.Dir == TraceFileRecord::Direction::Invalid) + { + // End of trace file. + break; + } + + const auto deltaCurrent = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - epochCurrent).count(); + const auto deltaFile = rec.TimestampNs - epochFile; + const auto delay = (_ignoreTiming ? 0 : deltaFile - deltaCurrent); + if (delay > 0) + { + if (delay > 1e6) + { + std::cout << "Sleeping for " << delay / 1000 << " ms.\n"; + } + + std::this_thread::sleep_for(std::chrono::microseconds(delay)); + } + + if (rec.Dir == TraceFileRecord::Direction::Event) + { + // Meta info about about an event. + static const std::string NewSession("NewSession: "); + static const std::string EndSession("EndSession: "); + + if (rec.Payload.find(NewSession) == 0) + { + const auto uriOrig = rec.Payload.substr(NewSession.size()); + std::string uri; + Poco::URI::decode(uriOrig, uri); + auto it = _sessions.find(uri); + if (it != _sessions.end()) + { + // Add a new session. + if (it->second.find(rec.SessionId) != it->second.end()) + { + std::cout << "ERROR: session [" << rec.SessionId << "] already exists on doc [" << uri << "]\n"; + } + else + { + it->second.emplace(rec.SessionId, Connection::create(_serverUri, uri, rec.SessionId)); + } + } + else + { + std::cout << "New Document: " << uri << "\n"; + _childToDoc.emplace(rec.Pid, uri); + _sessions[uri].emplace(rec.SessionId, Connection::create(_serverUri, uri, rec.SessionId)); + } + } + else if (rec.Payload.find(EndSession) == 0) + { + const auto uriOrig = rec.Payload.substr(EndSession.size()); + std::string uri; + Poco::URI::decode(uriOrig, uri); + auto it = _sessions.find(uri); + if (it != _sessions.end()) + { + std::cout << "EndSession [" << rec.SessionId << "]: " << uri << "\n"; + + it->second.erase(rec.SessionId); + if (it->second.empty()) + { + std::cout << "End Doc [" << uri << "].\n"; + _sessions.erase(it); + _childToDoc.erase(rec.Pid); + } + } + else + { + std::cout << "ERROR: Doc [" << uri << "] does not exist.\n"; + } + } + } + else if (rec.Dir == TraceFileRecord::Direction::Incoming) + { + auto docIt = _childToDoc.find(rec.Pid); + if (docIt != _childToDoc.end()) + { + const auto& uri = docIt->second; + auto it = _sessions.find(uri); + if (it != _sessions.end()) + { + const auto sessionIt = it->second.find(rec.SessionId); + if (sessionIt != it->second.end()) + { + // Send the command. + sessionIt->second->send(rec.Payload); + } + } + else + { + std::cout << "ERROR: Doc [" << uri << "] does not exist.\n"; + } + } + else + { + std::cout << "ERROR: Unknown PID [" << rec.Pid << "] maps to no active document.\n"; + } + } + + epochCurrent = std::chrono::steady_clock::now(); + epochFile = rec.TimestampNs; + } + } + +protected: + const std::string _serverUri; + const std::string _uri; + + /// Should we ignore timing that is saved in the trace file? + bool _ignoreTiming; + + /// LOK child process PID to Doc URI map. + std::map<unsigned, std::string> _childToDoc; + + /// Doc URI to _sessions map. _sessions are maps of SessionID to Connection. + std::map<std::string, std::map<std::string, std::shared_ptr<Connection>>> _sessions; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/tools/Stress.cpp b/tools/Stress.cpp index a7e90d1..33a2243 100644 --- a/tools/Stress.cpp +++ b/tools/Stress.cpp @@ -27,6 +27,7 @@ #include <Poco/Util/Option.h> #include <Poco/Util/OptionSet.h> +#include "Replay.hpp" #include "TraceFile.hpp" #include "test/helpers.hpp" @@ -78,68 +79,6 @@ long percentile(std::vector<long>& v, const double percentile) return v[k - 1] + d * (v[k] - v[k - 1]); } -/// Connection class with WSD. -class Connection -{ -public: - static - std::shared_ptr<Connection> create(const std::string& serverURI, const std::string& documentURL, const std::string& sessionId) - { - Poco::URI uri(serverURI); - - std::unique_lock<std::mutex> lock(Mutex); - - // Load a document and get its status. - std::cout << "NewSession [" << sessionId << "]: " << uri.toString() << "... "; - - std::string encodedUri; - Poco::URI::encode(documentURL, ":/?", encodedUri); - Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, "/lool/" + encodedUri + "/ws"); - Poco::Net::HTTPResponse response; - auto ws = helpers::connectLOKit(uri, request, response, sessionId + ' '); - std::cout << "Connected.\n"; - return std::shared_ptr<Connection>(new Connection(documentURL, sessionId, ws)); - } - - const std::string& getName() const { return _name; } - std::shared_ptr<LOOLWebSocket> getWS() const { return _ws; }; - - /// Send a command to the server. - void send(const std::string& data) const - { - helpers::sendTextFrame(_ws, data, _name); - } - - /// Poll socket until expected prefix is fetched, or timeout. - std::vector<char> recv(const std::string& prefix) - { - return helpers::getResponseMessage(_ws, prefix, _name); - } - - /// Request loading the document and wait for completion. - bool load() - { - send("load url=" + _documentURL); - return helpers::isDocumentLoaded(_ws, _name); - } - -private: - Connection(const std::string& documentURL, const std::string& sessionId, std::shared_ptr<LOOLWebSocket>& ws) : - _documentURL(documentURL), - _sessionId(sessionId), - _name(sessionId + ' '), - _ws(ws) - { - } - -private: - const std::string _documentURL; - const std::string _sessionId; - const std::string _name; - std::shared_ptr<LOOLWebSocket> _ws; - static std::mutex Mutex; -}; - std::mutex Connection::Mutex; //static constexpr auto FIRST_ROW_TILES = "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840"; @@ -147,13 +86,11 @@ static constexpr auto FIRST_PAGE_TILES = "tilecombine part=0 width=256 height=25 static constexpr auto FIRST_PAGE_TILE_COUNT = 16; /// Main thread class to replay a trace file. -class Worker: public Runnable +class Worker: public Replay { public: - Worker(Stress& app, const std::string& uri) : - _app(app), - _uri(uri) + Worker(const std::string& serverUri, const std::string& uri) : Replay(serverUri, uri, Stress::NoDelay) { } @@ -256,7 +193,7 @@ private: static std::atomic<unsigned> SessionId; const auto sessionId = ++SessionId; - auto connection = Connection::create(_app._serverURI, _uri, std::to_string(sessionId)); + auto connection = Connection::create(_serverUri, _uri, std::to_string(sessionId)); connection->load(); @@ -268,132 +205,7 @@ private: } } - void replay() - { - TraceFileReader traceFile(_uri); - - auto epochFile(traceFile.getEpoch()); - auto epochCurrent(std::chrono::steady_clock::now()); - - for (;;) - { - const auto rec = traceFile.getNextRecord(); - if (rec.Dir == TraceFileRecord::Direction::Invalid) - { - // End of trace file. - break; - } - - const auto deltaCurrent = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - epochCurrent).count(); - const auto deltaFile = rec.TimestampNs - epochFile; - const auto delay = (Stress::NoDelay ? 0 : deltaFile - deltaCurrent); - if (delay > 0) - { - if (delay > 1e6) - { - std::cout << "Sleeping for " << delay / 1000 << " ms.\n"; - } - - std::this_thread::sleep_for(std::chrono::microseconds(delay)); - } - - if (rec.Dir == TraceFileRecord::Direction::Event) - { - // Meta info about about an event. - static const std::string NewSession("NewSession: "); - static const std::string EndSession("EndSession: "); - - if (rec.Payload.find(NewSession) == 0) - { - const auto uriOrig = rec.Payload.substr(NewSession.size()); - std::string uri; - Poco::URI::decode(uriOrig, uri); - auto it = _sessions.find(uri); - if (it != _sessions.end()) - { - // Add a new session. - if (it->second.find(rec.SessionId) != it->second.end()) - { - std::cout << "ERROR: session [" << rec.SessionId << "] already exists on doc [" << uri << "]\n"; - } - else - { - it->second.emplace(rec.SessionId, Connection::create(_app._serverURI, uri, rec.SessionId)); - } - } - else - { - std::cout << "New Document: " << uri << "\n"; - _childToDoc.emplace(rec.Pid, uri); - _sessions[uri].emplace(rec.SessionId, Connection::create(_app._serverURI, uri, rec.SessionId)); - } - } - else if (rec.Payload.find(EndSession) == 0) - { - const auto uriOrig = rec.Payload.substr(EndSession.size()); - std::string uri; - Poco::URI::decode(uriOrig, uri); - auto it = _sessions.find(uri); - if (it != _sessions.end()) - { - std::cout << "EndSession [" << rec.SessionId << "]: " << uri << "\n"; - - it->second.erase(rec.SessionId); - if (it->second.empty()) - { - std::cout << "End Doc [" << uri << "].\n"; - _sessions.erase(it); - _childToDoc.erase(rec.Pid); - } - } - else - { - std::cout << "ERROR: Doc [" << uri << "] does not exist.\n"; - } - } - } - else if (rec.Dir == TraceFileRecord::Direction::Incoming) - { - auto docIt = _childToDoc.find(rec.Pid); - if (docIt != _childToDoc.end()) - { - const auto& uri = docIt->second; - auto it = _sessions.find(uri); - if (it != _sessions.end()) - { - const auto sessionIt = it->second.find(rec.SessionId); - if (sessionIt != it->second.end()) - { - // Send the command. - sessionIt->second->send(rec.Payload); - } - } - else - { - std::cout << "ERROR: Doc [" << uri << "] does not exist.\n"; - } - } - else - { - std::cout << "ERROR: Unknown PID [" << rec.Pid << "] maps to no active document.\n"; - } - } - - epochCurrent = std::chrono::steady_clock::now(); - epochFile = rec.TimestampNs; - } - } - private: - Stress& _app; - const std::string _uri; - - /// LOK child process PID to Doc URI map. - std::map<unsigned, std::string> _childToDoc; - - /// Doc URI to _sessions map. _sessions are maps of SessionID to Connection. - std::map<std::string, std::map<std::string, std::shared_ptr<Connection>>> _sessions; - std::vector<long> _latencyStats; std::vector<long> _renderingStats; std::vector<long> _cacheStats; @@ -486,7 +298,7 @@ int Stress::main(const std::vector<std::string>& args) std::cout << "Arg: " << args[i] << std::endl; for (unsigned j = 0; j < _numClients; ++j, ++index) { - workers.emplace_back(new Worker(*this, args[i])); + workers.emplace_back(new Worker(_serverURI, args[i])); clients[index].reset(new Thread()); clients[index]->start(*workers[workers.size() - 1]); } diff --git a/wsd/TraceFile.hpp b/wsd/TraceFile.hpp index 8f308f1..544adac 100644 --- a/wsd/TraceFile.hpp +++ b/wsd/TraceFile.hpp @@ -7,6 +7,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef INCLUDED_TRACEFILE_HPP +#define INCLUDED_TRACEFILE_HPP + #include <fstream> #include <mutex> #include <sstream> @@ -17,6 +20,7 @@ #include <Poco/DateTimeFormatter.h> #include <Poco/DeflatingStream.h> #include <Poco/InflatingStream.h> +#include <Poco/URI.h> #include "Protocol.hpp" #include "Log.hpp" @@ -463,4 +467,6 @@ private: unsigned _indexOut; }; +#endif + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits