Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}