summaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorWouter Klouwen <wouter.klouwen@youview.com>2019-02-02 09:06:36 (GMT)
committerWouter Klouwen <wouter.klouwen@youview.com>2020-03-07 13:36:27 (GMT)
commit9aa4640792bd99b232abfe826b0cb0ae72f6644a (patch)
tree6d576cb085d48a0073ddc09036a7e1a2b95df2d6 /Source
parent8f122b45570e8357886df115d78007c4ea437daa (diff)
downloadCMake-9aa4640792bd99b232abfe826b0cb0ae72f6644a.zip
CMake-9aa4640792bd99b232abfe826b0cb0ae72f6644a.tar.gz
CMake-9aa4640792bd99b232abfe826b0cb0ae72f6644a.tar.bz2
cmake: add command line options to output script profiling data
For users of CMake who want to optimize their scripts if they take a while to run, this commit adds the ability to output profiling data. To enable this output, it adds the two command line parameters to select the output path and format. This commit adds the first profiling format of type ``google-trace``, which is the output is a JSON file containing Duration events as per the Google Trace Format specification: https://docs.google.com/document/d/1CvAClvFfyA5R- PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#
Diffstat (limited to 'Source')
-rw-r--r--Source/CMakeLists.txt1
-rw-r--r--Source/cmMakefile.cxx16
-rw-r--r--Source/cmMakefileProfilingData.cxx113
-rw-r--r--Source/cmMakefileProfilingData.h29
-rw-r--r--Source/cmake.cxx57
-rw-r--r--Source/cmake.h12
-rw-r--r--Source/cmakemain.cxx6
7 files changed, 232 insertions, 2 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 24370aa..467abe9 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -354,6 +354,7 @@ set(SRCS
cmMakefileTargetGenerator.cxx
cmMakefileExecutableTargetGenerator.cxx
cmMakefileLibraryTargetGenerator.cxx
+ cmMakefileProfilingData.cxx
cmMakefileUtilityTargetGenerator.cxx
cmMessageType.h
cmMessenger.cxx
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 94d99b7..76c559f 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -60,6 +60,7 @@
#include "cmake.h"
#ifndef CMAKE_BOOTSTRAP
+# include "cmMakefileProfilingData.h"
# include "cmVariableWatch.h"
#endif
@@ -372,19 +373,30 @@ void cmMakefile::PrintCommandTrace(const cmListFileFunction& lff) const
class cmMakefileCall
{
public:
- cmMakefileCall(cmMakefile* mf, cmCommandContext const& cc,
+ cmMakefileCall(cmMakefile* mf, cmListFileFunction const& lff,
cmExecutionStatus& status)
: Makefile(mf)
{
cmListFileContext const& lfc = cmListFileContext::FromCommandContext(
- cc, this->Makefile->StateSnapshot.GetExecutionListFile());
+ lff, this->Makefile->StateSnapshot.GetExecutionListFile());
this->Makefile->Backtrace = this->Makefile->Backtrace.Push(lfc);
++this->Makefile->RecursionDepth;
this->Makefile->ExecutionStatusStack.push_back(&status);
+#if !defined(CMAKE_BOOTSTRAP)
+ if (this->Makefile->GetCMakeInstance()->IsProfilingEnabled()) {
+ this->Makefile->GetCMakeInstance()->GetProfilingOutput().StartEntry(lff,
+ lfc);
+ }
+#endif
}
~cmMakefileCall()
{
+#if !defined(CMAKE_BOOTSTRAP)
+ if (this->Makefile->GetCMakeInstance()->IsProfilingEnabled()) {
+ this->Makefile->GetCMakeInstance()->GetProfilingOutput().StopEntry();
+ }
+#endif
this->Makefile->ExecutionStatusStack.pop_back();
--this->Makefile->RecursionDepth;
this->Makefile->Backtrace = this->Makefile->Backtrace.Pop();
diff --git a/Source/cmMakefileProfilingData.cxx b/Source/cmMakefileProfilingData.cxx
new file mode 100644
index 0000000..ea64132
--- /dev/null
+++ b/Source/cmMakefileProfilingData.cxx
@@ -0,0 +1,113 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmMakefileProfilingData.h"
+
+#include <chrono>
+#include <cstdint>
+#include <stdexcept>
+#include <vector>
+
+#include "cmsys/FStream.hxx"
+#include "cmsys/SystemInformation.hxx"
+
+#include "cm_jsoncpp_value.h"
+#include "cm_jsoncpp_writer.h"
+
+#include "cmListFileCache.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+
+cmMakefileProfilingData::cmMakefileProfilingData(
+ const std::string& profileStream)
+{
+ std::ios::openmode omode = std::ios::out | std::ios::trunc;
+ this->ProfileStream.open(profileStream.c_str(), omode);
+ Json::StreamWriterBuilder wbuilder;
+ this->JsonWriter =
+ std::unique_ptr<Json::StreamWriter>(wbuilder.newStreamWriter());
+ if (!this->ProfileStream.good()) {
+ throw std::runtime_error(std::string("Unable to open: ") + profileStream);
+ }
+
+ this->ProfileStream << "[";
+};
+
+cmMakefileProfilingData::~cmMakefileProfilingData() noexcept
+{
+ if (this->ProfileStream.good()) {
+ try {
+ this->ProfileStream << "]";
+ this->ProfileStream.close();
+ } catch (...) {
+ cmSystemTools::Error("Error writing profiling output!");
+ }
+ }
+}
+
+void cmMakefileProfilingData::StartEntry(const cmListFileFunction& lff,
+ cmListFileContext const& lfc)
+{
+ /* Do not try again if we previously failed to write to output. */
+ if (!this->ProfileStream.good()) {
+ return;
+ }
+
+ try {
+ if (this->ProfileStream.tellp() > 1) {
+ this->ProfileStream << ",";
+ }
+ cmsys::SystemInformation info;
+ Json::Value v;
+ v["ph"] = "B";
+ v["name"] = lff.Name.Original;
+ v["cat"] = "cmake";
+ v["ts"] = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count());
+ v["pid"] = static_cast<int>(info.GetProcessId());
+ v["tid"] = 0;
+ Json::Value argsValue;
+ if (!lff.Arguments.empty()) {
+ std::string args;
+ for (const auto& a : lff.Arguments) {
+ args += (args.empty() ? "" : " ") + a.Value;
+ }
+ argsValue["functionArgs"] = args;
+ }
+ argsValue["location"] = lfc.FilePath + ":" + std::to_string(lfc.Line);
+ v["args"] = argsValue;
+
+ this->JsonWriter->write(v, &this->ProfileStream);
+ } catch (std::ios_base::failure& fail) {
+ cmSystemTools::Error(
+ cmStrCat("Failed to write to profiling output: ", fail.what()));
+ } catch (...) {
+ cmSystemTools::Error("Error writing profiling output!");
+ }
+}
+
+void cmMakefileProfilingData::StopEntry()
+{
+ /* Do not try again if we previously failed to write to output. */
+ if (!this->ProfileStream.good()) {
+ return;
+ }
+
+ try {
+ this->ProfileStream << ",";
+ cmsys::SystemInformation info;
+ Json::Value v;
+ v["ph"] = "E";
+ v["ts"] = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count());
+ v["pid"] = static_cast<int>(info.GetProcessId());
+ v["tid"] = 0;
+ this->JsonWriter->write(v, &this->ProfileStream);
+ } catch (std::ios_base::failure& fail) {
+ cmSystemTools::Error(
+ cmStrCat("Failed to write to profiling output:", fail.what()));
+ } catch (...) {
+ cmSystemTools::Error("Error writing profiling output!");
+ }
+}
diff --git a/Source/cmMakefileProfilingData.h b/Source/cmMakefileProfilingData.h
new file mode 100644
index 0000000..1babd97
--- /dev/null
+++ b/Source/cmMakefileProfilingData.h
@@ -0,0 +1,29 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmMakefileProfilingData_h
+#define cmMakefileProfilingData_h
+#include <memory>
+#include <string>
+
+#include "cmsys/FStream.hxx"
+
+namespace Json {
+class StreamWriter;
+}
+
+class cmListFileContext;
+struct cmListFileFunction;
+
+class cmMakefileProfilingData
+{
+public:
+ cmMakefileProfilingData(const std::string&);
+ ~cmMakefileProfilingData() noexcept;
+ void StartEntry(const cmListFileFunction& lff, cmListFileContext const& lfc);
+ void StopEntry();
+
+private:
+ cmsys::ofstream ProfileStream;
+ std::unique_ptr<Json::StreamWriter> JsonWriter;
+};
+#endif
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index f4b9f16..29ed61d 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -9,6 +9,7 @@
#include <initializer_list>
#include <iostream>
#include <sstream>
+#include <stdexcept>
#include <utility>
#include <cm/memory>
@@ -39,6 +40,9 @@
#include "cmLinkLineComputer.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
+#if !defined(CMAKE_BOOTSTRAP)
+# include "cmMakefileProfilingData.h"
+#endif
#include "cmMessenger.h"
#include "cmState.h"
#include "cmStateDirectory.h"
@@ -613,6 +617,10 @@ void cmake::SetArgs(const std::vector<std::string>& args)
{
bool haveToolset = false;
bool havePlatform = false;
+#if !defined(CMAKE_BOOTSTRAP)
+ std::string profilingFormat;
+ std::string profilingOutput;
+#endif
for (unsigned int i = 1; i < args.size(); ++i) {
std::string const& arg = args[i];
if (arg.find("-H", 0) == 0 || arg.find("-S", 0) == 0) {
@@ -839,6 +847,20 @@ void cmake::SetArgs(const std::vector<std::string>& args)
return;
}
this->SetGlobalGenerator(std::move(gen));
+#if !defined(CMAKE_BOOTSTRAP)
+ } else if (arg.find("--profiling-format", 0) == 0) {
+ profilingFormat = arg.substr(strlen("--profiling-format="));
+ if (profilingFormat.empty()) {
+ cmSystemTools::Error("No format specified for --profiling-format");
+ }
+ } else if (arg.find("--profiling-output", 0) == 0) {
+ profilingOutput = arg.substr(strlen("--profiling-output="));
+ profilingOutput = cmSystemTools::CollapseFullPath(profilingOutput);
+ cmSystemTools::ConvertToUnixSlashes(profilingOutput);
+ if (profilingOutput.empty()) {
+ cmSystemTools::Error("No path specified for --profiling-output");
+ }
+#endif
}
// no option assume it is the path to the source or an existing build
else {
@@ -856,6 +878,29 @@ void cmake::SetArgs(const std::vector<std::string>& args)
}
}
+#if !defined(CMAKE_BOOTSTRAP)
+ if (!profilingOutput.empty() || !profilingFormat.empty()) {
+ if (profilingOutput.empty()) {
+ cmSystemTools::Error(
+ "--profiling-format specified but no --profiling-output!");
+ return;
+ }
+ if (profilingFormat == "google-trace") {
+ try {
+ this->ProfilingOutput =
+ cm::make_unique<cmMakefileProfilingData>(profilingOutput);
+ } catch (std::runtime_error& e) {
+ cmSystemTools::Error(
+ cmStrCat("Could not start profiling: ", e.what()));
+ return;
+ }
+ } else {
+ cmSystemTools::Error("Invalid format specified for --profiling-format");
+ return;
+ }
+ }
+#endif
+
const bool haveSourceDir = !this->GetHomeDirectory().empty();
const bool haveBinaryDir = !this->GetHomeOutputDirectory().empty();
@@ -2952,3 +2997,15 @@ void cmake::SetDeprecatedWarningsAsErrors(bool b)
" and functions.",
cmStateEnums::INTERNAL);
}
+
+#if !defined(CMAKE_BOOTSTRAP)
+cmMakefileProfilingData& cmake::GetProfilingOutput()
+{
+ return *(this->ProfilingOutput);
+}
+
+bool cmake::IsProfilingEnabled() const
+{
+ return static_cast<bool>(this->ProfilingOutput);
+}
+#endif
diff --git a/Source/cmake.h b/Source/cmake.h
index 35425ec..58769fd 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -34,6 +34,9 @@ class cmFileTimeCache;
class cmGlobalGenerator;
class cmGlobalGeneratorFactory;
class cmMakefile;
+#if !defined(CMAKE_BOOTSTRAP)
+class cmMakefileProfilingData;
+#endif
class cmMessenger;
class cmVariableWatch;
struct cmDocumentationEntry;
@@ -549,6 +552,11 @@ public:
bool GetRegenerateDuringBuild() const { return this->RegenerateDuringBuild; }
+#if !defined(CMAKE_BOOTSTRAP)
+ cmMakefileProfilingData& GetProfilingOutput();
+ bool IsProfilingEnabled() const;
+#endif
+
protected:
void RunCheckForUnusedVariables();
int HandleDeleteCacheVariables(const std::string& var);
@@ -657,6 +665,10 @@ private:
void AppendGlobalGeneratorsDocumentation(std::vector<cmDocumentationEntry>&);
void AppendExtraGeneratorsDocumentation(std::vector<cmDocumentationEntry>&);
+
+#if !defined(CMAKE_BOOTSTRAP)
+ std::unique_ptr<cmMakefileProfilingData> ProfilingOutput;
+#endif
};
#define CMAKE_STANDARD_OPTIONS_TABLE \
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index 494d5d9..90d8ea6 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -93,6 +93,12 @@ const char* cmDocumentationOptions[][2] = {
{ "--check-system-vars",
"Find problems with variable usage in system "
"files." },
+# if !defined(CMAKE_BOOTSTRAP)
+ { "--profiling-format=<fmt>", "Output data for profiling CMake scripts." },
+ { "--profiling-output=<file>",
+ "Select an output path for the profiling data enabled through "
+ "--profiling-format." },
+# endif
{ nullptr, nullptr }
};