mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
Instrumentation: Add Process Resource Usage Metrics
Issue: #26675, #27742
This commit is contained in:
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -18,6 +18,7 @@ std::vector<std::string> const cmInstrumentationQuery::OptionString{
|
||||
"staticSystemInformation",
|
||||
"dynamicSystemInformation",
|
||||
"captureOutput",
|
||||
"processMetrics",
|
||||
"compileTrace",
|
||||
"cdashSubmit",
|
||||
"cdashVerbose",
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
StaticSystemInformation,
|
||||
DynamicSystemInformation,
|
||||
CaptureOutput,
|
||||
ProcessMetrics,
|
||||
CompileTrace,
|
||||
CDashSubmit,
|
||||
CDashVerbose,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
Reference in New Issue
Block a user