cmInstrumentation.cxx 27 KB

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