Instrumentation: Add Process Resource Usage Metrics

Issue: #26675, #27742
This commit is contained in:
Martin Duffy
2026-08-27 10:24:25 -04:00
committed by Brad King
parent 7204679f48
commit 5f222e8f3a
24 changed files with 508 additions and 27 deletions
+6 -3
View File
@@ -25,7 +25,7 @@ The ``API_VERSION`` and ``DATA_VERSION`` must always be given.
See :ref:`cmake-instrumentation API v1` for details.
``DATA_VERSION`` is a version value of the form ``major`` or ``major.minor``.
Currently, the maximum supported version is ``1.1``. See
Currently, the maximum supported version is ``1.2``. See
:ref:`cmake-instrumentation Data Version` for details.
Each of the optional keywords ``HOOKS``, ``OPTIONS``, and ``CALLBACK``
@@ -74,7 +74,7 @@ equivalent JSON query file.
cmake_instrumentation(
API_VERSION 1
DATA_VERSION 1.0
DATA_VERSION 1.2
HOOKS postGenerate preCMakeBuild postCMakeBuild
OPTIONS staticSystemInformation dynamicSystemInformation compileTrace trace
CALLBACK ${CMAKE_COMMAND} -P /path/to/handle_data.cmake
@@ -87,7 +87,10 @@ equivalent JSON query file.
.. code-block:: json
{
"version": 1,
"version": {
"major": 1,
"minor": 2
},
"hooks": [
"postGenerate", "preCMakeBuild", "postCMakeBuild"
],
+59 -1
View File
@@ -239,6 +239,28 @@ previously included data is removed or reformatted such that scripts written to
parse this data may become incompatible with the new format. A new minor version
number will be created whenever new data becomes available.
.. versionadded:: 4.4
`API v1`_ gained Data version ``1.1``.
* The ``captureOutput`` `option <v1 Query Files_>`_ and corresponding
``stderr`` and ``stdout`` snippet fields to capture command output were
added.
* The ``compileTrace`` `option <v1 Query Files_>`_ was added which enables
the collection of compiler trace files.
.. versionadded:: 4.5
`API v1`_ gained Data version ``1.2``.
* An ``interruptSignal`` field was added to appropriate snippet files to flag
whether they exited due to interrupt.
* The ``processMetrics`` `option <v1 Query Files_>`_ and corresponding
``processMetrics`` snippet field for reporting child process resource usage
were added.
.. _`cmake-instrumentation v1 Query Files`:
v1 Query Files
@@ -324,6 +346,14 @@ key is required, but all other fields are optional.
Only available as of data version ``1.1``.
``processMetrics``
.. versionadded:: 4.5
Enables collection of kernel-reported resource usage for the command.
When enabled, certain snippets will include a ``processMetrics`` field.
Only available as of data version ``1.2``.
``cdashSubmit``
Enables including instrumentation data in CDash. This is
equivalent to having the :envvar:`CTEST_USE_INSTRUMENTATION` environment
@@ -370,6 +400,7 @@ Example:
"staticSystemInformation",
"dynamicSystemInformation",
"captureOutput",
"processMetrics",
"cdashSubmit",
"trace"
]
@@ -565,6 +596,27 @@ Snippet files have a filename with the syntax
The Average CPU Load at ``timeStart + duration``, or ``null`` if it
cannot be determined.
``processMetrics``
.. versionadded:: 4.5
Kernel-reported resource usage accumulated for the command. Only included
when the ``processMetrics`` `option <v1 Query Files_>`_ is enabled and the
snippet is one of ``compile``, ``link``, ``custom``, ``install``, or
``test``.
``maxRSS``
Maximum resident set size in KiB.
``userTimeUSec``
User CPU time in microseconds.
``systemTimeUSec``
System CPU time in microseconds.
If the data could not be collected, this object is ``null``.
Only available as of data version ``1.2``.
``cmakeContent``
The path to a `v1 CMake Content File`_ located under ``data``, which
contains information about the CMake configure and generate steps
@@ -584,7 +636,7 @@ Example:
{
"version": {
"major": 1,
"minor": 1
"minor": 2
},
"command" : "\"/usr/bin/c++\" \"-MD\" \"-MT\" \"CMakeFiles/main.dir/main.cxx.o\" \"-MF\" \"CMakeFiles/main.dir/main.cxx.o.d\" \"-o\" \"CMakeFiles/main.dir/main.cxx.o\" \"-c\" \"<src>/main.cxx\"",
"role" : "compile",
@@ -605,6 +657,12 @@ Example:
"beforeCPULoadAverage" : 2.3500000000000001,
"beforeHostMemoryUsed" : 6635832.0
},
"processMetrics" :
{
"maxRSS" : 21032,
"userTimeUSec" : 18000,
"systemTimeUSec" : 5000
},
"timeStart" : 1737053448177,
"duration" : 31,
"cmakeContent" : "content/cmake-2025-07-11T12-46-32-0572.json"
@@ -66,6 +66,7 @@
"enum": [
"staticSystemInformation",
"dynamicSystemInformation",
"processMetrics",
"compileTrace",
"cdashSubmit",
"cdashVerbose",
@@ -392,6 +392,40 @@
"type": "null"
}
]
},
"processMetrics": {
"description": "Kernel-reported resource usage accumulated for child processes executed by the instrumentation wrapper, or null when unavailable.",
"anyOf": [
{
"type": "object",
"required": [
"maxRSS",
"userTimeUSec",
"systemTimeUSec"
],
"properties": {
"maxRSS": {
"type": "integer",
"description": "Maximum resident set size reported for wrapped child processes, in KiB.",
"minimum": 0
},
"userTimeUSec": {
"type": "integer",
"description": "User CPU time reported for wrapped child processes, in microseconds.",
"minimum": 0
},
"systemTimeUSec": {
"type": "integer",
"description": "System CPU time reported for wrapped child processes, in microseconds.",
"minimum": 0
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
}
},
"snippetV1_0": {
@@ -625,6 +659,9 @@
},
"interruptSignal": {
"$ref": "#/definitions/fields/interruptSignal"
},
"processMetrics": {
"$ref": "#/definitions/fields/processMetrics"
}
},
"additionalProperties": false,
+57 -2
View File
@@ -9,13 +9,17 @@
#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <cm/optional>
#include <cm3p/json/value.h>
#include <cm3p/json/writer.h>
#include <cm3p/uv.h>
#include "cmsys/FStream.hxx"
#include "cmsys/RegularExpression.hxx"
#include "cmsys/SystemInformation.hxx"
#include "cmCMakePath.h"
#include "cmCTestLaunchReporter.h"
@@ -77,7 +81,8 @@ bool cmCTestLaunch::ParseArguments(int argc, char const* const* argv)
DoingCount,
DoingFilterPrefix,
DoingConfig,
DoingObjectDir
DoingObjectDir,
DoingMetricsFile,
};
Doing doing = DoingNone;
int arg0 = 0;
@@ -113,6 +118,8 @@ bool cmCTestLaunch::ParseArguments(int argc, char const* const* argv)
doing = DoingConfig;
} else if (strcmp(arg, "--object-dir") == 0) {
doing = DoingObjectDir;
} else if (strcmp(arg, "--metrics-file") == 0) {
doing = DoingMetricsFile;
} else if (doing == DoingOutput) {
this->Reporter.OptionOutput = arg;
doing = DoingNone;
@@ -168,6 +175,9 @@ bool cmCTestLaunch::ParseArguments(int argc, char const* const* argv)
} else if (doing == DoingObjectDir) {
this->Reporter.OptionObjectDir = arg;
doing = DoingNone;
} else if (doing == DoingMetricsFile) {
this->MetricsFile = arg;
doing = DoingNone;
}
}
@@ -223,6 +233,8 @@ void cmCTestLaunch::RunChild()
return;
}
this->ChildResourceUsage = cm::nullopt;
this->CapturedStdOut.clear();
this->CapturedStdErr.clear();
@@ -316,13 +328,27 @@ void cmCTestLaunch::RunChild()
this->Reporter.ExitCode =
static_cast<int>(this->Reporter.Status.ExitStatus);
}
cmsys::SystemInformation::ProcessResourceUsage processUsage{};
if (cmsys::SystemInformation::GetProcessResourceUsage(
processUsage, chain.GetNativeProcessHandle(0))) {
this->ChildResourceUsage = processUsage;
}
}
int cmCTestLaunch::Run()
{
if (this->Operation == Op::InstrumentTest) {
this->RunChild();
this->WriteMetricsFile();
return this->Reporter.ExitCode;
}
auto instrumentation = cmInstrumentation(this->Reporter.OptionBuildDir);
bool const captureOutput =
instrumentation.HasOption(cmInstrumentationQuery::Option::CaptureOutput);
bool const processMetrics =
instrumentation.HasOption(cmInstrumentationQuery::Option::ProcessMetrics);
std::map<std::string, std::string> options;
if (this->Reporter.OptionTargetName != "TARGET_NAME") {
options["target"] = this->Reporter.OptionTargetName;
@@ -337,7 +363,8 @@ int cmCTestLaunch::Run()
arrayOptions["targetLabels"] = this->Reporter.OptionTargetLabels;
instrumentation.InstrumentCommand(
this->Reporter.OptionCommandType, this->RealArgV,
[this, captureOutput]() -> cmInstrumentation::CommandResult {
[this, captureOutput,
processMetrics]() -> cmInstrumentation::CommandResult {
this->RunChild();
cmInstrumentation::CommandResult result;
result.ExitCode = this->Reporter.ExitCode;
@@ -345,6 +372,10 @@ int cmCTestLaunch::Run()
result.StdOut = this->CapturedStdOut;
result.StdErr = this->CapturedStdErr;
}
if (processMetrics && this->ChildResourceUsage) {
result.ChildResourceUsage =
cmInstrumentation::ProcessMetrics(*this->ChildResourceUsage);
}
return result;
},
options, arrayOptions);
@@ -362,6 +393,30 @@ int cmCTestLaunch::Run()
return this->Reporter.ExitCode;
}
void cmCTestLaunch::WriteMetricsFile() const
{
if (this->MetricsFile.empty() || !this->ChildResourceUsage) {
return;
}
Json::Value root(Json::objectValue);
Json::Value processMetrics(Json::objectValue);
processMetrics["maxRSS"] =
static_cast<Json::Value::UInt64>(this->ChildResourceUsage->ru_maxrss);
processMetrics["userTimeUSec"] = static_cast<Json::Value::UInt64>(
this->ChildResourceUsage->ru_utime.tv_sec * 1000000ULL +
this->ChildResourceUsage->ru_utime.tv_usec);
processMetrics["systemTimeUSec"] = static_cast<Json::Value::UInt64>(
this->ChildResourceUsage->ru_stime.tv_sec * 1000000ULL +
this->ChildResourceUsage->ru_stime.tv_usec);
root["processMetrics"] = std::move(processMetrics);
cmsys::ofstream out(this->MetricsFile.c_str(),
std::ios::out | std::ios::trunc | std::ios::binary);
Json::StreamWriterBuilder builder;
out << Json::writeString(builder, root) << "\n";
}
bool cmCTestLaunch::CheckResults()
{
// Skip XML in passthru mode.
+10
View File
@@ -7,6 +7,10 @@
#include <string>
#include <vector>
#include <cm/optional>
#include <cmsys/SystemInformation.hxx>
#include "cmCTestLaunchReporter.h"
namespace cmsys {
@@ -26,6 +30,7 @@ public:
{
Normal,
Instrument,
InstrumentTest,
};
/** Entry point from ctest executable main(). */
@@ -55,6 +60,11 @@ private:
// The real command line after response file expansion.
std::vector<std::string> RealArgs;
void HandleRealArg(char const* arg);
std::string MetricsFile;
cm::optional<cmsys::SystemInformation::ProcessResourceUsage>
ChildResourceUsage;
void WriteMetricsFile() const;
// Whether or not any data have been written to stdout or stderr.
bool HaveOut;
+76 -3
View File
@@ -17,6 +17,8 @@
#include <cm/string_view>
#include <cmext/string_view>
#include <cm3p/json/value.h>
#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/RegularExpression.hxx"
@@ -27,6 +29,8 @@
#include "cmDuration.h"
#include "cmEnvironment.h"
#include "cmInstrumentation.h"
#include "cmInstrumentationQuery.h"
#include "cmJSONState.h"
#include "cmProcess.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
@@ -687,6 +691,9 @@ bool cmCTestRunTest::StartTest(size_t completed, size_t total)
void cmCTestRunTest::ComputeArguments()
{
this->Arguments.clear(); // reset because this might be a rerun
this->InstrumentationCommand.clear();
this->InstrumentationArguments.clear();
this->ProcessResourceUsage = cm::nullopt;
auto j = this->TestProperties->Args.begin();
++j; // skip test name
// find the test executable
@@ -724,6 +731,31 @@ void cmCTestRunTest::ComputeArguments()
testCommand = cmStrCat(std::move(testCommand), " \"", arg, '"');
this->Arguments.push_back(arg);
}
this->InstrumentationCommand = this->ActualCommand;
this->InstrumentationArguments = this->Arguments;
if (this->CTest->GetInstrumentation().HasOption(
cmInstrumentationQuery::Option::ProcessMetrics)) {
std::string const realCommand = this->ActualCommand;
std::vector<std::string> realArguments = this->Arguments;
std::string metricsFile = GetTestMetricsFile();
cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(metricsFile));
this->ActualCommand = cmSystemTools::GetCTestCommand();
this->Arguments.clear();
this->Arguments.insert(this->Arguments.end(),
{ "--instrument-test", "--metrics-file",
metricsFile, "--", realCommand });
this->Arguments.insert(this->Arguments.end(), realArguments.begin(),
realArguments.end());
testCommand = cmSystemTools::ConvertToOutputPath(this->ActualCommand);
for (std::string const& arg : this->Arguments) {
testCommand += cmStrCat(" \"", arg, '"');
}
this->TestResult.Environment.clear();
}
this->TestResult.FullCommandLine = testCommand;
// Print the test command in verbose mode
@@ -1048,6 +1080,15 @@ std::string cmCTestRunTest::GenerateLLVMPath(std::string fileString)
return cmStrCat(profRawRoot, fileString);
}
std::string cmCTestRunTest::GetTestMetricsFile() const
{
std::string safeName = this->TestProperties->Name;
cmSystemTools::ReplaceString(safeName, "/", "_");
cmSystemTools::ReplaceString(safeName, "\\", "_");
return cmStrCat(this->CTest->GetInstrumentation().GetDataDir(),
"/test/test-", safeName, "-", this->Index, ".json");
}
void cmCTestRunTest::CollectLLVMCoverage()
{
// find all *.profraw files
@@ -1124,11 +1165,43 @@ void cmCTestRunTest::FinalizeTest(bool started)
}
if (started && this->CTest->GetInstrumentation().HasQuery()) {
if (this->CTest->GetInstrumentation().HasOption(
cmInstrumentationQuery::Option::ProcessMetrics)) {
std::string metricsFile = GetTestMetricsFile();
if (cmSystemTools::FileExists(metricsFile)) {
Json::Value root;
cmJSONState state(metricsFile, &root);
if (state.errors.empty()) {
ProcessMetrics usage{};
Json::Value const& processMetrics = root["processMetrics"];
usage.ru_maxrss = processMetrics["maxRSS"].asLargestUInt();
auto userUSec = processMetrics["userTimeUSec"].asLargestUInt();
auto systemUSec = processMetrics["systemTimeUSec"].asLargestUInt();
usage.ru_utime.tv_sec = static_cast<long>(userUSec / 1000000ULL);
usage.ru_utime.tv_usec = static_cast<long>(userUSec % 1000000ULL);
usage.ru_stime.tv_sec = static_cast<long>(systemUSec / 1000000ULL);
usage.ru_stime.tv_usec = static_cast<long>(systemUSec % 1000000ULL);
this->ProcessResourceUsage = usage;
}
cmSystemTools::RemoveFile(metricsFile);
}
}
cm::optional<cmInstrumentation::ProcessMetrics> processMetrics;
if (this->ProcessResourceUsage) {
cmInstrumentation::ProcessMetrics metrics;
metrics.ru_maxrss = this->ProcessResourceUsage->ru_maxrss;
metrics.ru_utime.tv_sec = this->ProcessResourceUsage->ru_utime.tv_sec;
metrics.ru_utime.tv_usec = this->ProcessResourceUsage->ru_utime.tv_usec;
metrics.ru_stime.tv_sec = this->ProcessResourceUsage->ru_stime.tv_sec;
metrics.ru_stime.tv_usec = this->ProcessResourceUsage->ru_stime.tv_usec;
processMetrics = metrics;
}
std::string data_file = this->CTest->GetInstrumentation().InstrumentTest(
this->TestProperties->Name, this->ActualCommand, this->Arguments,
this->TestProcess->GetExitValue(), this->TestProcess->GetStartTime(),
this->TestProperties->Name, this->InstrumentationCommand,
this->InstrumentationArguments, this->TestProcess->GetExitValue(),
this->TestProcess->GetStartTime(),
this->TestProcess->GetSystemStartTime(),
this->GetCTest()->GetConfigType(), this->ProcessOutput);
this->GetCTest()->GetConfigType(), this->ProcessOutput, processMetrics);
this->TestResult.InstrumentationFile = data_file;
}
this->MultiTestHandler.FinishTestProcess(this->TestProcess->GetRunner(),
+19
View File
@@ -11,6 +11,8 @@
#include <string>
#include <vector>
#include <cm/optional>
#include "cmCTest.h"
#include "cmCTestMultiProcessHandler.h"
#include "cmCTestTestHandler.h"
@@ -107,6 +109,19 @@ public:
}
private:
struct TimeValue
{
long tv_sec;
long tv_usec;
};
struct ProcessMetrics
{
TimeValue ru_utime{};
TimeValue ru_stime{};
long ru_maxrss = 0;
};
bool NeedsToRepeat();
void ParseOutputForMeasurements();
void ExeNotFound(std::string exe);
@@ -115,6 +130,7 @@ private:
// Run post processing of the process output for MemCheck
void MemCheckPostProcess();
std::string GenerateLLVMPath(std::string fileString);
std::string GetTestMetricsFile() const;
void CollectLLVMCoverage();
void SetupResourcesEnvironment(cmEnvironment& env);
@@ -134,6 +150,9 @@ private:
std::string StartTime;
std::string ActualCommand;
std::vector<std::string> Arguments;
std::string InstrumentationCommand;
std::vector<std::string> InstrumentationArguments;
cm::optional<ProcessMetrics> ProcessResourceUsage;
bool UseAllocatedResources = false;
std::vector<std::map<
std::string, std::vector<cmCTestMultiProcessHandler::ResourceAllocation>>>
+1 -1
View File
@@ -2776,7 +2776,7 @@ int cmCTest::ExecuteTests(std::vector<std::string> const& args)
return instrumentation.InstrumentCommand(
"ctest", args,
[&processHandler]() -> cmInstrumentation::CommandResult {
return { processHandler(), cm::nullopt, cm::nullopt };
return { processHandler(), cm::nullopt, cm::nullopt, cm::nullopt };
},
data);
});
+30 -2
View File
@@ -50,6 +50,7 @@ using LoadQueriesAfter = cmInstrumentation::LoadQueriesAfter;
namespace {
cmInstrumentationQuery::Version latestDataVersion =
cmInstrumentationQuery::LatestDataVersion();
}
std::map<std::string, std::string> cmInstrumentation::cdashSnippetsMap = {
@@ -605,7 +606,8 @@ std::string cmInstrumentation::InstrumentTest(
std::vector<std::string> const& args, int64_t result,
std::chrono::steady_clock::time_point steadyStart,
std::chrono::system_clock::time_point systemStart, std::string config,
cm::optional<std::string> output)
cm::optional<std::string> output,
cm::optional<ProcessMetrics> processMetrics)
{
// Store command info
Json::Value root(this->preTestStats);
@@ -620,6 +622,11 @@ std::string cmInstrumentation::InstrumentTest(
root["stdout"] = output ? *output : "";
root["stderr"] = "";
}
if (this->HasOption(cmInstrumentationQuery::Option::ProcessMetrics)) {
root["processMetrics"] = processMetrics
? cmInstrumentation::ResourceUsageToJSON(*processMetrics)
: Json::nullValue;
}
// Post-Command
this->InsertTimingData(root, steadyStart, systemStart);
@@ -736,6 +743,16 @@ int cmInstrumentation::InstrumentCommand(
root["stderr"] = *callbackResult.StdErr;
}
}
bool const supportsProcessMetrics = command_type == "compile" ||
command_type == "link" || command_type == "custom" ||
command_type == "install" || command_type == "test";
if (this->HasOption(cmInstrumentationQuery::Option::ProcessMetrics) &&
supportsProcessMetrics) {
root["processMetrics"] = callbackResult.ChildResourceUsage
? cmInstrumentation::ResourceUsageToJSON(
*callbackResult.ChildResourceUsage)
: Json::nullValue;
}
// Exit early if configure didn't generate a query
if (reloadQueriesAfterCommand == LoadQueriesAfter::Yes) {
@@ -986,7 +1003,7 @@ int cmInstrumentation::CollectTimingAfterBuild(int ppid)
int ret = this->InstrumentCommand(
"build", {},
[waitForBuild]() -> cmInstrumentation::CommandResult {
return { waitForBuild(), cm::nullopt, cm::nullopt };
return { waitForBuild(), cm::nullopt, cm::nullopt, cm::nullopt };
},
cm::nullopt, cm::nullopt, LoadQueriesAfter::Yes);
this->buildLock.Release();
@@ -1251,6 +1268,17 @@ void cmInstrumentation::WriteTraceFile(Json::Value const& index,
}
}
Json::Value cmInstrumentation::ResourceUsageToJSON(ProcessMetrics const& usage)
{
Json::Value root(Json::objectValue);
root["maxRSS"] = static_cast<Json::Value::UInt64>(usage.ru_maxrss);
root["userTimeUSec"] = static_cast<Json::Value::UInt64>(
usage.ru_utime.tv_sec * 1000000ULL + usage.ru_utime.tv_usec);
root["systemTimeUSec"] = static_cast<Json::Value::UInt64>(
usage.ru_stime.tv_sec * 1000000ULL + usage.ru_stime.tv_usec);
return root;
}
Json::Value cmInstrumentation::BuildTraceEvent(std::vector<uint64_t>& workers,
Json::Value const& snippetData)
{
+33 -9
View File
@@ -16,11 +16,11 @@
#include <cm3p/json/value.h>
#include <stddef.h>
#include <stdint.h>
#ifndef CMAKE_BOOTSTRAP
# include <cmsys/SystemInformation.hxx>
#endif
#include <stdint.h>
#include "cmFileLock.h"
#include "cmInstrumentationQuery.h"
@@ -45,11 +45,35 @@ public:
LoadQueriesAfter loadQueries = LoadQueriesAfter::Yes);
void LoadQueries();
void CheckCDashVariable();
struct TimeValue
{
long tv_sec;
long tv_usec;
};
struct ProcessMetrics
{
TimeValue ru_utime{};
TimeValue ru_stime{};
long ru_maxrss = 0;
#ifndef CMAKE_BOOTSTRAP
ProcessMetrics() = default;
ProcessMetrics(cmsys::SystemInformation::ProcessResourceUsage const& usage)
: ru_utime{ usage.ru_utime.tv_sec, usage.ru_utime.tv_usec }
, ru_stime{ usage.ru_stime.tv_sec, usage.ru_stime.tv_usec }
, ru_maxrss(usage.ru_maxrss)
{
}
#endif
};
struct CommandResult
{
int ExitCode;
cm::optional<std::string> StdOut;
cm::optional<std::string> StdErr;
cm::optional<ProcessMetrics> ChildResourceUsage;
};
int InstrumentCommand(
@@ -59,14 +83,13 @@ public:
cm::optional<std::map<std::string, std::string>> arrayOptions =
cm::nullopt,
LoadQueriesAfter reloadQueriesAfterCommand = LoadQueriesAfter::No);
std::string InstrumentTest(std::string const& name,
std::string const& command,
std::vector<std::string> const& args,
int64_t result,
std::chrono::steady_clock::time_point steadyStart,
std::chrono::system_clock::time_point systemStart,
std::string config,
cm::optional<std::string> output = cm::nullopt);
std::string InstrumentTest(
std::string const& name, std::string const& command,
std::vector<std::string> const& args, int64_t result,
std::chrono::steady_clock::time_point steadyStart,
std::chrono::system_clock::time_point systemStart, std::string config,
cm::optional<std::string> output = cm::nullopt,
cm::optional<ProcessMetrics> processMetrics = cm::nullopt);
void GetPreTestStats();
bool HasQuery() const;
bool HasOption(cmInstrumentationQuery::Option option) const;
@@ -127,6 +150,7 @@ private:
static std::string ComputeSuffixTime(
cm::optional<std::chrono::system_clock::time_point> time = cm::nullopt);
static bool IsInstrumentableTargetType(cm::TargetType type);
static Json::Value ResourceUsageToJSON(ProcessMetrics const& usage);
void PrepareDataForCDash(std::string const& data_dir,
std::string const& index_path);
std::string GetCompileTraceFile(std::vector<std::string> const& command,
+1
View File
@@ -18,6 +18,7 @@ std::vector<std::string> const cmInstrumentationQuery::OptionString{
"staticSystemInformation",
"dynamicSystemInformation",
"captureOutput",
"processMetrics",
"compileTrace",
"cdashSubmit",
"cdashVerbose",
+1
View File
@@ -17,6 +17,7 @@ public:
StaticSystemInformation,
DynamicSystemInformation,
CaptureOutput,
ProcessMetrics,
CompileTrace,
CDashSubmit,
CDashVerbose,
+13
View File
@@ -525,6 +525,19 @@ bool cmUVProcessChain::Finished() const
return this->Data->ProcessesCompleted >= this->Data->Processes.size();
}
void* cmUVProcessChain::GetNativeProcessHandle(std::size_t index) const
{
if (index >= this->Data->Processes.size()) {
return nullptr;
}
#ifdef _WIN32
return this->Data->Processes[index]->Process->process_handle;
#else
static_cast<void>(index);
return nullptr;
#endif
}
void cmUVProcessChain::Terminate()
{
this->Data->Terminate();
+1
View File
@@ -118,6 +118,7 @@ public:
std::vector<Status const*> GetStatus() const;
Status const& GetStatus(std::size_t index) const;
bool Finished() const;
void* GetNativeProcessHandle(std::size_t index) const;
/** Terminate any remaining child processes.
Call this only after exiting the event loop, and at most once. */
+3 -3
View File
@@ -2809,7 +2809,7 @@ int cmake::ActualConfigure()
int ret = this->Instrumentation->InstrumentCommand(
"configure", this->cmdArgs,
[doConfigure]() -> cmInstrumentation::CommandResult {
return { doConfigure(), cm::nullopt, cm::nullopt };
return { doConfigure(), cm::nullopt, cm::nullopt, cm::nullopt };
},
cm::nullopt, cm::nullopt,
this->GetIsInTryCompile() ? cmInstrumentation::LoadQueriesAfter::No
@@ -3212,7 +3212,7 @@ int cmake::Generate()
int ret = this->Instrumentation->InstrumentCommand(
"generate", this->cmdArgs,
[doGenerate]() -> cmInstrumentation::CommandResult {
return { doGenerate(), cm::nullopt, cm::nullopt };
return { doGenerate(), cm::nullopt, cm::nullopt, cm::nullopt };
});
if (ret != 0) {
return ret;
@@ -4159,7 +4159,7 @@ int cmake::Build(cmBuildArgs buildArgs, std::vector<std::string> targets,
return instrumentation.InstrumentCommand(
"cmakeBuild", args,
[&doBuild]() -> cmInstrumentation::CommandResult {
return { doBuild(), cm::nullopt, cm::nullopt };
return { doBuild(), cm::nullopt, cm::nullopt, cm::nullopt };
});
});
int buildresult = buildOutcome.ExitCode;
+1 -1
View File
@@ -1004,7 +1004,7 @@ int do_install(int ac, char const* const* av)
return instrumentation.InstrumentCommand(
"cmakeInstall", cmd,
[&doInstall]() -> cmInstrumentation::CommandResult {
return { doInstall(), cm::nullopt, cm::nullopt };
return { doInstall(), cm::nullopt, cm::nullopt, cm::nullopt };
});
});
ret = installOutcome.ExitCode;
+4
View File
@@ -210,6 +210,10 @@ int main(int argc, char const* const* argv)
return cmCTestLaunch::Main(argc, argv, cmCTestLaunch::Op::Instrument);
}
if (argc >= 2 && strcmp(argv[1], "--instrument-test") == 0) {
return cmCTestLaunch::Main(argc, argv, cmCTestLaunch::Op::InstrumentTest);
}
// Dispatch post-build instrumentation daemon for ninja
if (argc == 3 && strcmp(argv[1], "--start-instrumentation") == 0) {
return cmInstrumentation(argv[2]).SpawnBuildDaemon();
@@ -25,6 +25,7 @@ function(instrument test)
"STATIC_QUERY"
"DYNAMIC_QUERY"
"CAPTURE_OUTPUT_QUERY"
"PROCESS_METRICS_QUERY"
"COMPILE_TRACE_QUERY"
"COMPILE_TRACE_QUERY_NULL"
"TRACE_QUERY"
@@ -87,6 +88,7 @@ function(instrument test)
endif()
set(ARGS_COMPILE_TRACE_QUERY ${ARGS_COMPILE_TRACE_QUERY} PARENT_SCOPE)
set(ARGS_COMPILE_TRACE_QUERY_NULL ${ARGS_COMPILE_TRACE_QUERY_NULL} PARENT_SCOPE)
set(ARGS_PROCESS_METRICS_QUERY ${ARGS_PROCESS_METRICS_QUERY} PARENT_SCOPE)
set(GET_HOOK
"\\\"${CMAKE_COMMAND}\\\""
"-DSTATIC_QUERY=${static_query_hook_arg}"
@@ -145,6 +147,12 @@ function(instrument test)
if (ARGS_FAIL)
list(APPEND ARGS_CONFIGURE_ARGS "-DFAIL=ON")
endif()
if (ARGS_PROCESS_METRICS_QUERY)
list(APPEND ARGS_CONFIGURE_ARGS "-DPROCESS_METRICS_QUERY=ON")
if (Python_EXECUTABLE)
list(APPEND ARGS_CONFIGURE_ARGS "-DPython_EXECUTABLE=${Python_EXECUTABLE}")
endif()
endif()
if (ARGS_DISABLE_TEST)
list(APPEND ARGS_CONFIGURE_ARGS "-DDISABLE_TEST=ON")
endif()
@@ -619,6 +627,14 @@ instrument(cmake-command-capture-output
CHECK_SCRIPT check-data-dir.cmake
)
# Test process metrics
if (Python_EXECUTABLE)
instrument(cmake-command-process-metrics
BUILD INSTALL INSTALL_PARALLEL TEST PROCESS_METRICS_QUERY
CHECK_SCRIPT check-data-dir.cmake
)
endif()
# Test compile trace collection
if (CMAKE_C_COMPILER_ID STREQUAL "AppleClang")
if (CMAKE_C_COMPILER_VERSION VERSION_LESS 11.1)
@@ -32,6 +32,65 @@ foreach(snippet IN LISTS snippets)
endif()
endif()
if (ARGS_PROCESS_METRICS_QUERY AND filename MATCHES "^(compile|link|custom|test|install)-")
json_has_key("${snippet}" "${contents}" processMetrics)
string(JSON process_metrics GET "${contents}" processMetrics)
json_has_key("${snippet}" "${process_metrics}" maxRSS)
json_has_key("${snippet}" "${process_metrics}" userTimeUSec)
json_has_key("${snippet}" "${process_metrics}" systemTimeUSec)
set(process_metrics_case "")
if (filename MATCHES "^custom-")
string(JSON outputs ERROR_VARIABLE noOutputs GET "${contents}" outputs)
if (outputs MATCHES "process_metrics_custom\.stamp")
set(process_metrics_case "memory")
elseif (outputs MATCHES "process_metrics_cpu_custom\.stamp")
set(process_metrics_case "cpu")
endif()
elseif (filename MATCHES "^test-")
string(JSON user_time_usec GET "${process_metrics}" userTimeUSec)
if (user_time_usec LESS 0)
json_error("${snippet}"
"Expected process-metrics test userTimeUSec >= 0, got: ${user_time_usec}"
)
endif()
string(JSON test_name GET "${contents}" testName)
if (test_name STREQUAL "process_metrics_memory_test")
set(process_metrics_case "memory")
elseif (test_name STREQUAL "process_metrics_cpu_test")
set(process_metrics_case "cpu")
endif()
endif()
if (process_metrics_case STREQUAL "memory")
string(JSON max_rss GET "${process_metrics}" maxRSS)
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "CYGWIN")
# MaxRSS does not seem to be affected by Python bytearray allocation.
set(minMaxRSS 8192)
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "SunOS")
# MaxRSS does not seem to be populated.
set(minMaxRSS 0)
else()
set(minMaxRSS 32768)
endif()
if (max_rss LESS ${minMaxRSS})
json_error("${snippet}"
"Expected process-metrics memory workload maxRSS >= ${minMaxRSS} KiB, got: ${max_rss}"
)
endif()
elseif (process_metrics_case STREQUAL "cpu")
string(JSON user_time_usec GET "${process_metrics}" userTimeUSec)
string(JSON system_time_usec GET "${process_metrics}" systemTimeUSec)
math(EXPR total_cpu_usec "${user_time_usec} + ${system_time_usec}")
if (total_cpu_usec LESS 10000)
json_error("${snippet}"
"Expected process-metrics CPU workload user+system time >= 10000 us, got user=${user_time_usec} system=${system_time_usec}"
)
endif()
endif()
else()
json_missing_key("${snippet}" "${contents}" processMetrics)
endif()
# Verify target
string(JSON target ERROR_VARIABLE noTarget GET "${contents}" target)
if (target)
@@ -123,7 +182,7 @@ foreach(snippet IN LISTS snippets)
endif()
json_missing_key("${snippet}" "${contents}" target)
# unrecognized outputs
elseif (NOT outputs MATCHES "shell_redirect\\.out|output3")
elseif (NOT outputs MATCHES "shell_redirect\\.out|output3|process_metrics_(cpu_)?custom\\.stamp")
json_error("${snippet}" "Custom command has unexpected outputs\n${outputs}")
endif()
endif()
@@ -143,7 +202,7 @@ foreach(snippet IN LISTS snippets)
json_error("${snippet}" "Unexpected test name: ${testName}")
endif()
else()
if (NOT testName STREQUAL "test")
if (NOT testName MATCHES "test|process_metrics_(memory|cpu)_test")
json_error("${snippet}" "Unexpected test name: ${testName}")
endif()
if (NOT result EQUAL 0)
@@ -24,6 +24,22 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E true
OUTPUT output1 output2
)
if (PROCESS_METRICS_QUERY)
add_custom_command(
OUTPUT process_metrics_custom.stamp
COMMAND ${Python_EXECUTABLE}
${CMAKE_CURRENT_LIST_DIR}/process_metrics_memory.py
${CMAKE_CURRENT_BINARY_DIR}/process_metrics_custom.stamp
VERBATIM
)
add_custom_command(
OUTPUT process_metrics_cpu_custom.stamp
COMMAND ${Python_EXECUTABLE}
${CMAKE_CURRENT_LIST_DIR}/process_metrics_cpu.py
${CMAKE_CURRENT_BINARY_DIR}/process_metrics_cpu_custom.stamp
VERBATIM
)
endif()
add_custom_command(
COMMAND $<TARGET_FILE:main>
OUTPUT output3
@@ -53,8 +69,20 @@ set_property(SOURCE output1 output2 output3 PROPERTY SYMBOLIC 1)
add_custom_target(customTarget ALL
COMMAND ${CMAKE_COMMAND} -E true
DEPENDS output1 output3 shell_redirect.out
$<$<BOOL:${PROCESS_METRICS_QUERY}>:process_metrics_custom.stamp>
$<$<BOOL:${PROCESS_METRICS_QUERY}>:process_metrics_cpu_custom.stamp>
)
add_test(NAME test COMMAND $<TARGET_FILE:main>)
if (PROCESS_METRICS_QUERY)
add_test(NAME process_metrics_memory_test
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/process_metrics_memory.py
${CMAKE_CURRENT_BINARY_DIR}/process_metrics_memory_test.stamp
)
add_test(NAME process_metrics_cpu_test
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/process_metrics_cpu.py
${CMAKE_CURRENT_BINARY_DIR}/process_metrics_cpu_test.stamp
)
endif()
if(DISABLE_TEST)
set_tests_properties(test PROPERTIES DISABLED TRUE)
endif()
@@ -0,0 +1,31 @@
import multiprocessing
import sys
WORK = 4000000
def work():
x = 0
for i in range(WORK):
x += i * i
def main():
procs = []
for _ in range(2):
proc = multiprocessing.Process(target=work)
proc.start()
procs.append(proc)
for proc in procs:
proc.join()
if proc.exitcode != 0:
return proc.exitcode
open(sys.argv[1], "ab").close()
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,13 @@
import os
import sys
def main():
data = bytearray(64 * 1024 * 1024)
data[::4096] = b"x" * ((len(data) + 4095) // 4096)
open(sys.argv[1], "ab").close()
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,6 @@
set_property(GLOBAL PROPERTY INSTALL_PARALLEL ON)
cmake_instrumentation(
API_VERSION 1
DATA_VERSION 1.2
OPTIONS processMetrics
)