• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

llnl / dftracer-utils / 30069185152

24 Jul 2026 05:20AM UTC coverage: 50.687% (-2.0%) from 52.66%
30069185152

Pull #99

github

web-flow
Merge 47af36077 into 06bc84ec9
Pull Request #99: Support CM time_metric (NS/MS/SEC/US) across reader and viz

18062 of 48203 branches covered (37.47%)

Branch coverage included in aggregate %.

1510 of 2205 new or added lines in 59 files covered. (68.48%)

742 existing lines in 114 files now uncovered.

23711 of 34210 relevant lines covered (69.31%)

39853.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.17
/src/dftracer/utils/server/trace_index.cpp
1
#include <dftracer/utils/core/common/filesystem.h>
2
#include <dftracer/utils/core/common/logging.h>
3
#include <dftracer/utils/core/coro/channel.h>
4
#include <dftracer/utils/core/io/io_backend.h>
5
#include <dftracer/utils/core/pipeline/pipeline.h>
6
#include <dftracer/utils/core/pipeline/pipeline_config.h>
7
#include <dftracer/utils/core/tasks/coro_scope.h>
8
#include <dftracer/utils/core/tasks/task.h>
9
#include <dftracer/utils/server/router.h>
10
#include <dftracer/utils/server/trace_index.h>
11
#include <dftracer/utils/utilities/composites/dft/internal/utils.h>
12
#include <dftracer/utils/utilities/composites/dft/metadata_collector_utility.h>
13
#include <dftracer/utils/utilities/filesystem/pattern_directory_scanner_utility.h>
14
#include <dftracer/utils/utilities/indexer/index_builder_utility.h>
15
#include <dftracer/utils/utilities/indexer/index_database.h>
16
#include <dftracer/utils/utilities/indexer/index_database_writer_context.h>
17
#include <dftracer/utils/utilities/indexer/internal/helpers.h>
18
#include <dftracer/utils/utilities/reader/trace_reader.h>
19

20
#include <cinttypes>
21
#include <cstdio>
22
#include <cstring>
23
#include <fstream>
24
#include <limits>
25
#include <memory>
26
#include <unordered_map>
27
#include <vector>
28

29
namespace dftracer::utils::server {
30

31
using namespace dftracer::utils::utilities::composites::dft;
32
using namespace dftracer::utils::utilities::composites::dft::indexing;
33
using namespace dftracer::utils::utilities::filesystem;
34
namespace indexer = dftracer::utils::utilities::indexer;
35

36
namespace {
37

38
// Bump VERSION when the on-disk layout below changes; the loader then ignores
39
// the stale cache.
40
constexpr std::uint32_t VIZ_SUMMARY_MAGIC = 0x315A5644;  // "DVZ1"
41
constexpr std::uint32_t VIZ_SUMMARY_FORMAT_VERSION = 5;
42

43
struct BufWriter {
44
    std::string b;
45
    void u32(std::uint32_t v) { b.append(reinterpret_cast<char*>(&v), 4); }
414✔
46
    void u64(std::uint64_t v) { b.append(reinterpret_cast<char*>(&v), 8); }
3,686✔
47
    void i64(std::int64_t v) { u64(static_cast<std::uint64_t>(v)); }
1,004✔
48
    void dbl(double v) { b.append(reinterpret_cast<char*>(&v), 8); }
133,694,045✔
49
    void str(const std::string& s) {
911✔
50
        u64(s.size());
911✔
51
        b.append(s);
911✔
52
    }
911✔
53
};
54

55
struct BufReader {
56
    const char* p;
57
    const char* end;
58
    bool ok = true;
59
    std::uint32_t u32() {
22✔
60
        std::uint32_t v = 0;
22✔
61
        if (!ok || p + 4 > end) {
22!
NEW
62
            ok = false;
×
NEW
63
            return 0;
×
64
        }
65
        std::memcpy(&v, p, 4);
22✔
66
        p += 4;
22✔
67
        return v;
22✔
68
    }
69
    std::uint64_t u64() {
152✔
70
        std::uint64_t v = 0;
152✔
71
        if (!ok || p + 8 > end) {
152!
NEW
72
            ok = false;
×
NEW
73
            return 0;
×
74
        }
75
        std::memcpy(&v, p, 8);
152✔
76
        p += 8;
152✔
77
        return v;
152✔
78
    }
79
    std::int64_t i64() { return static_cast<std::int64_t>(u64()); }
28✔
80
    double dbl() {
16,711,709✔
81
        double v = 0;
16,711,709✔
82
        if (!ok || p + 8 > end) {
16,711,709!
NEW
83
            ok = false;
×
NEW
84
            return 0;
×
85
        }
86
        std::memcpy(&v, p, 8);
16,711,709✔
87
        p += 8;
16,711,709✔
88
        return v;
16,711,709✔
89
    }
90
    std::string str() {
45✔
91
        std::uint64_t n = u64();
45✔
92
        if (!ok || n > static_cast<std::uint64_t>(end - p)) {
45!
NEW
93
            ok = false;
×
NEW
94
            return {};
×
95
        }
96
        std::string s(p, p + n);
45!
97
        p += n;
45✔
98
        return s;
45✔
99
    }
45✔
100
    // Guard a count before resize: each element consumes >= elem_min bytes, so
101
    // a corrupt count can't request an absurd allocation.
102
    bool fits(std::uint64_t n, std::size_t elem_min) const {
30✔
103
        if (!ok) return false;
30!
104
        std::size_t rem = static_cast<std::size_t>(end - p);
30✔
105
        return elem_min == 0 || n <= rem / elem_min;
30!
106
    }
107
};
108

109
void write_group_rows(BufWriter& w,
32✔
110
                      const std::vector<VizSummary::GroupRow>& rows) {
111
    w.u64(rows.size());
32✔
112
    for (const auto& r : rows) {
183✔
113
        w.str(r.key);
151!
114
        w.u64(r.count);
151!
115
        w.dbl(r.total);
151!
116
        w.dbl(r.min);
151!
117
        w.dbl(r.max);
151!
118
    }
119
}
32✔
120

121
bool read_group_rows(BufReader& r, std::vector<VizSummary::GroupRow>& rows) {
4✔
122
    std::uint64_t n = r.u64();
4✔
123
    if (!r.fits(n, 8 + 8 + 8 * 3)) return false;
4!
124
    rows.resize(n);
4✔
125
    for (auto& row : rows) {
11✔
126
        row.key = r.str();
7!
127
        row.count = r.u64();
7✔
128
        row.total = r.dbl();
7✔
129
        row.min = r.dbl();
7✔
130
        row.max = r.dbl();
7✔
131
    }
132
    return r.ok;
4✔
133
}
134

135
void write_spans(BufWriter& w, const std::vector<VizSummary::AppSpan>& spans) {
16✔
136
    w.u64(spans.size());
16✔
137
    for (const auto& s : spans) {
414✔
138
        w.u64(s.begin);
398!
139
        w.u64(s.end);
398!
140
        w.i64(s.pid);
398!
141
        w.i64(s.tid);
398!
142
        w.u32(s.name_id);
398!
143
        w.str(s.json);
398!
144
    }
145
}
16✔
146

147
bool read_spans(BufReader& r, std::vector<VizSummary::AppSpan>& spans) {
2✔
148
    std::uint64_t n = r.u64();
2✔
149
    if (!r.fits(n, 8 * 4 + 4 + 8)) return false;
2!
150
    spans.resize(n);
2✔
151
    for (auto& s : spans) {
14✔
152
        s.begin = r.u64();
12✔
153
        s.end = r.u64();
12✔
154
        s.pid = r.i64();
12✔
155
        s.tid = r.i64();
12✔
156
        s.name_id = r.u32();
12✔
157
        s.json = r.str();
12!
158
    }
159
    return r.ok;
2✔
160
}
161

162
void serialize_summary(BufWriter& w, const VizSummary& s) {
8✔
163
    w.u64(s.t_begin);
8✔
164
    w.u64(s.t_end);
8✔
165
    w.u64(s.nbuckets);
8✔
166
    w.dbl(s.bucket_us);
8✔
167
    w.u64(s.max_dur);
8✔
168
    w.u64(s.total_files);
8✔
169
    w.u64(s.io_files);
8✔
170

171
    w.dbl(s.long_threshold_us);
8✔
172

173
    w.u64(s.names.size());
8✔
174
    for (const auto& n : s.names) w.str(n);
40!
175

176
    w.u64(s.levels.size());
8✔
177
    for (const auto& lv : s.levels) {
40✔
178
        w.u64(lv.nbuckets);
32!
179
        w.dbl(lv.bucket_us);
32!
180
        auto wvec_lvl = [&](const std::vector<double>& v) {
96✔
181
            w.u64(v.size());
96✔
182
            for (double x : v) w.dbl(x);
133,693,536!
183
        };
96✔
184
        wvec_lvl(lv.read_bytes);
32!
185
        wvec_lvl(lv.write_bytes);
32!
186
        wvec_lvl(lv.ops);
32!
187
        w.u64(lv.lanes.size());
32!
188
        for (const auto& l : lv.lanes) {
32!
NEW
189
            w.i64(l.pid);
×
NEW
190
            w.i64(l.tid);
×
NEW
191
            w.u64(l.cells.size());
×
NEW
192
            for (std::uint32_t bk : l.buckets) w.u32(bk);
×
NEW
193
            for (const auto& c : l.cells) {
×
NEW
194
                w.u32(c.count);
×
NEW
195
                w.u64(c.total);
×
NEW
196
                w.u32(c.max_dur);
×
NEW
197
                w.u32(c.name_id);
×
198
            }
199
        }
200
    }
201

202
    write_group_rows(w, s.by_name);
8✔
203
    write_group_rows(w, s.by_cat);
8✔
204
    write_group_rows(w, s.by_pid);
8✔
205
    write_group_rows(w, s.by_fhash);
8✔
206

207
    w.u64(s.name_cats.size());
8✔
208
    for (const auto& p : s.name_cats) {
40✔
209
        w.str(p.first);
32!
210
        w.str(p.second);
32!
211
    }
212

213
    w.u64(s.columns.size());
8✔
214
    for (const auto& c : s.columns) w.str(c);
58!
215

216
    write_spans(w, s.app_spans);
8✔
217
    write_spans(w, s.long_events);
8✔
218

219
    w.u64(s.idle_gaps.size());
8✔
220
    for (const auto& g : s.idle_gaps) {
104✔
221
        w.u64(g.first);
96!
222
        w.u64(g.second);
96!
223
    }
224

225
    w.u64(s.procs.size());
8✔
226
    for (const auto& p : s.procs) {
112✔
227
        w.i64(p.pid);
104!
228
        w.i64(p.ppid);
104!
229
        w.u64(p.first_ts);
104!
230
        w.u64(p.bytes);
104!
231
        w.u64(p.io_ops);
104!
232
        w.dbl(p.io_busy);
104!
233
        w.str(p.hhash);
104!
234
        w.str(p.rank);
104!
235
    }
236

237
    w.u64(s.forks.size());
8✔
238
    for (const auto& f : s.forks) {
8!
NEW
239
        w.u64(f.ts);
×
NEW
240
        w.i64(f.pid);
×
NEW
241
        w.i64(f.child);
×
242
    }
243

244
    w.u64(s.hosts.size());
8✔
245
    for (const auto& h : s.hosts) {
8!
NEW
246
        w.str(h.first);
×
NEW
247
        w.str(h.second);
×
248
    }
249
}
8✔
250

251
bool deserialize_summary(BufReader& r, VizSummary& s) {
1✔
252
    s.t_begin = r.u64();
1✔
253
    s.t_end = r.u64();
1✔
254
    s.nbuckets = static_cast<std::size_t>(r.u64());
1✔
255
    s.bucket_us = r.dbl();
1✔
256
    s.max_dur = r.u64();
1✔
257
    s.total_files = static_cast<std::size_t>(r.u64());
1✔
258
    s.io_files = static_cast<std::size_t>(r.u64());
1✔
259

260
    s.long_threshold_us = r.dbl();
1✔
261

262
    std::uint64_t nnames = r.u64();
1✔
263
    if (!r.fits(nnames, 8)) return false;
1!
264
    s.names.resize(nnames);
1✔
265
    for (auto& n : s.names) n = r.str();
4!
266

267
    std::uint64_t nlevels = r.u64();
1✔
268
    if (!r.fits(nlevels, 8 + 8 + 8)) return false;
1!
269
    s.levels.resize(nlevels);
1✔
270
    for (auto& lv : s.levels) {
5✔
271
        lv.nbuckets = static_cast<std::size_t>(r.u64());
4✔
272
        lv.bucket_us = r.dbl();
4✔
273
        auto rvec_lvl = [&](std::vector<double>& v) -> bool {
12✔
274
            std::uint64_t n = r.u64();
12✔
275
            if (!r.fits(n, 8)) return false;
12!
276
            v.resize(n);
12✔
277
            for (auto& x : v) x = r.dbl();
16,711,692✔
278
            return r.ok;
12✔
279
        };
4✔
280
        if (!rvec_lvl(lv.read_bytes) || !rvec_lvl(lv.write_bytes) ||
8!
281
            !rvec_lvl(lv.ops))
4!
NEW
282
            return false;
×
283
        std::uint64_t nlanes = r.u64();
4✔
284
        if (!r.fits(nlanes, 8 * 2 + 8)) return false;
4!
285
        lv.lanes.resize(nlanes);
4!
286
        for (auto& l : lv.lanes) {
4!
NEW
287
            l.pid = r.i64();
×
NEW
288
            l.tid = r.i64();
×
NEW
289
            std::uint64_t ncells = r.u64();
×
NEW
290
            if (!r.fits(ncells, 4 + 4 + 8 + 4 + 4)) return false;
×
NEW
291
            l.buckets.resize(ncells);
×
NEW
292
            for (auto& bk : l.buckets) bk = r.u32();
×
NEW
293
            l.cells.resize(ncells);
×
NEW
294
            for (auto& c : l.cells) {
×
NEW
295
                c.count = r.u32();
×
NEW
296
                c.total = r.u64();
×
NEW
297
                c.max_dur = r.u32();
×
NEW
298
                c.name_id = r.u32();
×
299
            }
300
        }
301
    }
302

303
    if (!read_group_rows(r, s.by_name) || !read_group_rows(r, s.by_cat) ||
2!
304
        !read_group_rows(r, s.by_pid) || !read_group_rows(r, s.by_fhash))
2!
NEW
305
        return false;
×
306

307
    std::uint64_t ncats = r.u64();
1✔
308
    if (!r.fits(ncats, 8 * 2)) return false;
1!
309
    s.name_cats.resize(ncats);
1✔
310
    for (auto& p : s.name_cats) {
4✔
311
        p.first = r.str();
3!
312
        p.second = r.str();
3!
313
    }
314

315
    std::uint64_t ncols = r.u64();
1✔
316
    if (!r.fits(ncols, 8)) return false;
1!
317
    s.columns.resize(ncols);
1✔
318
    for (auto& c : s.columns) c = r.str();
9!
319

320
    if (!read_spans(r, s.app_spans) || !read_spans(r, s.long_events))
1!
NEW
321
        return false;
×
322

323
    std::uint64_t ngaps = r.u64();
1✔
324
    if (!r.fits(ngaps, 8 * 2)) return false;
1!
325
    s.idle_gaps.resize(ngaps);
1✔
326
    for (auto& g : s.idle_gaps) {
2✔
327
        g.first = r.u64();
1✔
328
        g.second = r.u64();
1✔
329
    }
330

331
    std::uint64_t nprocs = r.u64();
1✔
332
    if (!r.fits(nprocs, 8 * 5 + 8 * 2)) return false;
1!
333
    s.procs.resize(nprocs);
1✔
334
    for (auto& p : s.procs) {
3✔
335
        p.pid = r.i64();
2✔
336
        p.ppid = r.i64();
2✔
337
        p.first_ts = r.u64();
2✔
338
        p.bytes = r.u64();
2✔
339
        p.io_ops = r.u64();
2✔
340
        p.io_busy = r.dbl();
2✔
341
        p.hhash = r.str();
2!
342
        p.rank = r.str();
2!
343
    }
344

345
    std::uint64_t nforks = r.u64();
1✔
346
    if (!r.fits(nforks, 8 * 3)) return false;
1!
347
    s.forks.resize(nforks);
1✔
348
    for (auto& f : s.forks) {
1!
NEW
349
        f.ts = r.u64();
×
NEW
350
        f.pid = r.i64();
×
NEW
351
        f.child = r.i64();
×
352
    }
353

354
    std::uint64_t nhosts = r.u64();
1✔
355
    if (!r.fits(nhosts, 8 * 2)) return false;
1!
356
    s.hosts.resize(nhosts);
1✔
357
    for (auto& h : s.hosts) {
1!
NEW
358
        h.first = r.str();
×
NEW
359
        h.second = r.str();
×
360
    }
361

362
    return r.ok;
1✔
363
}
364

365
}  // namespace
366

367
TraceIndex::TraceIndex(const std::string& directory,
17✔
368
                       const std::string& index_dir, std::size_t max_concurrent,
369
                       std::size_t checkpoint_size)
17✔
370
    : directory_(directory),
17✔
371
      index_dir_(index_dir),
17!
372
      max_concurrent_(max_concurrent == 0 ? 8 : max_concurrent),
17✔
373
      checkpoint_size_(checkpoint_size == 0
34✔
374
                           ? constants::indexer::DEFAULT_CHECKPOINT_SIZE
17!
375
                           : checkpoint_size) {}
34!
376

377
coro::CoroTask<void> TraceIndex::initialize() {
16!
378
    PatternDirectoryScannerUtility scanner;
379
    PatternDirectoryScannerUtilityInput scan_input{
380
        directory_, {".pfw", ".pfw.gz"}, false};
381
    auto entries = co_await scanner.process(scan_input);
382

383
    files_.clear();
384
    path_to_index_.clear();
385
    files_.reserve(entries.size());
386

387
    global_min_ts_ = std::numeric_limits<std::uint64_t>::max();
388
    global_max_ts_ = 0;
389

390
    std::vector<std::size_t> needs_build;
391
    std::vector<std::size_t> large_files;
392

393
    // The reuse decision below is existence-only, so a changed .pfw would be
394
    // served from a stale index. Drop any (shared) index root whose sources
395
    // changed since indexing; the loop then rebuilds it as if absent.
396
    {
397
        std::unordered_map<std::string, std::vector<std::string>> by_root;
398
        for (const auto& entry : entries)
399
            by_root[internal::determine_index_path(entry.path.string(),
400
                                                   index_dir_)]
401
                .push_back(entry.path.string());
402
        for (auto& [root, paths] : by_root) {
403
            if (!fs::exists(root)) continue;
404
            bool stale = false;
405
            try {
406
                indexer::IndexDatabase db(root);
407
                stale = db.find_stale_files(paths).stale();
408
            } catch (const std::exception& e) {
409
                DFTRACER_UTILS_LOG_WARN(
410
                    "TraceIndex: stale check failed for %s: %s; rebuilding",
411
                    root.c_str(), e.what());
412
                stale = true;
413
            }
414
            if (stale) {
415
                DFTRACER_UTILS_LOG_WARN(
416
                    "TraceIndex: source changed since indexing; rebuilding %s",
417
                    root.c_str());
418
                std::error_code ec;
419
                fs::remove_all(root, ec);
420
            }
421
        }
422
    }
423

424
    for (const auto& entry : entries) {
425
        FileInfo info;
426
        info.path = entry.path.string();
427
        info.index_path = internal::determine_index_path(info.path, index_dir_);
428

429
        std::error_code ec;
430
        auto fsize = fs::file_size(info.path, ec);
431
        info.compressed_size = (!ec && fsize > 0) ? fsize : 0;
432

433
        std::size_t idx = files_.size();
434
        path_to_index_[info.path] = idx;
435

436
        info.has_bloom_data = fs::exists(info.index_path);
437
        info.has_checkpoint_index = fs::exists(info.index_path);
438
        if (!info.has_bloom_data) {
439
            needs_build.push_back(idx);
440
        } else {
441
            large_files.push_back(idx);
442
        }
443

444
        files_.push_back(std::move(info));
445
    }
446

447
    if (!needs_build.empty() || !large_files.empty()) {
448
        auto pipeline_config =
449
            PipelineConfig()
450
                .with_name("TraceIndex Init")
451
                .with_compute_threads(max_concurrent_)
452
                .with_watchdog(false)
453
                .with_global_timeout(std::chrono::seconds(0))
454
                .with_task_timeout(std::chrono::seconds(0))
455
                .with_io_backend(io::IoBackendType::THREADPOOL)
456
                .with_io_batch_size(1);
457

458
        Pipeline pipeline(pipeline_config);
459

460
        auto* files_ptr = &files_;
461
        auto* needs_build_ptr = &needs_build;
462
        auto* large_files_ptr = &large_files;
463
        auto* global_min_ts_ptr = &global_min_ts_;
464
        auto* global_max_ts_ptr = &global_max_ts_;
465
        std::string index_dir = index_dir_;
466
        std::size_t max_concurrent = max_concurrent_;
467
        std::size_t checkpoint_size = checkpoint_size_;
468

469
        auto init_task = make_task(
470
            [files_ptr, needs_build_ptr, large_files_ptr, global_min_ts_ptr,
15!
471
             global_max_ts_ptr, index_dir, max_concurrent,
472
             checkpoint_size](CoroScope& ctx) -> coro::CoroTask<void> {
473
                if (!needs_build_ptr->empty()) {
474
                    DFTRACER_UTILS_LOG_INFO(
475
                        "TraceIndex: building index for %zu file(s) ...",
476
                        needs_build_ptr->size());
477

478
                    // Assign file identities up front, single-threaded, so the
479
                    // concurrent builders below don't race the shared next-id
480
                    // counter and collide two files onto one id. Hold each
481
                    // read-write handle open across the build so builders reuse
482
                    // it instead of hitting a read-only -> read-write upgrade.
483
                    std::vector<std::unique_ptr<indexer::IndexDatabase>>
484
                        held_dbs;
485
                    {
486
                        std::unordered_map<std::string,
487
                                           std::vector<std::size_t>>
488
                            by_index;
489
                        for (auto idx : *needs_build_ptr)
490
                            by_index[(*files_ptr)[idx].index_path].push_back(
491
                                idx);
492
                        for (auto& [ipath, idxs] : by_index) {
493
                            try {
494
                                auto db =
495
                                    std::make_unique<indexer::IndexDatabase>(
496
                                        ipath);
497
                                auto w = db->begin_write();
498
                                for (auto idx : idxs) {
499
                                    const auto& p = (*files_ptr)[idx].path;
500
                                    w->get_or_create_file_info(
501
                                        indexer::internal::get_logical_path(p),
502
                                        indexer::internal::calculate_file_hash(
503
                                            p));
504
                                }
505
                                w->commit();
506
                                held_dbs.push_back(std::move(db));
507
                            } catch (const std::exception& e) {
508
                                DFTRACER_UTILS_LOG_WARN(
509
                                    "TraceIndex: file-id pre-assign failed for "
510
                                    "%s: %s",
511
                                    ipath.c_str(), e.what());
512
                            }
513
                        }
514
                    }
515

516
                    auto file_chan =
517
                        coro::make_channel<std::size_t>(max_concurrent * 2);
518

519
                    const auto* index_dir_ptr = &index_dir;
520
                    co_await ctx.scope([&file_chan, files_ptr, needs_build_ptr,
14!
521
                                        index_dir_ptr, max_concurrent,
522
                                        checkpoint_size](CoroScope& scope)
523
                                           -> coro::CoroTask<void> {
524
                        scope.spawn(
525
                            [ch = file_chan->producer(), needs_build_ptr](
14!
526
                                CoroScope&) mutable -> coro::CoroTask<void> {
527
                                auto guard = ch.guard();
528
                                for (auto idx : *needs_build_ptr) {
529
                                    if (!co_await ch.send(idx)) co_return;
530
                                }
531
                                co_return;
532
                            });
28!
533

534
                        for (std::size_t w = 0; w < max_concurrent; ++w) {
535
                            scope.spawn(
536
                                [ch = file_chan->consumer(), files_ptr,
51!
537
                                 index_dir_ptr, checkpoint_size](
538
                                    CoroScope&) -> coro::CoroTask<void> {
539
                                    while (auto fi_opt =
540
                                               co_await ch.receive()) {
541
                                        std::size_t fi = *fi_opt;
542
                                        auto* info = &(*files_ptr)[fi];
543

544
                                        indexer::IndexBuilderUtility builder;
545
                                        auto config =
546
                                            indexer::IndexBuildConfig::for_file(
547
                                                info->path)
548
                                                .with_index_dir(*index_dir_ptr)
549
                                                .with_checkpoint_size(
550
                                                    checkpoint_size);
551
                                        auto result =
552
                                            co_await builder.process(config);
553

554
                                        if (result.success) {
555
                                            info->index_path =
556
                                                internal::determine_index_path(
557
                                                    info->path, *index_dir_ptr);
558
                                            info->has_bloom_data = true;
559
                                            info->has_checkpoint_index =
560
                                                fs::exists(info->index_path);
561
                                        } else {
562
                                            DFTRACER_UTILS_LOG_WARN(
563
                                                "TraceIndex: failed to "
564
                                                "index %s: %s",
565
                                                info->path.c_str(),
566
                                                result.error_message.c_str());
567
                                        }
568
                                    }
569
                                    co_return;
570
                                });
104!
571
                        }
572
                        co_return;
573
                    });
28!
574

575
                    for (auto idx : *needs_build_ptr) {
576
                        if ((*files_ptr)[idx].has_bloom_data) {
577
                            large_files_ptr->push_back(idx);
578
                        }
579
                    }
580
                }
581

582
                if (!large_files_ptr->empty()) {
583
                    auto meta_chan =
584
                        coro::make_channel<std::size_t>(max_concurrent * 2);
585

586
                    co_await ctx.scope([&meta_chan, files_ptr, large_files_ptr,
15!
587
                                        max_concurrent](CoroScope& scope)
588
                                           -> coro::CoroTask<void> {
589
                        scope.spawn(
590
                            [ch = meta_chan->producer(), large_files_ptr](
15!
591
                                CoroScope&) mutable -> coro::CoroTask<void> {
592
                                auto guard = ch.guard();
593
                                for (auto idx : *large_files_ptr) {
594
                                    if (!co_await ch.send(idx)) co_return;
595
                                }
596
                                co_return;
597
                            });
30!
598

599
                        for (std::size_t w = 0; w < max_concurrent; ++w) {
600
                            scope.spawn([ch = meta_chan->consumer(),
54!
601
                                         files_ptr](CoroScope&)
602
                                            -> coro::CoroTask<void> {
603
                                while (auto fi_opt = co_await ch.receive()) {
604
                                    std::size_t fi = *fi_opt;
605
                                    auto* info = &(*files_ptr)[fi];
606

607
                                    if (info->has_bloom_data) {
608
                                        try {
609
                                            indexer::IndexDatabase idx_db(
610
                                                info->index_path);
611
                                            auto logical = indexer::internal::
612
                                                get_logical_path(info->path);
613
                                            int fid = idx_db.get_file_info_id(
614
                                                logical);
615
                                            if (fid >= 0) {
616
                                                auto bounds =
617
                                                    idx_db.query_time_bounds(
618
                                                        fid);
619
                                                if (bounds.valid) {
620
                                                    info->min_timestamp_us =
621
                                                        bounds.min_timestamp_us;
622
                                                    info->max_timestamp_us =
623
                                                        bounds.max_timestamp_us;
624
                                                }
625
                                            }
626
                                        } catch (const std::exception& e) {
627
                                            DFTRACER_UTILS_LOG_WARN(
628
                                                "TraceIndex: failed to "
629
                                                "read time bounds from "
630
                                                "%s: %s",
631
                                                info->index_path.c_str(),
632
                                                e.what());
633
                                        }
634
                                    }
635

636
                                    auto meta_input =
637
                                        MetadataCollectorUtilityInput::
638
                                            from_file(info->path)
639
                                                .with_index(info->index_path);
640
                                    auto metadata =
641
                                        co_await MetadataCollectorUtility{}
642
                                            .process(meta_input);
643
                                    if (metadata.success) {
644
                                        info->uncompressed_size =
645
                                            metadata.uncompressed_size;
646
                                        info->num_checkpoints =
647
                                            metadata.num_checkpoints;
648
                                        info->checkpoint_size =
649
                                            metadata.checkpoint_size;
650
                                        info->compressed_size =
651
                                            metadata.compressed_size;
652
                                        info->num_lines = metadata.num_lines;
653
                                        info->size_mb = metadata.size_mb;
654
                                    }
655
                                }
656
                                co_return;
657
                            });
108!
658
                        }
659
                        co_return;
660
                    });
30!
661

662
                    for (auto fi : *large_files_ptr) {
663
                        const auto& info = (*files_ptr)[fi];
664
                        if (info.min_timestamp_us > 0 &&
665
                            info.min_timestamp_us < *global_min_ts_ptr)
666
                            *global_min_ts_ptr = info.min_timestamp_us;
667
                        if (info.max_timestamp_us > *global_max_ts_ptr)
668
                            *global_max_ts_ptr = info.max_timestamp_us;
669
                    }
670
                }
671

672
                co_return;
673
            },
30!
674
            "TraceIndexInit");
675

676
        pipeline.set_source(init_task);
677
        pipeline.set_destination(init_task);
678
        pipeline.execute();
679
    }
680

681
    // One time unit per trace: resolve it once from the first file's CM.
682
    if (!files_.empty()) {
683
        try {
684
            utilities::reader::TraceReaderConfig cfg;
685
            cfg.file_path = files_.front().path;
686
            cfg.index_dir = index_dir_;
687
            utilities::reader::TraceReader reader(cfg);
688
            time_metric_ = co_await reader.read_time_metric();
689
        } catch (const std::exception& e) {
690
            DFTRACER_UTILS_LOG_WARN(
691
                "TraceIndex: failed to resolve time_metric: %s", e.what());
692
        }
693
        if (time_metric_ != TimeMetric::US) {
694
            DFTRACER_UTILS_LOG_INFO(
695
                "TraceIndex: trace time unit is %s (scaling viz to us)",
696
                std::string(time_metric_to_string(time_metric_)).c_str());
697
        }
698
    }
699

700
    DFTRACER_UTILS_LOG_INFO("TraceIndex: found %zu trace files in %s",
701
                            files_.size(), directory_.c_str());
702
    if (global_max_ts_ > 0) {
703
        DFTRACER_UTILS_LOG_INFO("TraceIndex: global time range [%" PRIu64
704
                                ", %" PRIu64 "] us",
705
                                global_min_ts_, global_max_ts_);
706
    }
707

708
    // Adopt a valid cached summary now rather than on the first request: the
709
    // viewer opens with several summary-backed requests at once, and all of
710
    // them would otherwise queue behind the load.
711
    load_persisted_viz_summary();
712
}
32!
713

714
// Looks up single hashes and keeps each root's database open, rather than
715
// caching whole hash tables per request.
NEW
716
std::string TraceIndex::resolve_hash(HashType type, const std::string& hash) {
×
NEW
717
    if (hash.empty()) return {};
×
718
    // Memoized across requests, misses included: the viewer re-asks for the
719
    // same hashes on every zoom.
NEW
720
    std::string memo_key;
×
NEW
721
    memo_key.reserve(hash.size() + 1);
×
NEW
722
    memo_key.push_back(static_cast<char>(static_cast<int>(type)));
×
NEW
723
    memo_key.append(hash);
×
724
    {
NEW
725
        std::lock_guard<std::mutex> lk(hash_db_mutex_);
×
NEW
726
        auto it = hash_names_.find(memo_key);
×
NEW
727
        if (it != hash_names_.end()) return it->second;
×
NEW
728
    }
×
NEW
729
    std::string resolved;
×
NEW
730
    for (const auto& f : files_) {
×
NEW
731
        if (f.index_path.empty()) continue;
×
NEW
732
        std::shared_ptr<indexer::IndexDatabase> db;
×
733
        {
NEW
734
            std::lock_guard<std::mutex> lk(hash_db_mutex_);
×
NEW
735
            auto it = hash_dbs_.find(f.index_path);
×
NEW
736
            if (it != hash_dbs_.end()) {
×
NEW
737
                db = it->second;
×
738
            } else {
739
                try {
NEW
740
                    db = std::make_shared<indexer::IndexDatabase>(
×
NEW
741
                        f.index_path,
×
NEW
742
                        rocksdb::RocksDatabase::OpenMode::ReadOnly);
×
NEW
743
                } catch (const std::exception&) {
×
NEW
744
                    db = nullptr;
×
NEW
745
                }
×
NEW
746
                hash_dbs_.emplace(f.index_path, db);
×
747
            }
NEW
748
        }
×
NEW
749
        if (!db) continue;
×
750
        try {
NEW
751
            auto name = db->lookup_hash(type, hash);
×
NEW
752
            if (name && !name->empty()) {
×
NEW
753
                resolved = std::move(*name);
×
NEW
754
                break;
×
755
            }
NEW
756
        } catch (const std::exception&) {
×
NEW
757
            continue;
×
NEW
758
        }
×
NEW
759
    }
×
760
    {
NEW
761
        std::lock_guard<std::mutex> lk(hash_db_mutex_);
×
NEW
762
        hash_names_.emplace(std::move(memo_key), resolved);
×
NEW
763
    }
×
NEW
764
    return resolved;
×
NEW
765
}
×
766

767
std::string TraceIndex::viz_summary_cache_path() const {
34✔
768
    return (fs::path(index_dir_) / ".dftviz_summary").string();
68!
769
}
770

771
// A changed source is re-indexed in initialize(), shifting these per-file
772
// values, so the cache invalidates itself on mismatch.
773
std::string TraceIndex::viz_summary_fingerprint() const {
13✔
774
    std::uint64_t h = 1469598103934665603ULL;
13✔
775
    auto mix = [&](const void* data, std::size_t n) {
122✔
776
        const auto* b = static_cast<const unsigned char*>(data);
122✔
777
        for (std::size_t i = 0; i < n; ++i) {
1,805✔
778
            h ^= b[i];
1,683✔
779
            h *= 1099511628211ULL;
1,683✔
780
        }
781
    };
135✔
782
    std::uint32_t ver = VIZ_SUMMARY_FORMAT_VERSION;
13✔
783
    mix(&ver, sizeof ver);
13✔
784
    int tm = static_cast<int>(time_metric_);
13✔
785
    mix(&tm, sizeof tm);
13✔
786
    mix(&global_min_ts_, sizeof global_min_ts_);
13✔
787
    mix(&global_max_ts_, sizeof global_max_ts_);
13✔
788
    for (const auto& f : files_) {
27✔
789
        mix(f.path.data(), f.path.size());
14✔
790
        mix(&f.uncompressed_size, sizeof f.uncompressed_size);
14✔
791
        mix(&f.num_checkpoints, sizeof f.num_checkpoints);
14✔
792
        mix(&f.min_timestamp_us, sizeof f.min_timestamp_us);
14✔
793
        mix(&f.max_timestamp_us, sizeof f.max_timestamp_us);
14✔
794
    }
795
    char out[17];
796
    std::snprintf(out, sizeof out, "%016llx",
13✔
797
                  static_cast<unsigned long long>(h));
798
    return std::string(out);
13!
799
}
800

801
bool TraceIndex::load_persisted_viz_summary() {
26✔
802
    try {
803
        auto path = viz_summary_cache_path();
26!
804
        std::ifstream is(path, std::ios::binary | std::ios::ate);
26!
805
        if (!is) return false;
26!
806
        auto size = is.tellg();
5!
807
        if (size <= 0) return false;
5!
808
        std::string buf(static_cast<std::size_t>(size), '\0');
5!
809
        is.seekg(0);
5!
810
        if (!is.read(buf.data(), size)) return false;
5!
811

812
        BufReader r{buf.data(), buf.data() + buf.size()};
5✔
813
        if (r.u32() != VIZ_SUMMARY_MAGIC ||
10!
814
            r.u32() != VIZ_SUMMARY_FORMAT_VERSION)
5✔
NEW
815
            return false;
×
816
        std::string fp = r.str();
5!
817
        if (!r.ok || fp != viz_summary_fingerprint()) return false;
5!
818

819
        auto summary = std::make_unique<VizSummary>();
1!
820
        if (!deserialize_summary(r, *summary)) return false;
1!
821

822
        set_viz_summary(std::move(summary));
1✔
823
        DFTRACER_UTILS_LOG_INFO("TraceIndex: loaded cached viz summary from %s",
1!
824
                                path.c_str());
825
        return true;
1✔
826
    } catch (const std::exception& e) {
26!
NEW
827
        DFTRACER_UTILS_LOG_WARN("TraceIndex: viz summary cache load failed: %s",
×
828
                                e.what());
NEW
829
        return false;
×
NEW
830
    }
×
831
}
832

833
void TraceIndex::persist_viz_summary() const {
10✔
834
    if (viz_summary_state_.load(std::memory_order_acquire) != 2 ||
28✔
835
        !viz_summary_)
8!
836
        return;
2✔
837
    try {
838
        BufWriter w;
8✔
839
        w.u32(VIZ_SUMMARY_MAGIC);
8!
840
        w.u32(VIZ_SUMMARY_FORMAT_VERSION);
8!
841
        w.str(viz_summary_fingerprint());
8!
842
        serialize_summary(w, *viz_summary_);
8!
843

844
        auto path = viz_summary_cache_path();
8!
845
        auto tmp = path + ".tmp";
8!
846
        {
847
            std::ofstream os(tmp, std::ios::binary | std::ios::trunc);
8!
848
            if (!os.write(w.b.data(),
8!
849
                          static_cast<std::streamsize>(w.b.size()))) {
8✔
NEW
850
                std::error_code rec;
×
NEW
851
                fs::remove(tmp, rec);
×
NEW
852
                return;
×
853
            }
854
        }
8!
855
        std::error_code ec;
8✔
856
        fs::rename(tmp, path, ec);
8!
857
        if (ec) fs::remove(tmp, ec);
8!
858
    } catch (const std::exception& e) {
8!
NEW
859
        DFTRACER_UTILS_LOG_WARN(
×
860
            "TraceIndex: viz summary cache write failed: %s", e.what());
NEW
861
    }
×
862
}
863

864
const TraceIndex::FileInfo* TraceIndex::find_file(
3✔
865
    const std::string& path) const {
866
    auto it = path_to_index_.find(path);
3!
867
    if (it == path_to_index_.end()) return nullptr;
3✔
868
    return &files_[it->second];
2✔
869
}
870

871
const TraceIndex::FileInfo* TraceIndex::file_at(std::size_t index) const {
2✔
872
    if (index >= files_.size()) return nullptr;
2✔
873
    return &files_[index];
1✔
874
}
875

876
std::vector<const TraceIndex::FileInfo*> collect_candidate_files(
13✔
877
    TraceIndex& index, const QueryParams& params) {
878
    std::vector<const TraceIndex::FileInfo*> files;
13✔
879
    auto file_param = params.get("file");
13!
880
    if (!file_param.empty()) {
13!
881
        auto* f = index.find_file(std::string(file_param));
×
882
        if (f) files.push_back(f);
×
883
    } else {
884
        for (const auto& f : index.files()) {
30✔
885
            files.push_back(&f);
17!
886
        }
887
    }
888
    return files;
26✔
UNCOV
889
}
×
890

891
}  // namespace dftracer::utils::server
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc