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