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