/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 | 0 | this->LockIndexing(); |
352 | | |
353 | | // Touch index file immediately to claim snippets |
354 | 0 | std::string suffix_time = ComputeSuffixTime(); |
355 | 0 | std::string const& index_name = cmStrCat("index-", suffix_time, ".json"); |
356 | 0 | std::string index_path = cmStrCat(this->dataDir, "/index/", index_name); |
357 | 0 | cmSystemTools::Touch(index_path, true); |
358 | | |
359 | | // Gather Snippets |
360 | 0 | using snippet = std::pair<std::string, std::string>; |
361 | 0 | std::vector<snippet> files; |
362 | 0 | cmsys::Directory d; |
363 | 0 | std::string last_index_name = |
364 | 0 | this->GetFileByTimestamp(LatestOrOldest::Latest, "index", index_name); |
365 | 0 | if (d.Load(this->dataDir)) { |
366 | 0 | for (unsigned int i = 0; i < d.GetNumberOfFiles(); i++) { |
367 | 0 | std::string fpath = d.GetFilePath(i); |
368 | 0 | std::string const& fname = d.GetFileName(i); |
369 | 0 | if (fname.rfind('.', 0) == 0 || d.FileIsDirectory(i)) { |
370 | 0 | continue; |
371 | 0 | } |
372 | 0 | files.push_back(snippet(fname, std::move(fpath))); |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | // Build Json Object |
377 | 0 | Json::Value index(Json::objectValue); |
378 | 0 | index["snippets"] = Json::arrayValue; |
379 | 0 | index["hook"] = cmInstrumentationQuery::HookString[hook]; |
380 | 0 | index["dataDir"] = this->dataDir; |
381 | 0 | index["buildDir"] = this->binaryDir; |
382 | 0 | index["version"] = 1; |
383 | 0 | if (this->HasOption( |
384 | 0 | cmInstrumentationQuery::Option::StaticSystemInformation)) { |
385 | 0 | this->InsertStaticSystemInformation(index); |
386 | 0 | } |
387 | 0 | for (auto const& file : files) { |
388 | 0 | if (last_index_name.empty()) { |
389 | 0 | index["snippets"].append(file.first); |
390 | 0 | } else { |
391 | 0 | int compare; |
392 | 0 | std::string last_index_path = |
393 | 0 | cmStrCat(this->dataDir, "/index/", last_index_name); |
394 | 0 | cmSystemTools::FileTimeCompare(file.second, last_index_path, &compare); |
395 | 0 | if (compare == 1) { |
396 | 0 | index["snippets"].append(file.first); |
397 | 0 | } |
398 | 0 | } |
399 | 0 | } |
400 | | |
401 | | // Parse snippets into the Google trace file |
402 | 0 | if (this->HasOption(cmInstrumentationQuery::Option::Trace)) { |
403 | 0 | std::string trace_name = cmStrCat("trace-", suffix_time, ".json"); |
404 | 0 | this->WriteTraceFile(index, trace_name); |
405 | 0 | index["trace"] = cmStrCat("trace/", trace_name); |
406 | 0 | } |
407 | | |
408 | | // Write index file |
409 | 0 | this->WriteInstrumentationJson(index, "data/index", index_name); |
410 | | |
411 | | // Execute callbacks |
412 | 0 | for (auto& cb : this->callbacks) { |
413 | 0 | cmSystemTools::RunSingleCommand(cmStrCat(cb, " \"", index_path, '"'), |
414 | 0 | nullptr, nullptr, nullptr, nullptr, |
415 | 0 | cmSystemTools::OUTPUT_PASSTHROUGH); |
416 | 0 | } |
417 | | |
418 | | // Special case for CDash collation |
419 | 0 | if (this->HasOption(cmInstrumentationQuery::Option::CDashSubmit)) { |
420 | 0 | this->PrepareDataForCDash(this->dataDir, index_path); |
421 | 0 | } |
422 | | |
423 | | // Delete files |
424 | 0 | for (auto const& f : index["snippets"]) { |
425 | 0 | cmSystemTools::RemoveFile(cmStrCat(this->dataDir, '/', f.asString())); |
426 | 0 | } |
427 | 0 | cmSystemTools::RemoveFile(index_path); |
428 | | |
429 | | // Delete old content and trace files |
430 | 0 | this->RemoveOldFiles("content"); |
431 | 0 | this->RemoveOldFiles("trace"); |
432 | |
|
433 | 0 | this->indexLock.Release(); |
434 | |
|
435 | 0 | return 0; |
436 | 0 | } |
437 | | |
438 | | void cmInstrumentation::InsertDynamicSystemInformation( |
439 | | Json::Value& root, std::string const& prefix) |
440 | 0 | { |
441 | 0 | Json::Value data; |
442 | 0 | double memory; |
443 | 0 | double load; |
444 | 0 | this->GetDynamicSystemInformation(memory, load); |
445 | 0 | if (!root.isMember("dynamicSystemInformation")) { |
446 | 0 | root["dynamicSystemInformation"] = Json::objectValue; |
447 | 0 | } |
448 | 0 | root["dynamicSystemInformation"][cmStrCat(prefix, "HostMemoryUsed")] = |
449 | 0 | memory; |
450 | 0 | root["dynamicSystemInformation"][cmStrCat(prefix, "CPULoadAverage")] = |
451 | 0 | load > 0 ? Json::Value(load) : Json::nullValue; |
452 | 0 | } |
453 | | |
454 | | void cmInstrumentation::GetDynamicSystemInformation(double& memory, |
455 | | double& load) |
456 | 0 | { |
457 | 0 | cmsys::SystemInformation& info = this->GetSystemInformation(); |
458 | 0 | if (!this->ranSystemChecks) { |
459 | 0 | info.RunCPUCheck(); |
460 | 0 | info.RunMemoryCheck(); |
461 | 0 | this->ranSystemChecks = true; |
462 | 0 | } |
463 | 0 | memory = (double)info.GetHostMemoryUsed(); |
464 | 0 | load = info.GetLoadAverage(); |
465 | 0 | } |
466 | | |
467 | | void cmInstrumentation::InsertStaticSystemInformation(Json::Value& root) |
468 | 0 | { |
469 | 0 | cmsys::SystemInformation& info = this->GetSystemInformation(); |
470 | 0 | if (!this->ranOSCheck) { |
471 | 0 | info.RunOSCheck(); |
472 | 0 | this->ranOSCheck = true; |
473 | 0 | } |
474 | 0 | if (!this->ranSystemChecks) { |
475 | 0 | info.RunCPUCheck(); |
476 | 0 | info.RunMemoryCheck(); |
477 | 0 | this->ranSystemChecks = true; |
478 | 0 | } |
479 | 0 | Json::Value infoRoot; |
480 | 0 | infoRoot["familyId"] = info.GetFamilyID(); |
481 | 0 | infoRoot["hostname"] = info.GetHostname(); |
482 | 0 | infoRoot["is64Bits"] = info.Is64Bits(); |
483 | 0 | infoRoot["modelId"] = info.GetModelID(); |
484 | 0 | infoRoot["modelName"] = info.GetModelName(); |
485 | 0 | infoRoot["numberOfLogicalCPU"] = info.GetNumberOfLogicalCPU(); |
486 | 0 | infoRoot["numberOfPhysicalCPU"] = info.GetNumberOfPhysicalCPU(); |
487 | 0 | infoRoot["OSName"] = info.GetOSName(); |
488 | 0 | infoRoot["OSPlatform"] = info.GetOSPlatform(); |
489 | 0 | infoRoot["OSRelease"] = info.GetOSRelease(); |
490 | 0 | infoRoot["OSVersion"] = info.GetOSVersion(); |
491 | 0 | infoRoot["processorAPICID"] = info.GetProcessorAPICID(); |
492 | 0 | infoRoot["processorCacheSize"] = info.GetProcessorCacheSize(); |
493 | 0 | infoRoot["processorClockFrequency"] = |
494 | 0 | (double)info.GetProcessorClockFrequency(); |
495 | 0 | infoRoot["processorName"] = info.GetExtendedProcessorName(); |
496 | 0 | infoRoot["totalPhysicalMemory"] = |
497 | 0 | static_cast<Json::Value::UInt64>(info.GetTotalPhysicalMemory()); |
498 | 0 | infoRoot["totalVirtualMemory"] = |
499 | 0 | static_cast<Json::Value::UInt64>(info.GetTotalVirtualMemory()); |
500 | 0 | infoRoot["vendorID"] = info.GetVendorID(); |
501 | 0 | infoRoot["vendorString"] = info.GetVendorString(); |
502 | | |
503 | | // Record fields unable to be determined as null JSON objects. |
504 | 0 | for (std::string const& field : infoRoot.getMemberNames()) { |
505 | 0 | if ((infoRoot[field].isNumeric() && infoRoot[field].asInt64() <= 0) || |
506 | 0 | (infoRoot[field].isString() && infoRoot[field].asString().empty())) { |
507 | 0 | infoRoot[field] = Json::nullValue; |
508 | 0 | } |
509 | 0 | } |
510 | 0 | root["staticSystemInformation"] = infoRoot; |
511 | 0 | } |
512 | | |
513 | | void cmInstrumentation::InsertTimingData( |
514 | | Json::Value& root, std::chrono::steady_clock::time_point steadyStart, |
515 | | std::chrono::system_clock::time_point systemStart) |
516 | 0 | { |
517 | 0 | uint64_t timeStart = std::chrono::duration_cast<std::chrono::milliseconds>( |
518 | 0 | systemStart.time_since_epoch()) |
519 | 0 | .count(); |
520 | 0 | uint64_t duration = std::chrono::duration_cast<std::chrono::milliseconds>( |
521 | 0 | std::chrono::steady_clock::now() - steadyStart) |
522 | 0 | .count(); |
523 | 0 | root["timeStart"] = static_cast<Json::Value::UInt64>(timeStart); |
524 | 0 | root["duration"] = static_cast<Json::Value::UInt64>(duration); |
525 | 0 | } |
526 | | |
527 | | Json::Value cmInstrumentation::ReadJsonSnippet(std::string const& file_name) |
528 | 0 | { |
529 | 0 | Json::CharReaderBuilder builder; |
530 | 0 | builder["collectComments"] = false; |
531 | 0 | cmsys::ifstream ftmp( |
532 | 0 | cmStrCat(this->timingDirv1, "/data/", file_name).c_str()); |
533 | 0 | Json::Value snippetData; |
534 | 0 | builder["collectComments"] = false; |
535 | |
|
536 | 0 | if (!Json::parseFromStream(builder, ftmp, &snippetData, nullptr)) { |
537 | | #if JSONCPP_VERSION_HEXA < 0x01070300 |
538 | | snippetData = Json::Value::null; |
539 | | #else |
540 | 0 | snippetData = Json::Value::nullSingleton(); |
541 | 0 | #endif |
542 | 0 | } |
543 | |
|
544 | 0 | ftmp.close(); |
545 | 0 | return snippetData; |
546 | 0 | } |
547 | | |
548 | | void cmInstrumentation::WriteInstrumentationJson(Json::Value& root, |
549 | | std::string const& subdir, |
550 | | std::string const& file_name) |
551 | 0 | { |
552 | 0 | Json::StreamWriterBuilder wbuilder; |
553 | 0 | wbuilder["indentation"] = "\t"; |
554 | 0 | std::unique_ptr<Json::StreamWriter> JsonWriter = |
555 | 0 | std::unique_ptr<Json::StreamWriter>(wbuilder.newStreamWriter()); |
556 | 0 | std::string const& directory = cmStrCat(this->timingDirv1, '/', subdir); |
557 | 0 | cmSystemTools::MakeDirectory(directory); |
558 | |
|
559 | 0 | cmsys::ofstream ftmp(cmStrCat(directory, '/', file_name).c_str()); |
560 | 0 | if (!ftmp.good()) { |
561 | 0 | throw std::runtime_error(std::string("Unable to open: ") + file_name); |
562 | 0 | } |
563 | | |
564 | 0 | try { |
565 | 0 | JsonWriter->write(root, &ftmp); |
566 | 0 | ftmp << "\n"; |
567 | 0 | ftmp.close(); |
568 | 0 | } catch (std::ios_base::failure& fail) { |
569 | 0 | cmSystemTools::Error(cmStrCat("Failed to write JSON: ", fail.what())); |
570 | 0 | } catch (...) { |
571 | 0 | cmSystemTools::Error("Error writing JSON output for instrumentation."); |
572 | 0 | } |
573 | 0 | } |
574 | | |
575 | | std::string cmInstrumentation::InstrumentTest( |
576 | | std::string const& name, std::string const& command, |
577 | | std::vector<std::string> const& args, int64_t result, |
578 | | std::chrono::steady_clock::time_point steadyStart, |
579 | | std::chrono::system_clock::time_point systemStart, std::string config) |
580 | 0 | { |
581 | | // Store command info |
582 | 0 | Json::Value root(this->preTestStats); |
583 | 0 | std::string command_str = cmStrCat(command, ' ', GetCommandStr(args)); |
584 | 0 | root["version"] = 1; |
585 | 0 | root["command"] = command_str; |
586 | 0 | root["role"] = "test"; |
587 | 0 | root["testName"] = name; |
588 | 0 | root["result"] = static_cast<Json::Value::Int64>(result); |
589 | 0 | root["config"] = config; |
590 | 0 | root["workingDir"] = cmSystemTools::GetLogicalWorkingDirectory(); |
591 | | |
592 | | // Post-Command |
593 | 0 | this->InsertTimingData(root, steadyStart, systemStart); |
594 | 0 | if (this->HasOption( |
595 | 0 | cmInstrumentationQuery::Option::DynamicSystemInformation)) { |
596 | 0 | this->InsertDynamicSystemInformation(root, "after"); |
597 | 0 | } |
598 | |
|
599 | 0 | cmsys::SystemInformation& info = this->GetSystemInformation(); |
600 | 0 | std::chrono::system_clock::time_point endTime = |
601 | 0 | systemStart + std::chrono::milliseconds(root["duration"].asUInt64()); |
602 | 0 | std::string file_name = cmStrCat( |
603 | 0 | "test-", |
604 | 0 | this->ComputeSuffixHash(cmStrCat(command_str, info.GetProcessId())), '-', |
605 | 0 | this->ComputeSuffixTime(endTime), ".json"); |
606 | 0 | this->WriteInstrumentationJson(root, "data", file_name); |
607 | 0 | return file_name; |
608 | 0 | } |
609 | | |
610 | | void cmInstrumentation::GetPreTestStats() |
611 | 0 | { |
612 | 0 | if (this->HasOption( |
613 | 0 | cmInstrumentationQuery::Option::DynamicSystemInformation)) { |
614 | 0 | this->InsertDynamicSystemInformation(this->preTestStats, "before"); |
615 | 0 | } |
616 | 0 | } |
617 | | |
618 | | int cmInstrumentation::InstrumentCommand( |
619 | | std::string command_type, std::vector<std::string> const& command, |
620 | | std::function<int()> const& callback, |
621 | | cm::optional<std::map<std::string, std::string>> data, |
622 | | cm::optional<std::map<std::string, std::string>> arrayData, |
623 | | LoadQueriesAfter reloadQueriesAfterCommand) |
624 | 0 | { |
625 | | |
626 | | // Always begin gathering data for configure in case cmake_instrumentation |
627 | | // command creates a query |
628 | 0 | if (!this->hasQuery && reloadQueriesAfterCommand == LoadQueriesAfter::No) { |
629 | 0 | return callback(); |
630 | 0 | } |
631 | | |
632 | | // Store command info |
633 | 0 | Json::Value root(Json::objectValue); |
634 | 0 | Json::Value commandInfo(Json::objectValue); |
635 | 0 | std::string command_str = GetCommandStr(command); |
636 | |
|
637 | 0 | if (!command_str.empty()) { |
638 | 0 | root["command"] = command_str; |
639 | 0 | } |
640 | 0 | root["version"] = 1; |
641 | | |
642 | | // Pre-Command |
643 | 0 | auto steady_start = std::chrono::steady_clock::now(); |
644 | 0 | auto system_start = std::chrono::system_clock::now(); |
645 | 0 | double preConfigureMemory = 0; |
646 | 0 | double preConfigureLoad = 0; |
647 | 0 | if (this->HasOption( |
648 | 0 | cmInstrumentationQuery::Option::DynamicSystemInformation)) { |
649 | 0 | this->InsertDynamicSystemInformation(root, "before"); |
650 | 0 | } else if (reloadQueriesAfterCommand == LoadQueriesAfter::Yes) { |
651 | 0 | this->GetDynamicSystemInformation(preConfigureMemory, preConfigureLoad); |
652 | 0 | } |
653 | | |
654 | | // Execute Command |
655 | 0 | int ret = callback(); |
656 | | |
657 | | // Exit early if configure didn't generate a query |
658 | 0 | if (reloadQueriesAfterCommand == LoadQueriesAfter::Yes) { |
659 | 0 | this->LoadQueries(); |
660 | 0 | if (!this->HasQuery()) { |
661 | 0 | return ret; |
662 | 0 | } |
663 | 0 | if (this->HasOption( |
664 | 0 | cmInstrumentationQuery::Option::DynamicSystemInformation)) { |
665 | 0 | root["dynamicSystemInformation"] = Json::objectValue; |
666 | 0 | root["dynamicSystemInformation"]["beforeHostMemoryUsed"] = |
667 | 0 | preConfigureMemory; |
668 | 0 | root["dynamicSystemInformation"]["beforeCPULoadAverage"] = |
669 | 0 | preConfigureLoad; |
670 | 0 | } |
671 | 0 | } |
672 | | |
673 | | // Post-Command |
674 | 0 | this->InsertTimingData(root, steady_start, system_start); |
675 | 0 | if (this->HasOption( |
676 | 0 | cmInstrumentationQuery::Option::DynamicSystemInformation)) { |
677 | 0 | this->InsertDynamicSystemInformation(root, "after"); |
678 | 0 | } |
679 | | |
680 | | // Gather additional data |
681 | 0 | if (data.has_value()) { |
682 | 0 | for (auto const& item : data.value()) { |
683 | 0 | if (item.first == "role" && !item.second.empty()) { |
684 | 0 | command_type = item.second; |
685 | 0 | } else if (item.first == "showOnly") { |
686 | 0 | root[item.first] = item.second == "1" ? true : false; |
687 | 0 | } else if (!item.second.empty()) { |
688 | 0 | root[item.first] = item.second; |
689 | 0 | } |
690 | 0 | } |
691 | 0 | } |
692 | | |
693 | | // See SpawnBuildDaemon(); this data is currently meaningless for build. |
694 | 0 | root["result"] = command_type == "build" ? Json::nullValue : ret; |
695 | | |
696 | | // Create empty config entry if config not found |
697 | 0 | if (!root.isMember("config") && |
698 | 0 | (command_type == "compile" || command_type == "link" || |
699 | 0 | command_type == "custom" || command_type == "install")) { |
700 | 0 | root["config"] = ""; |
701 | 0 | } |
702 | |
|
703 | 0 | if (arrayData.has_value()) { |
704 | 0 | for (auto const& item : arrayData.value()) { |
705 | 0 | if (item.first == "targetLabels" && command_type != "link") { |
706 | 0 | continue; |
707 | 0 | } |
708 | 0 | root[item.first] = Json::arrayValue; |
709 | 0 | std::stringstream ss(item.second); |
710 | 0 | std::string element; |
711 | 0 | while (getline(ss, element, ',')) { |
712 | 0 | root[item.first].append(element); |
713 | 0 | } |
714 | 0 | if (item.first == "outputs") { |
715 | 0 | root["outputSizes"] = Json::arrayValue; |
716 | 0 | for (auto const& output : root["outputs"]) { |
717 | 0 | root["outputSizes"].append( |
718 | 0 | static_cast<Json::Value::UInt64>(cmSystemTools::FileLength( |
719 | 0 | cmStrCat(this->binaryDir, '/', output.asCString())))); |
720 | 0 | } |
721 | 0 | } |
722 | 0 | } |
723 | 0 | } |
724 | 0 | root["role"] = command_type; |
725 | 0 | root["workingDir"] = cmSystemTools::GetLogicalWorkingDirectory(); |
726 | |
|
727 | 0 | auto addCMakeContent = [this](Json::Value& root_) -> void { |
728 | 0 | std::string contentFile = |
729 | 0 | this->GetFileByTimestamp(LatestOrOldest::Latest, "content"); |
730 | 0 | if (!contentFile.empty()) { |
731 | 0 | root_["cmakeContent"] = cmStrCat("content/", contentFile); |
732 | 0 | } |
733 | 0 | }; |
734 | | // Don't insert path to CMake content until generate time |
735 | 0 | if (command_type != "configure") { |
736 | 0 | addCMakeContent(root); |
737 | 0 | } |
738 | | |
739 | | // Write Json |
740 | 0 | cmsys::SystemInformation& info = this->GetSystemInformation(); |
741 | 0 | std::chrono::system_clock::time_point endTime = |
742 | 0 | system_start + std::chrono::milliseconds(root["duration"].asUInt64()); |
743 | 0 | std::string const& file_name = cmStrCat( |
744 | 0 | command_type, '-', |
745 | 0 | this->ComputeSuffixHash(cmStrCat(command_str, info.GetProcessId())), '-', |
746 | 0 | this->ComputeSuffixTime(endTime), ".json"); |
747 | | |
748 | | // Don't write configure snippet until generate time |
749 | 0 | if (command_type == "configure") { |
750 | 0 | this->configureSnippetData[file_name] = root; |
751 | 0 | } else { |
752 | | // Add reference to CMake content and write out configure snippet after |
753 | | // generate |
754 | 0 | if (command_type == "generate") { |
755 | 0 | for (auto it = this->configureSnippetData.begin(); |
756 | 0 | it != this->configureSnippetData.end(); ++it) { |
757 | 0 | if (std::next(it) != this->configureSnippetData.end()) { |
758 | 0 | it->second["cmakeContent"] = Json::nullValue; |
759 | 0 | } else { |
760 | 0 | addCMakeContent(it->second); |
761 | 0 | } |
762 | 0 | this->WriteInstrumentationJson(it->second, "data", it->first); |
763 | 0 | } |
764 | 0 | this->configureSnippetData.clear(); |
765 | 0 | } |
766 | 0 | this->WriteInstrumentationJson(root, "data", file_name); |
767 | 0 | } |
768 | 0 | return ret; |
769 | 0 | } |
770 | | |
771 | | std::string cmInstrumentation::GetCommandStr( |
772 | | std::vector<std::string> const& args) |
773 | 0 | { |
774 | 0 | std::string command_str; |
775 | 0 | for (size_t i = 0; i < args.size(); ++i) { |
776 | 0 | if (args[i].find(' ') != std::string::npos) { |
777 | 0 | command_str = cmStrCat(command_str, '"', args[i], '"'); |
778 | 0 | } else { |
779 | 0 | command_str = cmStrCat(command_str, args[i]); |
780 | 0 | } |
781 | 0 | if (i < args.size() - 1) { |
782 | 0 | command_str = cmStrCat(command_str, ' '); |
783 | 0 | } |
784 | 0 | } |
785 | 0 | return command_str; |
786 | 0 | } |
787 | | |
788 | | std::string cmInstrumentation::ComputeSuffixHash( |
789 | | std::string const& command_str) |
790 | 0 | { |
791 | 0 | cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_256); |
792 | 0 | std::string hash = hasher.HashString(command_str); |
793 | 0 | hash.resize(20, '0'); |
794 | 0 | return hash; |
795 | 0 | } |
796 | | |
797 | | std::string cmInstrumentation::ComputeSuffixTime( |
798 | | cm::optional<std::chrono::system_clock::time_point> time) |
799 | 0 | { |
800 | 0 | std::chrono::milliseconds ms = |
801 | 0 | std::chrono::duration_cast<std::chrono::milliseconds>( |
802 | 0 | (time.has_value() ? time.value() : std::chrono::system_clock::now()) |
803 | 0 | .time_since_epoch()); |
804 | 0 | std::chrono::seconds s = |
805 | 0 | std::chrono::duration_cast<std::chrono::seconds>(ms); |
806 | |
|
807 | 0 | std::time_t ts = s.count(); |
808 | 0 | std::size_t tms = ms.count() % 1000; |
809 | |
|
810 | 0 | cmTimestamp cmts; |
811 | 0 | std::ostringstream ss; |
812 | 0 | ss << cmts.CreateTimestampFromTimeT(ts, "%Y-%m-%dT%H-%M-%S", true) << '-' |
813 | 0 | << std::setfill('0') << std::setw(4) << tms; |
814 | 0 | return ss.str(); |
815 | 0 | } |
816 | | |
817 | | bool cmInstrumentation::IsInstrumentableTargetType( |
818 | | cmStateEnums::TargetType type) |
819 | 0 | { |
820 | 0 | return type == cmStateEnums::TargetType::EXECUTABLE || |
821 | 0 | type == cmStateEnums::TargetType::SHARED_LIBRARY || |
822 | 0 | type == cmStateEnums::TargetType::STATIC_LIBRARY || |
823 | 0 | type == cmStateEnums::TargetType::MODULE_LIBRARY || |
824 | 0 | type == cmStateEnums::TargetType::OBJECT_LIBRARY; |
825 | 0 | } |
826 | | |
827 | | /* |
828 | | * Called by ctest --start-instrumentation. |
829 | | * |
830 | | * This creates a detached process which waits for the parent process (i.e., |
831 | | * the build system) to die before running the postBuild hook. In this way, the |
832 | | * postBuild hook triggers after every invocation of the build system, |
833 | | * regardless of whether the build passed or failed. |
834 | | */ |
835 | | int cmInstrumentation::SpawnBuildDaemon() |
836 | 0 | { |
837 | | // Do not inherit handles from the parent process, so that the daemon is |
838 | | // fully detached. This helps prevent deadlock between the two. |
839 | 0 | uv_disable_stdio_inheritance(); |
840 | | |
841 | | // preBuild Hook |
842 | 0 | if (this->LockBuildDaemon()) { |
843 | | // Release lock before spawning the build daemon, to prevent blocking it. |
844 | 0 | this->buildLock.Release(); |
845 | 0 | this->CollectTimingData(cmInstrumentationQuery::Hook::PreBuild); |
846 | 0 | } |
847 | | |
848 | | // postBuild Hook |
849 | 0 | auto ppid = uv_os_getppid(); |
850 | 0 | if (ppid) { |
851 | 0 | std::vector<std::string> args; |
852 | 0 | args.push_back(cmSystemTools::GetCTestCommand()); |
853 | 0 | args.push_back("--wait-and-collect-instrumentation"); |
854 | 0 | args.push_back(this->binaryDir); |
855 | 0 | args.push_back(std::to_string(ppid)); |
856 | 0 | auto builder = cmUVProcessChainBuilder().SetDetached().AddCommand(args); |
857 | 0 | auto chain = builder.Start(); |
858 | 0 | uv_run(&chain.GetLoop(), UV_RUN_DEFAULT); |
859 | 0 | } |
860 | 0 | return 0; |
861 | 0 | } |
862 | | |
863 | | // Prevent multiple build daemons from running simultaneously |
864 | | bool cmInstrumentation::LockBuildDaemon() |
865 | 0 | { |
866 | | // 0 = non-blocking, 0s timeout |
867 | 0 | return this->AcquireLock(".build.lock", this->buildLock, 0); |
868 | 0 | } |
869 | | |
870 | | // Prevent multiple index processes from claiming snippets simultaneously |
871 | | bool cmInstrumentation::LockIndexing() |
872 | 0 | { |
873 | 0 | return this->AcquireLock(".index.lock", this->indexLock, |
874 | | // -1 = no timeout |
875 | 0 | static_cast<unsigned long>(-1)); |
876 | 0 | } |
877 | | |
878 | | bool cmInstrumentation::AcquireLock(std::string const& lock_file, |
879 | | cmFileLock& lock, unsigned long timeout) |
880 | 0 | { |
881 | 0 | std::string const lock_path = cmStrCat(this->timingDirv1, '/', lock_file); |
882 | 0 | if (!cmSystemTools::FileExists(lock_path)) { |
883 | 0 | cmSystemTools::Touch(lock_path, true); |
884 | 0 | } |
885 | 0 | return lock.Lock(lock_path, timeout).IsOk(); |
886 | 0 | } |
887 | | |
888 | | /* |
889 | | * Always called by ctest --wait-and-collect-instrumentation in a detached |
890 | | * process. Waits for the given PID to end before running the postBuild hook. |
891 | | * |
892 | | * See SpawnBuildDaemon() |
893 | | */ |
894 | | int cmInstrumentation::CollectTimingAfterBuild(int ppid) |
895 | 0 | { |
896 | | // Check if another process is already instrumenting the build. |
897 | | // This lock will be released when the process exits at the end of the build. |
898 | 0 | if (!this->LockBuildDaemon()) { |
899 | 0 | return 0; |
900 | 0 | } |
901 | 0 | std::function<int()> waitForBuild = [ppid]() -> int { |
902 | 0 | while (0 == uv_kill(ppid, 0)) { |
903 | 0 | cmSystemTools::Delay(100); |
904 | 0 | }; |
905 | | // FIXME(#27331): Investigate a cross-platform solution to obtain the exit |
906 | | // code given the `ppid` above. |
907 | 0 | return 0; |
908 | 0 | }; |
909 | 0 | int ret = this->InstrumentCommand( |
910 | 0 | "build", {}, [waitForBuild]() { return waitForBuild(); }, cm::nullopt, |
911 | 0 | cm::nullopt, LoadQueriesAfter::Yes); |
912 | 0 | this->buildLock.Release(); |
913 | 0 | this->CollectTimingData(cmInstrumentationQuery::Hook::PostBuild); |
914 | 0 | return ret; |
915 | 0 | } |
916 | | |
917 | | void cmInstrumentation::AddHook(cmInstrumentationQuery::Hook hook) |
918 | 0 | { |
919 | 0 | this->hooks.insert(hook); |
920 | 0 | } |
921 | | |
922 | | void cmInstrumentation::AddOption(cmInstrumentationQuery::Option option) |
923 | 0 | { |
924 | 0 | this->options.insert(option); |
925 | 0 | } |
926 | | |
927 | | std::string const& cmInstrumentation::GetCDashDir() const |
928 | 0 | { |
929 | 0 | return this->cdashDir; |
930 | 0 | } |
931 | | |
932 | | std::string const& cmInstrumentation::GetDataDir() const |
933 | 0 | { |
934 | 0 | return this->dataDir; |
935 | 0 | } |
936 | | |
937 | | /** Copy the snippets referred to by an index file to a separate |
938 | | * directory where they will be parsed for submission to CDash. |
939 | | **/ |
940 | | void cmInstrumentation::PrepareDataForCDash(std::string const& data_dir, |
941 | | std::string const& index_path) |
942 | 0 | { |
943 | 0 | cmSystemTools::MakeDirectory(this->cdashDir); |
944 | 0 | cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/configure")); |
945 | 0 | cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/build")); |
946 | 0 | cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/build/commands")); |
947 | 0 | cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/build/targets")); |
948 | 0 | cmSystemTools::MakeDirectory(cmStrCat(this->cdashDir, "/test")); |
949 | |
|
950 | 0 | Json::Value root; |
951 | 0 | std::string error_msg; |
952 | 0 | cmJSONState parseState = cmJSONState(index_path, &root); |
953 | 0 | if (!parseState.errors.empty()) { |
954 | 0 | cmSystemTools::Error(parseState.GetErrorMessage(true)); |
955 | 0 | return; |
956 | 0 | } |
957 | | |
958 | 0 | if (!root.isObject()) { |
959 | 0 | error_msg = |
960 | 0 | cmStrCat("Expected index file ", index_path, " to contain an object"); |
961 | 0 | cmSystemTools::Error(error_msg); |
962 | 0 | return; |
963 | 0 | } |
964 | | |
965 | 0 | if (!root.isMember("snippets")) { |
966 | 0 | error_msg = cmStrCat("Expected index file ", index_path, |
967 | 0 | " to have a key 'snippets'"); |
968 | 0 | cmSystemTools::Error(error_msg); |
969 | 0 | return; |
970 | 0 | } |
971 | | |
972 | 0 | std::string dst_dir; |
973 | 0 | Json::Value snippets = root["snippets"]; |
974 | 0 | for (auto const& snippet : snippets) { |
975 | | // Parse the role of this snippet. |
976 | 0 | std::string snippet_str = snippet.asString(); |
977 | 0 | std::string snippet_path = cmStrCat(data_dir, '/', snippet_str); |
978 | 0 | Json::Value snippet_root; |
979 | 0 | parseState = cmJSONState(snippet_path, &snippet_root); |
980 | 0 | if (!parseState.errors.empty()) { |
981 | 0 | cmSystemTools::Error(parseState.GetErrorMessage(true)); |
982 | 0 | continue; |
983 | 0 | } |
984 | 0 | if (!snippet_root.isObject()) { |
985 | 0 | error_msg = cmStrCat("Expected snippet file ", snippet_path, |
986 | 0 | " to contain an object"); |
987 | 0 | cmSystemTools::Error(error_msg); |
988 | 0 | continue; |
989 | 0 | } |
990 | 0 | if (!snippet_root.isMember("role")) { |
991 | 0 | error_msg = cmStrCat("Expected snippet file ", snippet_path, |
992 | 0 | " to have a key 'role'"); |
993 | 0 | cmSystemTools::Error(error_msg); |
994 | 0 | continue; |
995 | 0 | } |
996 | | |
997 | 0 | std::string snippet_role = snippet_root["role"].asString(); |
998 | 0 | auto map_element = this->cdashSnippetsMap.find(snippet_role); |
999 | 0 | if (map_element == this->cdashSnippetsMap.end()) { |
1000 | 0 | std::string message = |
1001 | 0 | "Unexpected snippet type encountered: " + snippet_role; |
1002 | 0 | cmSystemTools::Message(message, "Warning"); |
1003 | 0 | continue; |
1004 | 0 | } |
1005 | | |
1006 | 0 | if (map_element->second == "skip") { |
1007 | 0 | continue; |
1008 | 0 | } |
1009 | | |
1010 | 0 | if (map_element->second == "build") { |
1011 | | // We organize snippets on a per-target basis (when possible) |
1012 | | // for Build.xml. |
1013 | 0 | if (snippet_root.isMember("target")) { |
1014 | 0 | dst_dir = cmStrCat(this->cdashDir, "/build/targets/", |
1015 | 0 | snippet_root["target"].asString()); |
1016 | 0 | cmSystemTools::MakeDirectory(dst_dir); |
1017 | 0 | } else { |
1018 | 0 | dst_dir = cmStrCat(this->cdashDir, "/build/commands"); |
1019 | 0 | } |
1020 | 0 | } else { |
1021 | 0 | dst_dir = cmStrCat(this->cdashDir, '/', map_element->second); |
1022 | 0 | } |
1023 | |
|
1024 | 0 | std::string dst = cmStrCat(dst_dir, '/', snippet_str); |
1025 | 0 | cmsys::Status copied = cmSystemTools::CopyFileAlways(snippet_path, dst); |
1026 | 0 | if (!copied) { |
1027 | 0 | error_msg = cmStrCat("Failed to copy ", snippet_path, " to ", dst); |
1028 | 0 | cmSystemTools::Error(error_msg); |
1029 | 0 | } |
1030 | 0 | } |
1031 | 0 | } |
1032 | | |
1033 | | void cmInstrumentation::WriteTraceFile(Json::Value const& index, |
1034 | | std::string const& trace_name) |
1035 | 0 | { |
1036 | 0 | std::vector<std::string> snippets = std::vector<std::string>(); |
1037 | 0 | for (auto const& f : index["snippets"]) { |
1038 | 0 | snippets.push_back(f.asString()); |
1039 | 0 | } |
1040 | | // Reverse-sort snippets by timeEnd (timeStart + duration) as a |
1041 | | // prerequisite for AssignTargetToTraceThread(). |
1042 | 0 | auto extractSnippetTimestamp = [](std::string file) -> std::string { |
1043 | 0 | cmsys::RegularExpression snippetTimeRegex( |
1044 | 0 | "[A-Za-z]+-[A-Za-z0-9]+-([0-9T\\-]+)\\.json"); |
1045 | 0 | cmsys::RegularExpressionMatch matchA; |
1046 | 0 | if (snippetTimeRegex.find(file.c_str(), matchA)) { |
1047 | 0 | return matchA.match(1); |
1048 | 0 | } |
1049 | 0 | return ""; |
1050 | 0 | }; |
1051 | 0 | std::sort( |
1052 | 0 | snippets.begin(), snippets.end(), |
1053 | 0 | [extractSnippetTimestamp](std::string snippetA, std::string snippetB) { |
1054 | 0 | return extractSnippetTimestamp(snippetA) > |
1055 | 0 | extractSnippetTimestamp(snippetB); |
1056 | 0 | }); |
1057 | |
|
1058 | 0 | std::string traceDir = cmStrCat(this->timingDirv1, "/data/trace/"); |
1059 | 0 | std::string traceFile = cmStrCat(traceDir, trace_name); |
1060 | 0 | cmSystemTools::MakeDirectory(traceDir); |
1061 | 0 | cmsys::ofstream traceStream; |
1062 | 0 | Json::StreamWriterBuilder wbuilder; |
1063 | 0 | wbuilder["indentation"] = "\t"; |
1064 | 0 | std::unique_ptr<Json::StreamWriter> jsonWriter = |
1065 | 0 | std::unique_ptr<Json::StreamWriter>(wbuilder.newStreamWriter()); |
1066 | 0 | traceStream.open(traceFile.c_str(), std::ios::out | std::ios::trunc); |
1067 | 0 | if (!traceStream.good()) { |
1068 | 0 | throw std::runtime_error(std::string("Unable to open: ") + traceFile); |
1069 | 0 | } |
1070 | 0 | traceStream << "["; |
1071 | | |
1072 | | // Append trace events from single snippets. Prefer writing to the output |
1073 | | // stream incrementally over building up a Json::arrayValue in memory for |
1074 | | // large traces. |
1075 | 0 | std::vector<uint64_t> workers = std::vector<uint64_t>(); |
1076 | 0 | Json::Value traceEvent; |
1077 | 0 | Json::Value snippetData; |
1078 | 0 | for (size_t i = 0; i < snippets.size(); i++) { |
1079 | 0 | snippetData = this->ReadJsonSnippet(snippets[i]); |
1080 | 0 | traceEvent = this->BuildTraceEvent(workers, snippetData); |
1081 | 0 | try { |
1082 | 0 | if (i > 0) { |
1083 | 0 | traceStream << ","; |
1084 | 0 | } |
1085 | 0 | jsonWriter->write(traceEvent, &traceStream); |
1086 | 0 | if (i % 50 == 0 || i == snippets.size() - 1) { |
1087 | 0 | traceStream.flush(); |
1088 | 0 | traceStream.clear(); |
1089 | 0 | } |
1090 | 0 | } catch (std::ios_base::failure& fail) { |
1091 | 0 | cmSystemTools::Error( |
1092 | 0 | cmStrCat("Failed to write to Google trace file: ", fail.what())); |
1093 | 0 | } catch (...) { |
1094 | 0 | cmSystemTools::Error("Error writing Google trace output."); |
1095 | 0 | } |
1096 | 0 | } |
1097 | |
|
1098 | 0 | try { |
1099 | 0 | traceStream << "]\n"; |
1100 | 0 | traceStream.close(); |
1101 | 0 | } catch (...) { |
1102 | 0 | cmSystemTools::Error("Error writing Google trace output."); |
1103 | 0 | } |
1104 | 0 | } |
1105 | | |
1106 | | Json::Value cmInstrumentation::BuildTraceEvent(std::vector<uint64_t>& workers, |
1107 | | Json::Value const& snippetData) |
1108 | 0 | { |
1109 | 0 | Json::Value snippetTraceEvent; |
1110 | | |
1111 | | // Provide a useful trace event name depending on what data is available |
1112 | | // from the snippet. |
1113 | 0 | std::string nameSuffix; |
1114 | 0 | if (snippetData["role"] == "compile") { |
1115 | 0 | nameSuffix = snippetData["source"].asString(); |
1116 | 0 | } else if (snippetData["role"] == "link") { |
1117 | 0 | nameSuffix = snippetData["target"].asString(); |
1118 | 0 | } else if (snippetData["role"] == "install") { |
1119 | 0 | cmCMakePath workingDir(snippetData["workingDir"].asCString()); |
1120 | 0 | nameSuffix = workingDir.GetFileName().String(); |
1121 | 0 | } else if (snippetData["role"] == "custom") { |
1122 | 0 | nameSuffix = snippetData["command"].asString(); |
1123 | 0 | } else if (snippetData["role"] == "test") { |
1124 | 0 | nameSuffix = snippetData["testName"].asString(); |
1125 | 0 | } |
1126 | 0 | if (!nameSuffix.empty()) { |
1127 | 0 | snippetTraceEvent["name"] = |
1128 | 0 | cmStrCat(snippetData["role"].asString(), ": ", nameSuffix); |
1129 | 0 | } else { |
1130 | 0 | snippetTraceEvent["name"] = snippetData["role"].asString(); |
1131 | 0 | } |
1132 | |
|
1133 | 0 | snippetTraceEvent["cat"] = snippetData["role"]; |
1134 | 0 | snippetTraceEvent["ph"] = "X"; |
1135 | 0 | snippetTraceEvent["args"] = snippetData; |
1136 | | |
1137 | | // Time in the Trace Event Format is stored in microseconds |
1138 | | // but the snippet files store time in milliseconds. |
1139 | 0 | snippetTraceEvent["ts"] = snippetData["timeStart"].asUInt64() * 1000; |
1140 | 0 | snippetTraceEvent["dur"] = snippetData["duration"].asUInt64() * 1000; |
1141 | | |
1142 | | // Assign an arbitrary PID, since this data isn't useful for the |
1143 | | // visualization in our case. |
1144 | 0 | snippetTraceEvent["pid"] = 0; |
1145 | | // Assign TID of 0 for snippets which will have other snippet data |
1146 | | // visualized "underneath" them. (For others, start from 1.) |
1147 | 0 | if (snippetData["role"] == "build" || snippetData["role"] == "cmakeBuild" || |
1148 | 0 | snippetData["role"] == "ctest" || |
1149 | 0 | snippetData["role"] == "cmakeInstall") { |
1150 | 0 | snippetTraceEvent["tid"] = 0; |
1151 | 0 | } else { |
1152 | 0 | snippetTraceEvent["tid"] = static_cast<Json::Value::UInt64>( |
1153 | 0 | AssignTargetToTraceThread(workers, snippetData["timeStart"].asUInt64(), |
1154 | 0 | snippetData["duration"].asUInt64())); |
1155 | 0 | } |
1156 | |
|
1157 | 0 | return snippetTraceEvent; |
1158 | 0 | } |
1159 | | |
1160 | | size_t cmInstrumentation::AssignTargetToTraceThread( |
1161 | | std::vector<uint64_t>& workers, uint64_t timeStart, uint64_t duration) |
1162 | 0 | { |
1163 | 0 | for (size_t i = 0; i < workers.size(); i++) { |
1164 | 0 | if (workers[i] >= timeStart + duration) { |
1165 | 0 | workers[i] = timeStart; |
1166 | 0 | return i + 1; |
1167 | 0 | } |
1168 | 0 | } |
1169 | 0 | workers.push_back(timeStart); |
1170 | 0 | return workers.size(); |
1171 | 0 | } |