Coverage Report

Created: 2026-03-12 06:35

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