cmInstrumentation.cxx 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. #include "cmInstrumentation.h"
  2. #include <algorithm>
  3. #include <chrono>
  4. #include <ctime>
  5. #include <iomanip>
  6. #include <set>
  7. #include <sstream>
  8. #include <utility>
  9. #include <cm/memory>
  10. #include <cm/optional>
  11. #include <cm3p/json/reader.h>
  12. #include <cm3p/json/version.h>
  13. #include <cm3p/json/writer.h>
  14. #include <cm3p/uv.h>
  15. #include "cmsys/Directory.hxx"
  16. #include "cmsys/FStream.hxx"
  17. #include "cmsys/SystemInformation.hxx"
  18. #include "cmCMakePath.h"
  19. #include "cmCryptoHash.h"
  20. #include "cmExperimental.h"
  21. #include "cmFileLock.h"
  22. #include "cmFileLockResult.h"
  23. #include "cmInstrumentationQuery.h"
  24. #include "cmJSONState.h"
  25. #include "cmStringAlgorithms.h"
  26. #include "cmSystemTools.h"
  27. #include "cmTimestamp.h"
  28. #include "cmUVProcessChain.h"
  29. #include "cmValue.h"
  30. using LoadQueriesAfter = cmInstrumentation::LoadQueriesAfter;
  31. std::map<std::string, std::string> cmInstrumentation::cdashSnippetsMap = {
  32. {
  33. "configure",
  34. "configure",
  35. },
  36. {
  37. "generate",
  38. "configure",
  39. },
  40. {
  41. "compile",
  42. "build",
  43. },
  44. {
  45. "link",
  46. "build",
  47. },
  48. {
  49. "custom",
  50. "build",
  51. },
  52. {
  53. "build",
  54. "skip",
  55. },
  56. {
  57. "cmakeBuild",
  58. "build",
  59. },
  60. {
  61. "cmakeInstall",
  62. "build",
  63. },
  64. {
  65. "install",
  66. "build",
  67. },
  68. {
  69. "ctest",
  70. "build",
  71. },
  72. {
  73. "test",
  74. "test",
  75. }
  76. };
  77. cmInstrumentation::cmInstrumentation(std::string const& binary_dir,
  78. LoadQueriesAfter loadQueries)
  79. {
  80. std::string const uuid =
  81. cmExperimental::DataForFeature(cmExperimental::Feature::Instrumentation)
  82. .Uuid;
  83. this->binaryDir = binary_dir;
  84. this->timingDirv1 =
  85. cmStrCat(this->binaryDir, "/.cmake/instrumentation-", uuid, "/v1");
  86. this->cdashDir = cmStrCat(this->timingDirv1, "/cdash");
  87. if (cm::optional<std::string> configDir =
  88. cmSystemTools::GetCMakeConfigDirectory()) {
  89. this->userTimingDirv1 =
  90. cmStrCat(configDir.value(), "/instrumentation-", uuid, "/v1");
  91. }
  92. if (loadQueries == LoadQueriesAfter::Yes) {
  93. this->LoadQueries();
  94. }
  95. }
  96. void cmInstrumentation::LoadQueries()
  97. {
  98. auto const readJSONQueries = [this](std::string const& dir) {
  99. if (cmSystemTools::FileIsDirectory(dir) && this->ReadJSONQueries(dir)) {
  100. this->hasQuery = true;
  101. }
  102. };
  103. readJSONQueries(cmStrCat(this->timingDirv1, "/query"));
  104. readJSONQueries(cmStrCat(this->timingDirv1, "/query/generated"));
  105. if (!this->userTimingDirv1.empty()) {
  106. readJSONQueries(cmStrCat(this->userTimingDirv1, "/query"));
  107. }
  108. }
  109. void cmInstrumentation::CheckCDashVariable()
  110. {
  111. std::string envVal;
  112. if (cmSystemTools::GetEnv("CTEST_USE_INSTRUMENTATION", envVal) &&
  113. !cmIsOff(envVal)) {
  114. if (cmSystemTools::GetEnv("CTEST_EXPERIMENTAL_INSTRUMENTATION", envVal)) {
  115. std::string const uuid = cmExperimental::DataForFeature(
  116. cmExperimental::Feature::Instrumentation)
  117. .Uuid;
  118. if (envVal == uuid) {
  119. std::set<cmInstrumentationQuery::Option> options_ = {
  120. cmInstrumentationQuery::Option::CDashSubmit,
  121. cmInstrumentationQuery::Option::DynamicSystemInformation
  122. };
  123. if (cmSystemTools::GetEnv("CTEST_USE_VERBOSE_INSTRUMENTATION",
  124. envVal) &&
  125. !cmIsOff(envVal)) {
  126. options_.insert(cmInstrumentationQuery::Option::CDashVerbose);
  127. }
  128. for (auto const& option : options_) {
  129. this->AddOption(option);
  130. }
  131. std::set<cmInstrumentationQuery::Hook> hooks_ = {
  132. cmInstrumentationQuery::Hook::PrepareForCDash
  133. };
  134. this->AddHook(cmInstrumentationQuery::Hook::PrepareForCDash);
  135. this->WriteJSONQuery(options_, hooks_, {});
  136. }
  137. }
  138. }
  139. }
  140. cmsys::SystemInformation& cmInstrumentation::GetSystemInformation()
  141. {
  142. if (!this->systemInformation) {
  143. this->systemInformation = cm::make_unique<cmsys::SystemInformation>();
  144. }
  145. return *this->systemInformation;
  146. }
  147. bool cmInstrumentation::ReadJSONQueries(std::string const& directory)
  148. {
  149. cmsys::Directory d;
  150. bool result = false;
  151. if (d.Load(directory)) {
  152. for (unsigned int i = 0; i < d.GetNumberOfFiles(); i++) {
  153. std::string fpath = d.GetFilePath(i);
  154. if (cmHasLiteralSuffix(fpath, ".json")) {
  155. result = true;
  156. this->ReadJSONQuery(fpath);
  157. }
  158. }
  159. }
  160. return result;
  161. }
  162. void cmInstrumentation::ReadJSONQuery(std::string const& file)
  163. {
  164. auto query = cmInstrumentationQuery();
  165. query.ReadJSON(file, this->errorMsg, this->options, this->hooks,
  166. this->callbacks);
  167. if (!this->errorMsg.empty()) {
  168. cmSystemTools::Error(cmStrCat(
  169. "Could not load instrumentation queries from ",
  170. cmSystemTools::GetParentDirectory(file), ":\n", this->errorMsg));
  171. }
  172. }
  173. bool cmInstrumentation::HasErrors() const
  174. {
  175. return !this->errorMsg.empty();
  176. }
  177. void cmInstrumentation::WriteJSONQuery(
  178. std::set<cmInstrumentationQuery::Option> const& options_,
  179. std::set<cmInstrumentationQuery::Hook> const& hooks_,
  180. std::vector<std::vector<std::string>> const& callbacks_)
  181. {
  182. Json::Value root;
  183. root["version"] = 1;
  184. root["options"] = Json::arrayValue;
  185. for (auto const& option : options_) {
  186. root["options"].append(cmInstrumentationQuery::OptionString[option]);
  187. }
  188. root["hooks"] = Json::arrayValue;
  189. for (auto const& hook : hooks_) {
  190. root["hooks"].append(cmInstrumentationQuery::HookString[hook]);
  191. }
  192. root["callbacks"] = Json::arrayValue;
  193. for (auto const& callback : callbacks_) {
  194. root["callbacks"].append(cmInstrumentation::GetCommandStr(callback));
  195. }
  196. this->WriteInstrumentationJson(
  197. root, "query/generated",
  198. cmStrCat("query-", this->writtenJsonQueries++, ".json"));
  199. }
  200. void cmInstrumentation::AddCustomContent(std::string const& name,
  201. Json::Value const& contents)
  202. {
  203. this->customContent[name] = contents;
  204. }
  205. void cmInstrumentation::WriteCustomContent()
  206. {
  207. if (!this->customContent.isNull()) {
  208. this->WriteInstrumentationJson(
  209. this->customContent, "data/content",
  210. cmStrCat("configure-", this->ComputeSuffixTime(), ".json"));
  211. }
  212. }
  213. std::string cmInstrumentation::GetFileByTimestamp(
  214. cmInstrumentation::LatestOrOldest order, std::string const& dataSubdir,
  215. std::string const& exclude)
  216. {
  217. std::string fullDir = cmStrCat(this->timingDirv1, "/data/", dataSubdir);
  218. std::string result;
  219. if (cmSystemTools::FileExists(fullDir)) {
  220. cmsys::Directory d;
  221. if (d.Load(fullDir)) {
  222. for (unsigned int i = 0; i < d.GetNumberOfFiles(); i++) {
  223. std::string fname = d.GetFileName(i);
  224. if (fname != "." && fname != ".." && fname != exclude &&
  225. (result.empty() ||
  226. (order == LatestOrOldest::Latest && fname > result) ||
  227. (order == LatestOrOldest::Oldest && fname < result))) {
  228. result = fname;
  229. }
  230. }
  231. }
  232. }
  233. return result;
  234. }
  235. void cmInstrumentation::RemoveOldFiles(std::string const& dataSubdir)
  236. {
  237. std::string const dataSubdirPath =
  238. cmStrCat(this->timingDirv1, "/data/", dataSubdir);
  239. std::string oldIndex =
  240. this->GetFileByTimestamp(LatestOrOldest::Oldest, "index");
  241. if (!oldIndex.empty()) {
  242. oldIndex = cmStrCat(this->timingDirv1, "/data/index/", oldIndex);
  243. }
  244. if (cmSystemTools::FileExists(dataSubdirPath)) {
  245. std::string latestFile =
  246. this->GetFileByTimestamp(LatestOrOldest::Latest, dataSubdir);
  247. cmsys::Directory d;
  248. if (d.Load(dataSubdirPath)) {
  249. for (unsigned int i = 0; i < d.GetNumberOfFiles(); i++) {
  250. std::string fname = d.GetFileName(i);
  251. std::string fpath = d.GetFilePath(i);
  252. if (fname != "." && fname != ".." && fname < latestFile) {
  253. if (!oldIndex.empty()) {
  254. int compare;
  255. cmSystemTools::FileTimeCompare(oldIndex, fpath, &compare);
  256. if (compare == 1) {
  257. continue;
  258. }
  259. }
  260. cmSystemTools::RemoveFile(fpath);
  261. }
  262. }
  263. }
  264. }
  265. }
  266. void cmInstrumentation::ClearGeneratedQueries()
  267. {
  268. std::string dir = cmStrCat(this->timingDirv1, "/query/generated");
  269. if (cmSystemTools::FileIsDirectory(dir)) {
  270. cmSystemTools::RemoveADirectory(dir);
  271. }
  272. }
  273. bool cmInstrumentation::HasQuery() const
  274. {
  275. return this->hasQuery;
  276. }
  277. bool cmInstrumentation::HasOption(cmInstrumentationQuery::Option option) const
  278. {
  279. return (this->options.find(option) != this->options.end());
  280. }
  281. bool cmInstrumentation::HasHook(cmInstrumentationQuery::Hook hook) const
  282. {
  283. return (this->hooks.find(hook) != this->hooks.end());
  284. }
  285. int cmInstrumentation::CollectTimingData(cmInstrumentationQuery::Hook hook)
  286. {
  287. // Don't run collection if hook is disabled
  288. if (hook != cmInstrumentationQuery::Hook::Manual && !this->HasHook(hook)) {
  289. return 0;
  290. }
  291. // Touch index file immediately to claim snippets
  292. std::string const& directory = cmStrCat(this->timingDirv1, "/data");
  293. std::string suffix_time = ComputeSuffixTime();
  294. std::string const& index_name = cmStrCat("index-", suffix_time, ".json");
  295. std::string index_path = cmStrCat(directory, "/index/", index_name);
  296. cmSystemTools::Touch(index_path, true);
  297. // Gather Snippets
  298. using snippet = std::pair<std::string, std::string>;
  299. std::vector<snippet> files;
  300. cmsys::Directory d;
  301. std::string last_index_name =
  302. this->GetFileByTimestamp(LatestOrOldest::Latest, "index", index_name);
  303. if (d.Load(directory)) {
  304. for (unsigned int i = 0; i < d.GetNumberOfFiles(); i++) {
  305. std::string fpath = d.GetFilePath(i);
  306. std::string fname = d.GetFile(i);
  307. if (fname.rfind('.', 0) == 0 || d.FileIsDirectory(i)) {
  308. continue;
  309. }
  310. files.push_back(snippet(std::move(fname), std::move(fpath)));
  311. }
  312. }
  313. // Build Json Object
  314. Json::Value index(Json::objectValue);
  315. index["snippets"] = Json::arrayValue;
  316. index["hook"] = cmInstrumentationQuery::HookString[hook];
  317. index["dataDir"] = directory;
  318. index["buildDir"] = this->binaryDir;
  319. index["version"] = 1;
  320. if (this->HasOption(
  321. cmInstrumentationQuery::Option::StaticSystemInformation)) {
  322. this->InsertStaticSystemInformation(index);
  323. }
  324. for (auto const& file : files) {
  325. if (last_index_name.empty()) {
  326. index["snippets"].append(file.first);
  327. } else {
  328. int compare;
  329. std::string last_index_path =
  330. cmStrCat(directory, "/index/", last_index_name);
  331. cmSystemTools::FileTimeCompare(file.second, last_index_path, &compare);
  332. if (compare == 1) {
  333. index["snippets"].append(file.first);
  334. }
  335. }
  336. }
  337. // Parse snippets into the Google trace file
  338. if (this->HasOption(cmInstrumentationQuery::Option::Trace)) {
  339. std::string trace_name = cmStrCat("trace-", suffix_time, ".json");
  340. this->WriteTraceFile(index, trace_name);
  341. index["trace"] = "trace/" + trace_name;
  342. }
  343. // Write index file
  344. this->WriteInstrumentationJson(index, "data/index", index_name);
  345. // Execute callbacks
  346. for (auto& cb : this->callbacks) {
  347. cmSystemTools::RunSingleCommand(cmStrCat(cb, " \"", index_path, '"'),
  348. nullptr, nullptr, nullptr, nullptr,
  349. cmSystemTools::OUTPUT_PASSTHROUGH);
  350. }
  351. // Special case for CDash collation
  352. if (this->HasOption(cmInstrumentationQuery::Option::CDashSubmit)) {
  353. this->PrepareDataForCDash(directory, index_path);
  354. }
  355. // Delete files
  356. for (auto const& f : index["snippets"]) {
  357. cmSystemTools::RemoveFile(cmStrCat(directory, '/', f.asString()));
  358. }
  359. cmSystemTools::RemoveFile(index_path);
  360. // Delete old content and trace files
  361. this->RemoveOldFiles("content");
  362. this->RemoveOldFiles("trace");
  363. return 0;
  364. }
  365. void cmInstrumentation::InsertDynamicSystemInformation(
  366. Json::Value& root, std::string const& prefix)
  367. {
  368. Json::Value data;
  369. double memory;
  370. double load;
  371. this->GetDynamicSystemInformation(memory, load);
  372. if (!root.isMember("dynamicSystemInformation")) {
  373. root["dynamicSystemInformation"] = Json::objectValue;
  374. }
  375. root["dynamicSystemInformation"][cmStrCat(prefix, "HostMemoryUsed")] =
  376. memory;
  377. root["dynamicSystemInformation"][cmStrCat(prefix, "CPULoadAverage")] =
  378. load > 0 ? Json::Value(load) : Json::nullValue;
  379. }
  380. void cmInstrumentation::GetDynamicSystemInformation(double& memory,
  381. double& load)
  382. {
  383. cmsys::SystemInformation& info = this->GetSystemInformation();
  384. if (!this->ranSystemChecks) {
  385. info.RunCPUCheck();
  386. info.RunMemoryCheck();
  387. this->ranSystemChecks = true;
  388. }
  389. memory = (double)info.GetHostMemoryUsed();
  390. load = info.GetLoadAverage();
  391. }
  392. void cmInstrumentation::InsertStaticSystemInformation(Json::Value& root)
  393. {
  394. cmsys::SystemInformation& info = this->GetSystemInformation();
  395. if (!this->ranOSCheck) {
  396. info.RunOSCheck();
  397. this->ranOSCheck = true;
  398. }
  399. Json::Value infoRoot;
  400. infoRoot["familyId"] = info.GetFamilyID();
  401. infoRoot["hostname"] = info.GetHostname();
  402. infoRoot["is64Bits"] = info.Is64Bits();
  403. infoRoot["modelId"] = info.GetModelID();
  404. infoRoot["numberOfLogicalCPU"] = info.GetNumberOfLogicalCPU();
  405. infoRoot["numberOfPhysicalCPU"] = info.GetNumberOfPhysicalCPU();
  406. infoRoot["OSName"] = info.GetOSName();
  407. infoRoot["OSPlatform"] = info.GetOSPlatform();
  408. infoRoot["OSRelease"] = info.GetOSRelease();
  409. infoRoot["OSVersion"] = info.GetOSVersion();
  410. infoRoot["processorAPICID"] = info.GetProcessorAPICID();
  411. infoRoot["processorCacheSize"] = info.GetProcessorCacheSize();
  412. infoRoot["processorClockFrequency"] =
  413. (double)info.GetProcessorClockFrequency();
  414. infoRoot["processorName"] = info.GetExtendedProcessorName();
  415. infoRoot["totalPhysicalMemory"] =
  416. static_cast<Json::Value::UInt64>(info.GetTotalPhysicalMemory());
  417. infoRoot["totalVirtualMemory"] =
  418. static_cast<Json::Value::UInt64>(info.GetTotalVirtualMemory());
  419. infoRoot["vendorID"] = info.GetVendorID();
  420. infoRoot["vendorString"] = info.GetVendorString();
  421. root["staticSystemInformation"] = infoRoot;
  422. }
  423. void cmInstrumentation::InsertTimingData(
  424. Json::Value& root, std::chrono::steady_clock::time_point steadyStart,
  425. std::chrono::system_clock::time_point systemStart)
  426. {
  427. uint64_t timeStart = std::chrono::duration_cast<std::chrono::milliseconds>(
  428. systemStart.time_since_epoch())
  429. .count();
  430. uint64_t duration = std::chrono::duration_cast<std::chrono::milliseconds>(
  431. std::chrono::steady_clock::now() - steadyStart)
  432. .count();
  433. root["timeStart"] = static_cast<Json::Value::UInt64>(timeStart);
  434. root["duration"] = static_cast<Json::Value::UInt64>(duration);
  435. }
  436. Json::Value cmInstrumentation::ReadJsonSnippet(std::string const& directory,
  437. std::string const& file_name)
  438. {
  439. Json::CharReaderBuilder builder;
  440. builder["collectComments"] = false;
  441. cmsys::ifstream ftmp(cmStrCat(directory, '/', file_name).c_str());
  442. Json::Value snippetData;
  443. builder["collectComments"] = false;
  444. if (!Json::parseFromStream(builder, ftmp, &snippetData, nullptr)) {
  445. #if JSONCPP_VERSION_HEXA < 0x01070300
  446. snippetData = Json::Value::null;
  447. #else
  448. snippetData = Json::Value::nullSingleton();
  449. #endif
  450. }
  451. ftmp.close();
  452. return snippetData;
  453. }
  454. void cmInstrumentation::WriteInstrumentationJson(Json::Value& root,
  455. std::string const& subdir,
  456. std::string const& file_name)
  457. {
  458. Json::StreamWriterBuilder wbuilder;
  459. wbuilder["indentation"] = "\t";
  460. std::unique_ptr<Json::StreamWriter> JsonWriter =
  461. std::unique_ptr<Json::StreamWriter>(wbuilder.newStreamWriter());
  462. std::string const& directory = cmStrCat(this->timingDirv1, '/', subdir);
  463. cmSystemTools::MakeDirectory(directory);
  464. cmsys::ofstream ftmp(cmStrCat(directory, '/', file_name).c_str());
  465. JsonWriter->write(root, &ftmp);
  466. ftmp << "\n";
  467. ftmp.close();
  468. }
  469. std::string cmInstrumentation::InstrumentTest(
  470. std::string const& name, std::string const& command,
  471. std::vector<std::string> const& args, int64_t result,
  472. std::chrono::steady_clock::time_point steadyStart,
  473. std::chrono::system_clock::time_point systemStart, std::string config)
  474. {
  475. // Store command info
  476. Json::Value root(this->preTestStats);
  477. std::string command_str = cmStrCat(command, ' ', GetCommandStr(args));
  478. root["version"] = 1;
  479. root["command"] = command_str;
  480. root["role"] = "test";
  481. root["testName"] = name;
  482. root["result"] = static_cast<Json::Value::Int64>(result);
  483. root["config"] = config;
  484. root["workingDir"] = cmSystemTools::GetLogicalWorkingDirectory();
  485. // Post-Command
  486. this->InsertTimingData(root, steadyStart, systemStart);
  487. if (this->HasOption(
  488. cmInstrumentationQuery::Option::DynamicSystemInformation)) {
  489. this->InsertDynamicSystemInformation(root, "after");
  490. }
  491. cmsys::SystemInformation& info = this->GetSystemInformation();
  492. std::string file_name = cmStrCat(
  493. "test-",
  494. this->ComputeSuffixHash(cmStrCat(command_str, info.GetProcessId())),
  495. this->ComputeSuffixTime(), ".json");
  496. this->WriteInstrumentationJson(root, "data", file_name);
  497. return file_name;
  498. }
  499. void cmInstrumentation::GetPreTestStats()
  500. {
  501. if (this->HasOption(
  502. cmInstrumentationQuery::Option::DynamicSystemInformation)) {
  503. this->InsertDynamicSystemInformation(this->preTestStats, "before");
  504. }
  505. }
  506. int cmInstrumentation::InstrumentCommand(
  507. std::string command_type, std::vector<std::string> const& command,
  508. std::function<int()> const& callback,
  509. cm::optional<std::map<std::string, std::string>> data,
  510. cm::optional<std::map<std::string, std::string>> arrayData,
  511. LoadQueriesAfter reloadQueriesAfterCommand)
  512. {
  513. // Always begin gathering data for configure in case cmake_instrumentation
  514. // command creates a query
  515. if (!this->hasQuery && reloadQueriesAfterCommand == LoadQueriesAfter::No) {
  516. return callback();
  517. }
  518. // Store command info
  519. Json::Value root(Json::objectValue);
  520. Json::Value commandInfo(Json::objectValue);
  521. std::string command_str = GetCommandStr(command);
  522. if (!command_str.empty()) {
  523. root["command"] = command_str;
  524. }
  525. root["version"] = 1;
  526. // Pre-Command
  527. auto steady_start = std::chrono::steady_clock::now();
  528. auto system_start = std::chrono::system_clock::now();
  529. double preConfigureMemory = 0;
  530. double preConfigureLoad = 0;
  531. if (this->HasOption(
  532. cmInstrumentationQuery::Option::DynamicSystemInformation)) {
  533. this->InsertDynamicSystemInformation(root, "before");
  534. } else if (reloadQueriesAfterCommand == LoadQueriesAfter::Yes) {
  535. this->GetDynamicSystemInformation(preConfigureMemory, preConfigureLoad);
  536. }
  537. // Execute Command
  538. int ret = callback();
  539. root["result"] = ret;
  540. // Write configure content if command was configure
  541. if (command_type == "configure") {
  542. this->WriteCustomContent();
  543. }
  544. // Exit early if configure didn't generate a query
  545. if (reloadQueriesAfterCommand == LoadQueriesAfter::Yes) {
  546. this->LoadQueries();
  547. if (!this->HasQuery()) {
  548. return ret;
  549. }
  550. if (this->HasOption(
  551. cmInstrumentationQuery::Option::DynamicSystemInformation)) {
  552. root["dynamicSystemInformation"] = Json::objectValue;
  553. root["dynamicSystemInformation"]["beforeHostMemoryUsed"] =
  554. preConfigureMemory;
  555. root["dynamicSystemInformation"]["beforeCPULoadAverage"] =
  556. preConfigureLoad;
  557. }
  558. }
  559. // Post-Command
  560. this->InsertTimingData(root, steady_start, system_start);
  561. if (this->HasOption(
  562. cmInstrumentationQuery::Option::DynamicSystemInformation)) {
  563. this->InsertDynamicSystemInformation(root, "after");
  564. }
  565. // Gather additional data
  566. if (data.has_value()) {
  567. for (auto const& item : data.value()) {
  568. if (item.first == "role" && !item.second.empty()) {
  569. command_type = item.second;
  570. } else if (!item.second.empty()) {
  571. root[item.first] = item.second;
  572. }
  573. }
  574. }
  575. // Create empty config entry if config not found
  576. if (!root.isMember("config") &&
  577. (command_type == "compile" || command_type == "link" ||
  578. command_type == "custom" || command_type == "install")) {
  579. root["config"] = "";
  580. }
  581. if (arrayData.has_value()) {
  582. for (auto const& item : arrayData.value()) {
  583. if (item.first == "targetLabels" && command_type != "link") {
  584. continue;
  585. }
  586. root[item.first] = Json::arrayValue;
  587. std::stringstream ss(item.second);
  588. std::string element;
  589. while (getline(ss, element, ',')) {
  590. root[item.first].append(element);
  591. }
  592. if (item.first == "outputs") {
  593. root["outputSizes"] = Json::arrayValue;
  594. for (auto const& output : root["outputs"]) {
  595. root["outputSizes"].append(
  596. static_cast<Json::Value::UInt64>(cmSystemTools::FileLength(
  597. cmStrCat(this->binaryDir, '/', output.asCString()))));
  598. }
  599. }
  600. }
  601. }
  602. root["role"] = command_type;
  603. root["workingDir"] = cmSystemTools::GetLogicalWorkingDirectory();
  604. // Add custom configure content
  605. std::string contentFile =
  606. this->GetFileByTimestamp(LatestOrOldest::Latest, "content");
  607. if (!contentFile.empty()) {
  608. root["configureContent"] = cmStrCat("content/", contentFile);
  609. }
  610. // Write Json
  611. cmsys::SystemInformation& info = this->GetSystemInformation();
  612. std::string const& file_name = cmStrCat(
  613. command_type, '-',
  614. this->ComputeSuffixHash(cmStrCat(command_str, info.GetProcessId())),
  615. this->ComputeSuffixTime(), ".json");
  616. this->WriteInstrumentationJson(root, "data", file_name);
  617. return ret;
  618. }
  619. std::string cmInstrumentation::GetCommandStr(
  620. std::vector<std::string> const& args)
  621. {
  622. std::string command_str;
  623. for (size_t i = 0; i < args.size(); ++i) {
  624. command_str = cmStrCat(command_str, '"', args[i], '"');
  625. if (i < args.size() - 1) {
  626. command_str = cmStrCat(command_str, ' ');
  627. }
  628. }
  629. return command_str;
  630. }
  631. std::string cmInstrumentation::ComputeSuffixHash(
  632. std::string const& command_str)
  633. {
  634. cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_256);
  635. std::string hash = hasher.HashString(command_str);
  636. hash.resize(20, '0');
  637. return hash;
  638. }
  639. std::string cmInstrumentation::ComputeSuffixTime()
  640. {
  641. std::chrono::milliseconds ms =
  642. std::chrono::duration_cast<std::chrono::milliseconds>(
  643. std::chrono::system_clock::now().time_since_epoch());
  644. std::chrono::seconds s =
  645. std::chrono::duration_cast<std::chrono::seconds>(ms);
  646. std::time_t ts = s.count();
  647. std::size_t tms = ms.count() % 1000;
  648. cmTimestamp cmts;
  649. std::ostringstream ss;
  650. ss << cmts.CreateTimestampFromTimeT(ts, "%Y-%m-%dT%H-%M-%S", true) << '-'
  651. << std::setfill('0') << std::setw(4) << tms;
  652. return ss.str();
  653. }
  654. /*
  655. * Called by ctest --start-instrumentation.
  656. *
  657. * This creates a detached process which waits for the parent process (i.e.,
  658. * the build system) to die before running the postBuild hook. In this way, the
  659. * postBuild hook triggers after every invocation of the build system,
  660. * regardless of whether the build passed or failed.
  661. */
  662. int cmInstrumentation::SpawnBuildDaemon()
  663. {
  664. // Do not inherit handles from the parent process, so that the daemon is
  665. // fully detached. This helps prevent deadlock between the two.
  666. uv_disable_stdio_inheritance();
  667. // preBuild Hook
  668. if (this->LockBuildDaemon()) {
  669. // Release lock before spawning the build daemon, to prevent blocking it.
  670. this->lock.Release();
  671. this->CollectTimingData(cmInstrumentationQuery::Hook::PreBuild);
  672. }
  673. // postBuild Hook
  674. auto ppid = uv_os_getppid();
  675. if (ppid) {
  676. std::vector<std::string> args;
  677. args.push_back(cmSystemTools::GetCTestCommand());
  678. args.push_back("--wait-and-collect-instrumentation");
  679. args.push_back(this->binaryDir);
  680. args.push_back(std::to_string(ppid));
  681. auto builder = cmUVProcessChainBuilder().SetDetached().AddCommand(args);
  682. auto chain = builder.Start();
  683. uv_run(&chain.GetLoop(), UV_RUN_DEFAULT);
  684. }
  685. return 0;
  686. }
  687. // Prevent multiple build daemons from running simultaneously
  688. bool cmInstrumentation::LockBuildDaemon()
  689. {
  690. std::string const lockFile = cmStrCat(this->timingDirv1, "/.build.lock");
  691. if (!cmSystemTools::FileExists(lockFile)) {
  692. cmSystemTools::Touch(lockFile, true);
  693. }
  694. return this->lock.Lock(lockFile, 0).IsOk();
  695. }
  696. /*
  697. * Always called by ctest --wait-and-collect-instrumentation in a detached
  698. * process. Waits for the given PID to end before running the postBuild hook.
  699. *
  700. * See SpawnBuildDaemon()
  701. */
  702. int cmInstrumentation::CollectTimingAfterBuild(int ppid)
  703. {
  704. // Check if another process is already instrumenting the build.
  705. // This lock will be released when the process exits at the end of the build.
  706. if (!this->LockBuildDaemon()) {
  707. return 0;
  708. }
  709. std::function<int()> waitForBuild = [ppid]() -> int {
  710. while (0 == uv_kill(ppid, 0)) {
  711. cmSystemTools::Delay(100);
  712. };
  713. return 0;
  714. };
  715. int ret = this->InstrumentCommand(
  716. "build", {}, [waitForBuild]() { return waitForBuild(); }, cm::nullopt,
  717. cm::nullopt, LoadQueriesAfter::Yes);
  718. this->CollectTimingData(cmInstrumentationQuery::Hook::PostBuild);
  719. return ret;
  720. }
  721. void cmInstrumentation::AddHook(cmInstrumentationQuery::Hook hook)
  722. {
  723. this->hooks.insert(hook);
  724. }
  725. void cmInstrumentation::AddOption(cmInstrumentationQuery::Option option)
  726. {
  727. this->options.insert(option);
  728. }
  729. std::string const& cmInstrumentation::GetCDashDir()
  730. {
  731. return this->cdashDir;
  732. }
  733. /** Copy the snippets referred to by an index file to a separate
  734. * directory where they will be parsed for submission to CDash.
  735. **/
  736. void cmInstrumentation::PrepareDataForCDash(std::string const& data_dir,
  737. std::string const& index_path)
  738. {
  739. cmSystemTools::MakeDirectory(this->cdashDir);
  740. cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/configure"));
  741. cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/build"));
  742. cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/build/commands"));
  743. cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/build/targets"));
  744. cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/test"));
  745. Json::Value root;
  746. std::string error_msg;
  747. cmJSONState parseState = cmJSONState(index_path, &root);
  748. if (!parseState.errors.empty()) {
  749. cmSystemTools::Error(parseState.GetErrorMessage(true));
  750. return;
  751. }
  752. if (!root.isObject()) {
  753. error_msg =
  754. cmStrCat("Expected index file ", index_path, " to contain an object");
  755. cmSystemTools::Error(error_msg);
  756. return;
  757. }
  758. if (!root.isMember("snippets")) {
  759. error_msg = cmStrCat("Expected index file ", index_path,
  760. " to have a key 'snippets'");
  761. cmSystemTools::Error(error_msg);
  762. return;
  763. }
  764. std::string dst_dir;
  765. Json::Value snippets = root["snippets"];
  766. for (auto const& snippet : snippets) {
  767. // Parse the role of this snippet.
  768. std::string snippet_str = snippet.asString();
  769. std::string snippet_path = cmStrCat(data_dir, '/', snippet_str);
  770. Json::Value snippet_root;
  771. parseState = cmJSONState(snippet_path, &snippet_root);
  772. if (!parseState.errors.empty()) {
  773. cmSystemTools::Error(parseState.GetErrorMessage(true));
  774. continue;
  775. }
  776. if (!snippet_root.isObject()) {
  777. error_msg = cmStrCat("Expected snippet file ", snippet_path,
  778. " to contain an object");
  779. cmSystemTools::Error(error_msg);
  780. continue;
  781. }
  782. if (!snippet_root.isMember("role")) {
  783. error_msg = cmStrCat("Expected snippet file ", snippet_path,
  784. " to have a key 'role'");
  785. cmSystemTools::Error(error_msg);
  786. continue;
  787. }
  788. std::string snippet_role = snippet_root["role"].asString();
  789. auto map_element = this->cdashSnippetsMap.find(snippet_role);
  790. if (map_element == this->cdashSnippetsMap.end()) {
  791. std::string message =
  792. "Unexpected snippet type encountered: " + snippet_role;
  793. cmSystemTools::Message(message, "Warning");
  794. continue;
  795. }
  796. if (map_element->second == "skip") {
  797. continue;
  798. }
  799. if (map_element->second == "build") {
  800. // We organize snippets on a per-target basis (when possible)
  801. // for Build.xml.
  802. if (snippet_root.isMember("target")) {
  803. dst_dir = cmStrCat(this->cdashDir, "/build/targets/",
  804. snippet_root["target"].asString());
  805. cmSystemTools::MakeDirectory(dst_dir);
  806. } else {
  807. dst_dir = cmStrCat(this->cdashDir, "/build/commands");
  808. }
  809. } else {
  810. dst_dir = cmStrCat(this->cdashDir, '/', map_element->second);
  811. }
  812. std::string dst = cmStrCat(dst_dir, '/', snippet_str);
  813. cmsys::Status copied = cmSystemTools::CopyFileAlways(snippet_path, dst);
  814. if (!copied) {
  815. error_msg = cmStrCat("Failed to copy ", snippet_path, " to ", dst);
  816. cmSystemTools::Error(error_msg);
  817. }
  818. }
  819. }
  820. void cmInstrumentation::WriteTraceFile(Json::Value const& index,
  821. std::string const& trace_name)
  822. {
  823. std::string const& directory = cmStrCat(this->timingDirv1, "/data");
  824. std::vector<Json::Value> snippets = std::vector<Json::Value>();
  825. for (auto const& f : index["snippets"]) {
  826. Json::Value snippetData = this->ReadJsonSnippet(directory, f.asString());
  827. snippets.push_back(snippetData);
  828. }
  829. // Reverse-sort snippets by timeEnd (timeStart + duration) as a
  830. // prerequisite for AssignTargetToTraceThread().
  831. std::sort(snippets.begin(), snippets.end(),
  832. [](Json::Value snippetA, Json::Value snippetB) {
  833. uint64_t timeEndA = snippetA["timeStart"].asUInt64() +
  834. snippetA["duration"].asUInt64();
  835. uint64_t timeEndB = snippetB["timeStart"].asUInt64() +
  836. snippetB["duration"].asUInt64();
  837. return timeEndA > timeEndB;
  838. });
  839. Json::Value trace = Json::arrayValue;
  840. std::vector<uint64_t> workers = std::vector<uint64_t>();
  841. for (auto const& snippetData : snippets) {
  842. this->AppendTraceEvent(trace, workers, snippetData);
  843. }
  844. this->WriteInstrumentationJson(trace, "data/trace", trace_name);
  845. }
  846. void cmInstrumentation::AppendTraceEvent(Json::Value& trace,
  847. std::vector<uint64_t>& workers,
  848. Json::Value const& snippetData)
  849. {
  850. Json::Value snippetTraceEvent;
  851. // Provide a useful trace event name depending on what data is available
  852. // from the snippet.
  853. std::string nameSuffix;
  854. if (snippetData["role"] == "compile") {
  855. nameSuffix = snippetData["source"].asString();
  856. } else if (snippetData["role"] == "link") {
  857. nameSuffix = snippetData["target"].asString();
  858. } else if (snippetData["role"] == "install") {
  859. cmCMakePath workingDir(snippetData["workingDir"].asCString());
  860. nameSuffix = workingDir.GetFileName().String();
  861. } else if (snippetData["role"] == "custom") {
  862. nameSuffix = snippetData["command"].asString();
  863. } else if (snippetData["role"] == "test") {
  864. nameSuffix = snippetData["testName"].asString();
  865. }
  866. if (!nameSuffix.empty()) {
  867. snippetTraceEvent["name"] =
  868. cmStrCat(snippetData["role"].asString(), ": ", nameSuffix);
  869. } else {
  870. snippetTraceEvent["name"] = snippetData["role"].asString();
  871. }
  872. snippetTraceEvent["cat"] = snippetData["role"];
  873. snippetTraceEvent["ph"] = "X";
  874. snippetTraceEvent["args"] = snippetData;
  875. // Time in the Trace Event Format is stored in microseconds
  876. // but the snippet files store time in milliseconds.
  877. snippetTraceEvent["ts"] = snippetData["timeStart"].asUInt64() * 1000;
  878. snippetTraceEvent["dur"] = snippetData["duration"].asUInt64() * 1000;
  879. // Assign an arbitrary PID, since this data isn't useful for the
  880. // visualization in our case.
  881. snippetTraceEvent["pid"] = 0;
  882. // Assign TID of 0 for snippets which will have other snippet data
  883. // visualized "underneath" them. (For others, start from 1.)
  884. if (snippetData["role"] == "build" || snippetData["role"] == "cmakeBuild" ||
  885. snippetData["role"] == "ctest" ||
  886. snippetData["role"] == "cmakeInstall") {
  887. snippetTraceEvent["tid"] = 0;
  888. } else {
  889. snippetTraceEvent["tid"] = static_cast<Json::Value::UInt64>(
  890. AssignTargetToTraceThread(workers, snippetData["timeStart"].asUInt64(),
  891. snippetData["duration"].asUInt64()));
  892. }
  893. trace.append(snippetTraceEvent);
  894. }
  895. size_t cmInstrumentation::AssignTargetToTraceThread(
  896. std::vector<uint64_t>& workers, uint64_t timeStart, uint64_t duration)
  897. {
  898. for (size_t i = 0; i < workers.size(); i++) {
  899. if (workers[i] >= timeStart + duration) {
  900. workers[i] = timeStart;
  901. return i + 1;
  902. }
  903. }
  904. workers.push_back(timeStart);
  905. return workers.size();
  906. }