diff --git a/Help/manual/cmake-server.7.rst b/Help/manual/cmake-server.7.rst index fd0c9ee42..75aa0ee17 100644 --- a/Help/manual/cmake-server.7.rst +++ b/Help/manual/cmake-server.7.rst @@ -68,6 +68,40 @@ Messages sent to and from the process are wrapped in magic strings:: The server is now ready to accept further requests via stdin. +Debugging +========= + +CMake server mode can be asked to provide statistics on execution times, etc. +or to dump a copy of the response into a file. This is done passing a "debug" +JSON object as a child of the request. + +The debug object supports the "showStats" key, which takes a boolean and makes +the server mode return a "zzzDebug" object with stats as part of its response. +"dumpToFile" takes a string value and will cause the cmake server to copy +the response into the given filename. + +This is a response from the cmake server with "showStats" set to true:: + + [== CMake Server ==[ + { + "cookie":"", + "errorMessage":"Waiting for type \"handshake\".", + "inReplyTo":"unknown", + "type":"error", + "zzzDebug": { + "dumpFile":"/tmp/error.txt", + "jsonSerialization":0.011016, + "size":111, + "totalTime":0.025995 + } + } + ]== CMake Server ==] + +The server has made a copy of this response into the file /tmp/error.txt and +took 0.011 seconds to turn the JSON response into a string, and it took 0.025 +seconds to process the request in total. The reply has a size of 111 bytes. + + Protocol API ============ @@ -132,6 +166,21 @@ a message of type "reply" or "error" that complete the request. the request that triggered the responses was delivered. +Type "message" +^^^^^^^^^^^^^^ + +A message is triggered when the server processes a request and produces some +form of output that should be displayed to the user. A Message has a "message" +with the actual text to display as well as a "title" with a suggested dialog +box title. + +Example:: + + [== CMake Server ==[ + {"cookie":"","message":"Something happened.","title":"Title Text","inReplyTo":"handshake","type":"message"} + ]== CMake Server ==] + + Specific Message Types ---------------------- diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx index 208fac6ac..be001a776 100644 --- a/Source/cmServer.cxx +++ b/Source/cmServer.cxx @@ -14,6 +14,7 @@ #include "cmServer.h" #include "cmServerProtocol.h" +#include "cmSystemTools.h" #include "cmVersionMacros.h" #include "cmake.h" @@ -22,17 +23,22 @@ #include "cm_jsoncpp_value.h" #endif -const char kTYPE_KEY[] = "type"; -const char kCOOKIE_KEY[] = "cookie"; -const char REPLY_TO_KEY[] = "inReplyTo"; -const char ERROR_MESSAGE_KEY[] = "errorMessage"; +#include +#include +#include -const char ERROR_TYPE[] = "error"; -const char REPLY_TYPE[] = "reply"; -const char PROGRESS_TYPE[] = "progress"; +static const std::string kTYPE_KEY = "type"; +static const std::string kCOOKIE_KEY = "cookie"; +static const std::string kREPLY_TO_KEY = "inReplyTo"; +static const std::string kERROR_MESSAGE_KEY = "errorMessage"; -const char START_MAGIC[] = "[== CMake Server ==["; -const char END_MAGIC[] = "]== CMake Server ==]"; +static const std::string kERROR_TYPE = "error"; +static const std::string kREPLY_TYPE = "reply"; +static const std::string kPROGRESS_TYPE = "progress"; +static const std::string kMESSAGE_TYPE = "message"; + +static const std::string kSTART_MAGIC = "[== CMake Server ==["; +static const std::string kEND_MAGIC = "]== CMake Server ==]"; typedef struct { @@ -85,6 +91,20 @@ void read_stdin(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) free(buf->base); } +class cmServer::DebugInfo +{ +public: + DebugInfo() + : StartTime(uv_hrtime()) + { + } + + bool PrintStatistics = false; + + std::string OutputFile; + uint64_t StartTime; +}; + cmServer::cmServer(bool supportExperimental) : SupportExperimental(supportExperimental) { @@ -124,18 +144,33 @@ void cmServer::PopOne() return; } + std::unique_ptr debug; + Json::Value debugValue = value["debug"]; + if (!debugValue.isNull()) { + debug = std::make_unique(); + debug->OutputFile = debugValue["dumpToFile"].asString(); + debug->PrintStatistics = debugValue["showStats"].asBool(); + } + const cmServerRequest request(this, value[kTYPE_KEY].asString(), value[kCOOKIE_KEY].asString(), value); if (request.Type == "") { cmServerResponse response(request); response.SetError("No type given in request."); - this->WriteResponse(response); + this->WriteResponse(response, nullptr); return; } - this->WriteResponse(this->Protocol ? this->Protocol->Process(request) - : this->SetProtocolVersion(request)); + cmSystemTools::SetMessageCallback(reportMessage, + const_cast(&request)); + if (this->Protocol) { + this->Protocol->CMakeInstance()->SetProgressCallback( + reportProgress, const_cast(&request)); + this->WriteResponse(this->Protocol->Process(request), debug.get()); + } else { + this->WriteResponse(this->SetProtocolVersion(request), debug.get()); + } } void cmServer::handleData(const std::string& data) @@ -154,11 +189,11 @@ void cmServer::handleData(const std::string& data) line.erase(ls - 1, 1); this->DataBuffer.erase(this->DataBuffer.begin(), this->DataBuffer.begin() + needle + 1); - if (line == START_MAGIC) { + if (line == kSTART_MAGIC) { this->JsonData.clear(); continue; } - if (line == END_MAGIC) { + if (line == kEND_MAGIC) { this->Queue.push_back(this->JsonData); this->JsonData.clear(); if (!this->Writing) { @@ -207,7 +242,31 @@ void cmServer::PrintHello() const protocolVersions.append(tmp); } - this->WriteJsonObject(hello); + this->WriteJsonObject(hello, nullptr); +} + +void cmServer::reportProgress(const char* msg, float progress, void* data) +{ + const cmServerRequest* request = static_cast(data); + assert(request); + if (progress < 0.0 || progress > 1.0) { + request->ReportMessage(msg, ""); + } else { + request->ReportProgress(0, static_cast(progress * 1000), 1000, msg); + } +} + +void cmServer::reportMessage(const char* msg, const char* title, + bool& /* cancel */, void* data) +{ + const cmServerRequest* request = static_cast(data); + assert(request); + assert(msg); + std::string titleString; + if (title) { + titleString = title; + } + request->ReportMessage(std::string(msg), titleString); } cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request) @@ -292,16 +351,44 @@ bool cmServer::Serve() return true; } -void cmServer::WriteJsonObject(const Json::Value& jsonValue) const +void cmServer::WriteJsonObject(const Json::Value& jsonValue, + const DebugInfo* debug) const { Json::FastWriter writer; - std::string result = std::string("\n") + std::string(START_MAGIC) + - std::string("\n") + writer.write(jsonValue) + std::string(END_MAGIC) + - std::string("\n"); + auto beforeJson = uv_hrtime(); + std::string result = writer.write(jsonValue); + + if (debug) { + Json::Value copy = jsonValue; + if (debug->PrintStatistics) { + Json::Value stats = Json::objectValue; + auto endTime = uv_hrtime(); + + stats["jsonSerialization"] = double(endTime - beforeJson) / 1000000.0; + stats["totalTime"] = double(endTime - debug->StartTime) / 1000000.0; + stats["size"] = static_cast(result.size()); + if (!debug->OutputFile.empty()) { + stats["dumpFile"] = debug->OutputFile; + } + + copy["zzzDebug"] = stats; + + result = writer.write(copy); // Update result to include debug info + } + + if (!debug->OutputFile.empty()) { + std::ofstream myfile; + myfile.open(debug->OutputFile); + myfile << result; + myfile.close(); + } + } this->Writing = true; - write_data(this->OutputStream, result, on_stdout_write); + write_data(this->OutputStream, std::string("\n") + kSTART_MAGIC + + std::string("\n") + result + kEND_MAGIC + std::string("\n"), + on_stdout_write); } cmServerProtocol* cmServer::FindMatchingProtocol( @@ -328,39 +415,59 @@ void cmServer::WriteProgress(const cmServerRequest& request, int min, assert(message.length() != 0); Json::Value obj = Json::objectValue; - obj[kTYPE_KEY] = PROGRESS_TYPE; - obj[REPLY_TO_KEY] = request.Type; + obj[kTYPE_KEY] = kPROGRESS_TYPE; + obj[kREPLY_TO_KEY] = request.Type; obj[kCOOKIE_KEY] = request.Cookie; obj["progressMessage"] = message; obj["progressMinimum"] = min; obj["progressMaximum"] = max; obj["progressCurrent"] = current; - this->WriteJsonObject(obj); + this->WriteJsonObject(obj, nullptr); +} + +void cmServer::WriteMessage(const cmServerRequest& request, + const std::string& message, + const std::string& title) const +{ + if (message.empty()) + return; + + Json::Value obj = Json::objectValue; + obj[kTYPE_KEY] = kMESSAGE_TYPE; + obj[kREPLY_TO_KEY] = request.Type; + obj[kCOOKIE_KEY] = request.Cookie; + obj["message"] = message; + if (!title.empty()) { + obj["title"] = title; + } + + WriteJsonObject(obj, nullptr); } void cmServer::WriteParseError(const std::string& message) const { Json::Value obj = Json::objectValue; - obj[kTYPE_KEY] = ERROR_TYPE; - obj[ERROR_MESSAGE_KEY] = message; - obj[REPLY_TO_KEY] = ""; + obj[kTYPE_KEY] = kERROR_TYPE; + obj[kERROR_MESSAGE_KEY] = message; + obj[kREPLY_TO_KEY] = ""; obj[kCOOKIE_KEY] = ""; - this->WriteJsonObject(obj); + this->WriteJsonObject(obj, nullptr); } -void cmServer::WriteResponse(const cmServerResponse& response) const +void cmServer::WriteResponse(const cmServerResponse& response, + const DebugInfo* debug) const { assert(response.IsComplete()); Json::Value obj = response.Data(); obj[kCOOKIE_KEY] = response.Cookie; - obj[kTYPE_KEY] = response.IsError() ? ERROR_TYPE : REPLY_TYPE; - obj[REPLY_TO_KEY] = response.Type; + obj[kTYPE_KEY] = response.IsError() ? kERROR_TYPE : kREPLY_TYPE; + obj[kREPLY_TO_KEY] = response.Type; if (response.IsError()) { - obj[ERROR_MESSAGE_KEY] = response.ErrorMessage(); + obj[kERROR_MESSAGE_KEY] = response.ErrorMessage(); } - this->WriteJsonObject(obj); + this->WriteJsonObject(obj, debug); } diff --git a/Source/cmServer.h b/Source/cmServer.h index 4a9c3f53c..38a11bbca 100644 --- a/Source/cmServer.h +++ b/Source/cmServer.h @@ -31,6 +31,8 @@ class cmServerResponse; class cmServer { public: + class DebugInfo; + cmServer(bool supportExperimental); ~cmServer(); @@ -43,6 +45,10 @@ public: private: void RegisterProtocol(cmServerProtocol* protocol); + static void reportProgress(const char* msg, float progress, void* data); + static void reportMessage(const char* msg, const char* title, bool& cancel, + void* data); + // Handle requests: cmServerResponse SetProtocolVersion(const cmServerRequest& request); @@ -51,10 +57,14 @@ private: // Write responses: void WriteProgress(const cmServerRequest& request, int min, int current, int max, const std::string& message) const; - void WriteResponse(const cmServerResponse& response) const; + void WriteMessage(const cmServerRequest& request, const std::string& message, + const std::string& title) const; + void WriteResponse(const cmServerResponse& response, + const DebugInfo* debug) const; void WriteParseError(const std::string& message) const; - void WriteJsonObject(Json::Value const& jsonValue) const; + void WriteJsonObject(Json::Value const& jsonValue, + const DebugInfo* debug) const; static cmServerProtocol* FindMatchingProtocol( const std::vector& protocols, int major, int minor); diff --git a/Source/cmServerProtocol.cxx b/Source/cmServerProtocol.cxx index d53ac28a1..26942d310 100644 --- a/Source/cmServerProtocol.cxx +++ b/Source/cmServerProtocol.cxx @@ -22,17 +22,14 @@ #include "cm_jsoncpp_value.h" #endif -namespace { // Vocabulary: -const std::string kBUILD_DIRECTORY_KEY = "buildDirectory"; -const std::string kCOOKIE_KEY = "cookie"; -const std::string kEXTRA_GENERATOR_KEY = "extraGenerator"; -const std::string kGENERATOR_KEY = "generator"; -const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory"; -const std::string kTYPE_KEY = "type"; - -} // namespace +static const std::string kBUILD_DIRECTORY_KEY = "buildDirectory"; +static const std::string kCOOKIE_KEY = "cookie"; +static const std::string kEXTRA_GENERATOR_KEY = "extraGenerator"; +static const std::string kGENERATOR_KEY = "generator"; +static const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory"; +static const std::string kTYPE_KEY = "type"; cmServerRequest::cmServerRequest(cmServer* server, const std::string& t, const std::string& c, const Json::Value& d) @@ -49,6 +46,12 @@ void cmServerRequest::ReportProgress(int min, int current, int max, this->m_Server->WriteProgress(*this, min, current, max, message); } +void cmServerRequest::ReportMessage(const std::string& message, + const std::string& title) const +{ + m_Server->WriteMessage(*this, message, title); +} + cmServerResponse cmServerRequest::Reply(const Json::Value& data) const { cmServerResponse response(*this); diff --git a/Source/cmServerProtocol.h b/Source/cmServerProtocol.h index e95c2f19c..bab949b75 100644 --- a/Source/cmServerProtocol.h +++ b/Source/cmServerProtocol.h @@ -57,9 +57,6 @@ private: class cmServerRequest { public: - void ReportProgress(int min, int current, int max, - const std::string& message) const; - cmServerResponse Reply(const Json::Value& data) const; cmServerResponse ReportError(const std::string& message) const; @@ -71,6 +68,11 @@ private: cmServerRequest(cmServer* server, const std::string& t, const std::string& c, const Json::Value& d); + void ReportProgress(int min, int current, int max, + const std::string& message) const; + void ReportMessage(const std::string& message, + const std::string& title) const; + cmServer* m_Server; friend class cmServer; @@ -95,6 +97,8 @@ protected: private: std::unique_ptr m_CMakeInstance; + + friend class cmServer; }; class cmServerProtocol1_0 : public cmServerProtocol