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

tstack / lnav / 19308946304-2665

12 Nov 2025 07:02PM UTC coverage: 68.714% (+0.05%) from 68.663%
19308946304-2665

push

github

tstack
more issues related-to/uncovered-by time cutoffs

33 of 55 new or added lines in 12 files covered. (60.0%)

7 existing lines in 1 file now uncovered.

50740 of 73842 relevant lines covered (68.71%)

430744.53 hits per line

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

72.92
/src/logfile.cc
1
/**
2
 * Copyright (c) 2007-2012, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file logfile.cc
30
 */
31

32
#include <exception>
33
#include <filesystem>
34
#include <memory>
35
#include <optional>
36
#include <utility>
37
#include <vector>
38

39
#include "logfile.hh"
40

41
#include <errno.h>
42
#include <fcntl.h>
43
#include <string.h>
44
#include <sys/param.h>
45
#include <sys/resource.h>
46
#include <sys/stat.h>
47
#include <time.h>
48

49
#include "base/ansi_scrubber.hh"
50
#include "base/attr_line.builder.hh"
51
#include "base/auto_fd.hh"
52
#include "base/date_time_scanner.cfg.hh"
53
#include "base/fs_util.hh"
54
#include "base/injector.hh"
55
#include "base/intern_string.hh"
56
#include "base/is_utf8.hh"
57
#include "base/itertools.enumerate.hh"
58
#include "base/result.h"
59
#include "base/snippet_highlighters.hh"
60
#include "base/string_util.hh"
61
#include "base/time_util.hh"
62
#include "config.h"
63
#include "file_options.hh"
64
#include "hasher.hh"
65
#include "lnav_util.hh"
66
#include "log.watch.hh"
67
#include "log_format.hh"
68
#include "logfile.cfg.hh"
69
#include "piper.header.hh"
70
#include "shared_buffer.hh"
71
#include "yajlpp/yajlpp_def.hh"
72

73
using namespace lnav::roles::literals;
74

75
static auto intern_lifetime = intern_string::get_table_lifetime();
76

77
static constexpr size_t INDEX_RESERVE_INCREMENT = 1024;
78

79
static constexpr size_t RETRY_MATCH_SIZE = 250;
80

81
static const typed_json_path_container<lnav::gzip::header>&
82
get_file_header_handlers()
4✔
83
{
84
    static const typed_json_path_container<lnav::gzip::header> retval = {
85
        yajlpp::property_handler("name").for_field(&lnav::gzip::header::h_name),
8✔
86
        yajlpp::property_handler("mtime").for_field(
8✔
87
            &lnav::gzip::header::h_mtime),
88
        yajlpp::property_handler("comment").for_field(
8✔
89
            &lnav::gzip::header::h_comment),
90
    };
24✔
91

92
    return retval;
4✔
93
}
16✔
94

95
Result<std::shared_ptr<logfile>, std::string>
96
logfile::open(std::filesystem::path filename,
702✔
97
              const logfile_open_options& loo,
98
              auto_fd fd)
99
{
100
    require(!filename.empty());
702✔
101

102
    auto lf = std::shared_ptr<logfile>(new logfile(std::move(filename), loo));
702✔
103

104
    memset(&lf->lf_stat, 0, sizeof(lf->lf_stat));
702✔
105
    std::filesystem::path resolved_path;
702✔
106

107
    if (!fd.has_value()) {
702✔
108
        auto rp_res = lnav::filesystem::realpath(lf->lf_filename);
692✔
109
        if (rp_res.isErr()) {
692✔
110
            return Err(fmt::format(FMT_STRING("realpath({}) failed with: {}"),
×
111
                                   lf->lf_filename,
×
112
                                   rp_res.unwrapErr()));
×
113
        }
114

115
        resolved_path = rp_res.unwrap();
692✔
116
        if (lnav::filesystem::statp(resolved_path, &lf->lf_stat) == -1) {
692✔
117
            return Err(fmt::format(FMT_STRING("stat({}) failed with: {}"),
×
118
                                   lf->lf_filename,
×
119
                                   lnav::from_errno()));
×
120
        }
121

122
        if (!S_ISREG(lf->lf_stat.st_mode)) {
692✔
123
            return Err(fmt::format(FMT_STRING("{} is not a regular file"),
×
124
                                   lf->lf_filename));
×
125
        }
126
    }
692✔
127

128
    auto_fd lf_fd;
702✔
129
    if (fd.has_value()) {
702✔
130
        lf_fd = std::move(fd);
10✔
131
    } else if ((lf_fd
692✔
132
                = lnav::filesystem::openp(resolved_path, O_RDONLY | O_CLOEXEC))
692✔
133
               == -1)
692✔
134
    {
135
        return Err(fmt::format(FMT_STRING("open({}) failed with: {}"),
×
136
                               lf->lf_filename,
×
137
                               lnav::from_errno()));
×
138
    } else {
139
        lf->lf_actual_path = lf->lf_filename;
692✔
140
        lf->lf_valid_filename = true;
692✔
141
    }
142

143
    lf_fd.close_on_exec();
702✔
144

145
    log_info("Creating logfile: fd=%d; size=%" PRId64 "; mtime=%" PRId64
702✔
146
             "; filename=%s",
147
             (int) lf_fd,
148
             (long long) lf->lf_stat.st_size,
149
             (long long) lf->lf_stat.st_mtime,
150
             lf->lf_filename_as_string.c_str());
151
    if (lf->lf_actual_path) {
702✔
152
        log_info("  actual_path=%s", lf->lf_actual_path->c_str());
692✔
153
    }
154

155
    if (!lf->lf_options.loo_filename.empty()) {
702✔
156
        lf->set_filename(lf->lf_options.loo_filename);
74✔
157
        lf->lf_valid_filename = false;
74✔
158
    }
159

160
    lf->lf_line_buffer.set_fd(lf_fd);
702✔
161
    lf->lf_index.reserve(INDEX_RESERVE_INCREMENT);
702✔
162

163
    lf->lf_indexing = lf->lf_options.loo_is_visible;
702✔
164
    lf->lf_text_format
702✔
165
        = lf->lf_options.loo_text_format.value_or(text_format_t::TF_UNKNOWN);
702✔
166
    lf->lf_format_match_messages = loo.loo_match_details;
702✔
167

168
    const auto& hdr = lf->lf_line_buffer.get_header_data();
702✔
169
    if (hdr.valid()) {
702✔
170
        log_info("%s: has header %d",
65✔
171
                 lf->lf_filename_as_string.c_str(),
172
                 hdr.valid());
173
        hdr.match(
65✔
174
            [&lf](const lnav::gzip::header& gzhdr) {
×
175
                if (!gzhdr.empty()) {
4✔
176
                    lf->lf_embedded_metadata["net.zlib.gzip.header"] = {
12✔
177
                        text_format_t::TF_JSON,
178
                        get_file_header_handlers()
8✔
179
                            .formatter_for(gzhdr)
8✔
180
                            .with_config(yajl_gen_beautify, 1)
8✔
181
                            .to_string(),
182
                    };
12✔
183
                }
184
            },
12✔
185
            [&lf](const lnav::piper::header& phdr) {
65✔
186
                static auto& safe_options_hier
187
                    = injector::get<lnav::safe_file_options_hier&>();
61✔
188

189
                lf->lf_embedded_metadata["org.lnav.piper.header"] = {
183✔
190
                    text_format_t::TF_JSON,
191
                    lnav::piper::header_handlers.formatter_for(phdr)
61✔
192
                        .with_config(yajl_gen_beautify, 1)
122✔
193
                        .to_string(),
194
                };
122✔
195
                log_info("setting file name from piper header: %s",
61✔
196
                         phdr.h_name.c_str());
197
                lf->set_filename(phdr.h_name);
61✔
198
                lf->lf_valid_filename = false;
61✔
199
                if (phdr.h_demux_output == lnav::piper::demux_output_t::signal)
61✔
200
                {
201
                    lf->lf_text_format = text_format_t::TF_LOG;
14✔
202
                }
203

204
                lnav::file_options fo;
61✔
205
                if (!phdr.h_timezone.empty()) {
61✔
206
                    log_info("setting default time zone from piper header: %s",
25✔
207
                             phdr.h_timezone.c_str());
208
                    try {
209
                        fo.fo_default_zone.pp_value
210
                            = date::locate_zone(phdr.h_timezone);
25✔
211
                    } catch (const std::runtime_error& e) {
×
212
                        log_error("unable to get tz from piper header %s -- %s",
×
213
                                  phdr.h_timezone.c_str(),
214
                                  e.what());
215
                    }
×
216
                }
217
                if (!fo.empty()) {
61✔
218
                    safe::WriteAccess<lnav::safe_file_options_hier>
219
                        options_hier(safe_options_hier);
25✔
220

221
                    auto& coll = options_hier->foh_path_to_collection["/"];
25✔
222
                    auto iter
223
                        = coll.foc_pattern_to_options.find(lf->get_filename());
25✔
224
                    if (iter == coll.foc_pattern_to_options.end()
25✔
225
                        || !(iter->second == fo))
25✔
226
                    {
227
                        coll.foc_pattern_to_options[lf->get_filename()] = fo;
25✔
228
                        options_hier->foh_generation += 1;
25✔
229
                    }
230
                }
25✔
231
            });
183✔
232
    }
233

234
    lf->file_options_have_changed();
702✔
235
    lf->lf_content_id = hasher().update(lf->lf_filename_as_string).to_string();
702✔
236

237
    lf->lf_line_buffer.set_do_preloading(true);
702✔
238
    lf->lf_line_buffer.send_initial_load();
702✔
239

240
    ensure(lf->invariant());
702✔
241

242
    return Ok(lf);
702✔
243
}
702✔
244

245
logfile::logfile(std::filesystem::path filename,
702✔
246
                 const logfile_open_options& loo)
702✔
247
    : lf_filename(std::move(filename)),
702✔
248
      lf_filename_as_string(lf_filename.string()), lf_options(loo)
1,404✔
249
{
250
    this->lf_opids.writeAccess()->los_opid_ranges.reserve(64);
702✔
251
    this->lf_thread_ids.writeAccess()->ltis_tid_ranges.reserve(64);
702✔
252
}
702✔
253

254
logfile::~logfile()
1,404✔
255
{
256
    log_info("destructing logfile: %s", this->lf_filename_as_string.c_str());
702✔
257
}
1,404✔
258

259
bool
260
logfile::file_options_have_changed()
4,960✔
261
{
262
    static auto& safe_options_hier
263
        = injector::get<lnav::safe_file_options_hier&>();
4,960✔
264

265
    bool tz_changed = false;
4,960✔
266

267
    {
268
        safe::ReadAccess<lnav::safe_file_options_hier> options_hier(
269
            safe_options_hier);
4,960✔
270

271
        if (this->lf_file_options_generation == options_hier->foh_generation) {
4,960✔
272
            return false;
4,327✔
273
        }
274
        log_info("%s: checking new generation of file options: %zu -> %zu",
633✔
275
                 this->lf_filename_as_string.c_str(),
276
                 this->lf_file_options_generation,
277
                 options_hier->foh_generation);
278
        auto new_options = options_hier->match(this->get_filename());
633✔
279
        if (this->lf_file_options == new_options) {
633✔
280
            this->lf_file_options_generation = options_hier->foh_generation;
601✔
281
            return false;
601✔
282
        }
283

284
        this->lf_file_options = new_options;
32✔
285
        log_info("%s: file options have changed",
32✔
286
                 this->lf_filename_as_string.c_str());
287
        if (this->lf_file_options) {
32✔
288
            log_info(
32✔
289
                "  tz=%s",
290
                this->lf_file_options->second.fo_default_zone.pp_value->name()
291
                    .c_str());
292
            if (this->lf_file_options->second.fo_default_zone.pp_value
32✔
293
                    != nullptr
294
                && this->lf_format != nullptr
32✔
295
                && !(this->lf_format->lf_timestamp_flags & ETF_ZONE_SET))
64✔
296
            {
297
                log_info("  tz change affects this file");
3✔
298
                tz_changed = true;
3✔
299
            }
300
        } else if (this->lf_format != nullptr
×
301
                   && !(this->lf_format->lf_timestamp_flags & ETF_ZONE_SET)
×
302
                   && this->lf_format->lf_date_time.dts_default_zone != nullptr)
×
303
        {
304
            tz_changed = true;
×
305
        }
306
        this->lf_file_options_generation = options_hier->foh_generation;
32✔
307
    }
5,561✔
308

309
    return tz_changed;
32✔
310
}
311

312
logfile::map_entry_result
313
logfile::find_content_map_entry(file_off_t offset, map_read_requirement req)
×
314
{
315
    static constexpr auto LOOKBACK_SIZE = 32 * 1024;
316
    static constexpr auto MAX_LOOKBACK_SIZE = 4 * 1024 * 1024;
317

318
    if (offset < LOOKBACK_SIZE) {
×
319
        return map_entry_not_found{};
×
320
    }
321
    auto end_range = file_range{
×
322
        offset - LOOKBACK_SIZE,
×
323
        LOOKBACK_SIZE,
324
    };
325

326
    auto full_size = this->get_content_size();
×
327
    file_size_t lower_offset = 0;
×
328
    file_size_t upper_offset = full_size;
×
329
    auto looping = true;
×
330
    do {
×
331
        std::optional<content_map_entry> lower_retval;
×
332
        auto peek_res = this->lf_line_buffer.peek_range(end_range);
×
333
        if (!peek_res.isOk()) {
×
334
            log_error("%s: peek failed -- %s",
×
335
                      this->lf_filename_as_string.c_str(),
336
                      peek_res.unwrapErr().c_str());
337
            return map_entry_not_found{};
×
338
        }
339
        auto peek_buf = peek_res.unwrap();
×
340
        auto peek_sf = to_string_fragment(peek_buf);
×
341

342
        if (req.is<map_read_upper_bound>()) {
×
343
            if (!peek_sf.endswith("\n")) {
×
344
                log_warning("%s: peek returned partial line",
×
345
                            this->lf_filename_as_string.c_str());
346
                return map_entry_not_found{};
×
347
            }
348
            peek_sf.pop_back();
×
349
        }
350
        while (!peek_sf.empty()) {
×
351
            auto rsplit_res = peek_sf.rsplit_pair(string_fragment::tag1{'\n'});
×
352
            if (!rsplit_res) {
×
353
                log_trace("%s: did not peek enough to find last line",
×
354
                          this->lf_filename_as_string.c_str());
355
                if (req.is<map_read_upper_bound>()) {
×
356
                    if (end_range.fr_offset < LOOKBACK_SIZE) {
×
357
                        return map_entry_not_found{};
×
358
                    }
359
                    end_range.fr_offset -= LOOKBACK_SIZE;
×
360
                    end_range.fr_size += LOOKBACK_SIZE;
×
361
                    if (end_range.next_offset() > full_size) {
×
362
                        end_range.fr_offset = 0;
×
363
                        end_range.fr_size = full_size;
×
364
                    } else if (end_range.fr_size > MAX_LOOKBACK_SIZE) {
×
365
                        return map_entry_not_found{};
×
366
                    }
367
                }
368
                break;
×
369
            }
370

371
            auto [leading, last_line] = rsplit_res.value();
×
372
            // log_debug("leading %d", leading.length());
373
            // log_debug("last %.*s", last_line.length(), last_line.data());
374
            pattern_locks line_locks;
×
375
            scan_batch_context sbc_tmp{
×
376
                this->lf_allocator,
×
377
                line_locks,
378
            };
379
            shared_buffer tmp_sb;
×
380
            shared_buffer_ref tmp_sbr;
×
381
            tmp_sbr.share(tmp_sb, last_line.data(), last_line.length());
×
382
            auto end_lines_fr = file_range{
×
383
                end_range.fr_offset + last_line.sf_begin,
×
384
                last_line.length(),
×
385
            };
386
            auto utf8_res = is_utf8(last_line, '\n');
×
387
            end_lines_fr.fr_metadata.m_has_ansi = utf8_res.usr_has_ansi;
×
388
            end_lines_fr.fr_metadata.m_valid_utf = utf8_res.is_valid();
×
389
            auto end_li = line_info{
×
390
                end_lines_fr,
391
            };
392
            end_li.li_utf8_scan_result = utf8_res;
×
393
            std::vector<logline> tmp_index;
×
394
            auto scan_res = this->lf_format->scan(
×
395
                *this, tmp_index, end_li, tmp_sbr, sbc_tmp);
×
396
            if (scan_res.is<log_format::scan_match>() && !tmp_index.empty()) {
×
397
                auto line_time
398
                    = tmp_index.back().get_time<std::chrono::microseconds>();
×
399

400
                if (req.is<map_read_lower_bound>()) {
×
401
                    auto lb = req.get<map_read_lower_bound>();
×
402
                    if (line_time >= lb.mrlb_time) {
×
403
                        lower_retval = content_map_entry{
×
404
                            end_lines_fr,
405
                            line_time,
406
                        };
407
                    } else if (lower_retval) {
×
408
                        return map_entry_found{lower_retval.value()};
×
409
                    } else {
410
                        // need to move forward
411
                        peek_sf = string_fragment{};
×
412
                        continue;
×
413
                    }
414
                } else {
415
                    return map_entry_found{content_map_entry{
×
416
                        end_lines_fr,
417
                        line_time,
418
                    }};
×
419
                }
420
            }
421
            // log_trace("%s: no match for line, going back",
422
            // this->lf_filename_as_string.c_str());
423
            peek_sf = leading;
×
424
        }
425

426
        log_trace("%s: no messages found in peak, going back further",
×
427
                  this->lf_filename_as_string.c_str());
428
        if (end_range.fr_offset < LOOKBACK_SIZE) {
×
429
            return map_entry_not_found{};
×
430
        }
431
        req.match([](map_read_upper_bound& m) {},
×
432
                  [&](map_read_lower_bound& m) {
×
433
                      if (lower_retval) {
×
434
                          upper_offset = end_range.fr_offset;
×
435
                          end_range.fr_offset
×
436
                              -= (upper_offset - lower_offset) / 2;
×
437
                          log_debug("first half %llu", end_range.fr_offset);
×
438
                      } else if (end_range.next_offset() < full_size) {
×
439
                          lower_offset = end_range.fr_offset;
×
440
                          end_range.fr_offset
×
441
                              += (upper_offset - end_range.fr_offset) / 2;
×
442
                          log_debug("2nd half %llu", end_range.fr_offset);
×
443
                      } else {
444
                          looping = false;
×
445
                      }
446
                      if (end_range.next_offset() > full_size) {
×
447
                          end_range.fr_offset = full_size - end_range.fr_size;
×
448
                      }
449
                  });
×
450
    } while (looping);
×
451

452
    return map_entry_not_found{};
×
453
}
454

455
logfile::rebuild_result_t
456
logfile::build_content_map()
566✔
457
{
458
    static auto op = lnav_operation{"build_content_map"};
566✔
459

460
    auto op_guard = lnav_opid_guard::internal(op);
566✔
461
    auto retval = rebuild_result_t::NO_NEW_LINES;
566✔
462

463
    this->lf_lower_bound_entry = std::nullopt;
566✔
464
    this->lf_upper_bound_entry = std::nullopt;
566✔
465
    this->lf_file_size_at_map_time = this->lf_index_size;
566✔
466

467
    auto full_size = this->get_content_size();
566✔
468

469
    if (this->lf_options.loo_time_range.has_lower_bound()
566✔
470
        && this->lf_options.loo_time_range.tr_begin
×
471
            > this->lf_index.front().get_time<std::chrono::microseconds>()
×
472
        && this->lf_options.loo_time_range.tr_begin
566✔
473
            <= this->lf_index.back().get_time<std::chrono::microseconds>())
566✔
474
    {
475
        auto ll_opt = this->find_from_time(
×
476
            to_timeval(this->lf_options.loo_time_range.tr_begin));
×
477
        auto ll = ll_opt.value();
×
478
        auto first_line_offset = ll->get_offset();
×
479
        this->lf_lower_bound_entry = content_map_entry{
×
480
            file_range{first_line_offset, full_size - first_line_offset},
×
481
            ll->get_time<std::chrono::microseconds>(),
×
482
        };
483
        log_info("%s: lower bound is within current index, erasing %ld lines",
×
484
                 this->lf_filename_as_string.c_str(),
485
                 std::distance(this->lf_index.cbegin(), ll));
486
        this->lf_index_size = first_line_offset;
×
487
        this->lf_index.clear();
×
488
        retval = rebuild_result_t::NEW_ORDER;
×
489
    }
490

491
    if (this->lf_index_size == full_size) {
566✔
492
        log_trace("%s: file has already been scanned, no need to peek",
566✔
493
                  this->lf_filename_as_string.c_str());
494
        const auto& last_line = this->lf_index.back();
566✔
495
        auto last_line_offset = last_line.get_offset();
566✔
496
        this->lf_upper_bound_entry = content_map_entry{
×
497
            file_range{last_line_offset, full_size - last_line_offset},
566✔
498
            last_line.get_time<std::chrono::microseconds>(),
566✔
499
        };
566✔
500
        if (this->lf_options.loo_time_range.has_lower_bound()
566✔
501
            && this->lf_options.loo_time_range.tr_begin
566✔
502
                > this->lf_index.back().get_time<std::chrono::microseconds>())
566✔
503
        {
504
            log_info("%s: lower bound is past content",
×
505
                     this->lf_filename_as_string.c_str());
506
            this->lf_index.clear();
×
507
            retval = rebuild_result_t::NEW_ORDER;
×
508
        }
509
        return retval;
566✔
510
    }
511

512
    auto end_entry_opt
513
        = this->find_content_map_entry(full_size, map_read_upper_bound{});
×
514
    if (!end_entry_opt.is<map_entry_found>()) {
×
515
        log_warning(
×
516
            "%s: skipping content map since the last message could not be "
517
            "found",
518
            this->get_filename().c_str());
519
        return retval;
×
520
    }
521

522
    auto end_entry = end_entry_opt.get<map_entry_found>().mef_entry;
×
523
    log_info("found content end: %llu %s",
×
524
             end_entry.cme_range.fr_offset,
525
             lnav::to_rfc3339_string(to_timeval(end_entry.cme_time)).c_str());
526
    this->lf_upper_bound_entry = end_entry;
×
527

528
    if (this->lf_options.loo_time_range.has_lower_bound()) {
×
529
        if (this->lf_options.loo_time_range.tr_begin > end_entry.cme_time) {
×
530
            retval = rebuild_result_t::NEW_ORDER;
×
531
        } else if (this->lf_index.empty()
×
532
                   || this->lf_options.loo_time_range.tr_begin
×
533
                       > this->lf_index.back()
×
534
                             .get_time<std::chrono::microseconds>())
×
535
        {
536
            auto offset = full_size / 2;
×
537
            log_debug("%s: searching for lower bound %lld",
×
538
                      this->lf_filename_as_string.c_str(),
539
                      this->lf_options.loo_time_range.tr_begin.count());
540
            auto low_entry_opt = this->find_content_map_entry(
541
                offset,
542
                map_read_lower_bound{
×
543
                    this->lf_options.loo_time_range.tr_begin,
544
                });
×
545
            if (low_entry_opt.is<map_entry_found>()) {
×
546
                auto low_entry = low_entry_opt.get<map_entry_found>().mef_entry;
×
547
                log_info("found content start: %llu %s",
×
548
                         low_entry.cme_range.fr_offset,
549
                         lnav::to_rfc3339_string(to_timeval(low_entry.cme_time))
550
                             .c_str());
551
                this->lf_lower_bound_entry = low_entry;
×
552
                this->lf_index_size = low_entry.cme_range.fr_offset;
×
553

554
                retval = rebuild_result_t::NEW_ORDER;
×
555
            }
556
        }
557
    }
558

559
    if (retval == rebuild_result_t::NEW_ORDER) {
×
560
        {
561
            auto los = this->lf_opids.writeAccess();
×
562

563
            los->los_opid_ranges.clear();
×
564
            los->los_sub_in_use.clear();
×
565
        }
566
        {
567
            auto tids = this->lf_thread_ids.writeAccess();
×
568
            tids->ltis_tid_ranges.clear();
×
569
        }
570
        this->lf_pattern_locks.pl_lines.clear();
×
571
        this->lf_value_stats.clear();
×
572
        this->lf_index.clear();
×
573
        this->lf_upper_bound_size = std::nullopt;
×
574
    }
575

576
    return retval;
×
577
}
566✔
578

579
bool
580
logfile::in_range() const
4,138✔
581
{
582
    if (this->lf_format == nullptr) {
4,138✔
583
        return true;
646✔
584
    }
585

586
    return !this->lf_index.empty() || this->lf_lower_bound_entry.has_value();
3,492✔
587
}
588

589
bool
590
logfile::exists() const
4,138✔
591
{
592
    if (!this->lf_actual_path) {
4,138✔
593
        return true;
67✔
594
    }
595

596
    if (this->lf_options.loo_source == logfile_name_source::ARCHIVE) {
4,071✔
597
        return true;
×
598
    }
599

600
    auto stat_res = lnav::filesystem::stat_file(this->lf_actual_path.value());
4,071✔
601
    if (stat_res.isErr()) {
4,071✔
602
        log_error("%s: stat failed -- %s",
×
603
                  this->lf_actual_path.value().c_str(),
604
                  stat_res.unwrapErr().c_str());
605
        return false;
×
606
    }
607

608
    auto st = stat_res.unwrap();
4,071✔
609
    return this->lf_stat.st_dev == st.st_dev
4,071✔
610
        && this->lf_stat.st_ino == st.st_ino;
4,071✔
611
}
4,071✔
612

613
auto
614
logfile::reset_state() -> void
8✔
615
{
616
    this->clear_time_offset();
8✔
617
    this->lf_indexing = this->lf_options.loo_is_visible;
8✔
618
}
8✔
619

620
void
621
logfile::set_format_base_time(log_format* lf, const line_info& li)
850,220✔
622
{
623
    time_t file_time = li.li_timestamp.tv_sec != 0
850,220✔
624
        ? li.li_timestamp.tv_sec
850,220✔
625
        : this->lf_line_buffer.get_file_time();
827,637✔
626

627
    if (file_time == 0) {
850,220✔
628
        file_time = this->lf_stat.st_mtime;
825,859✔
629
    }
630

631
    if (!this->lf_cached_base_time
850,220✔
632
        || this->lf_cached_base_time.value() != file_time)
850,220✔
633
    {
634
        tm new_base_tm;
635
        this->lf_cached_base_time = file_time;
671✔
636
        localtime_r(&file_time, &new_base_tm);
671✔
637
        this->lf_cached_base_tm = new_base_tm;
671✔
638
    }
639
    lf->lf_date_time.set_base_time(this->lf_cached_base_time.value(),
850,220✔
640
                                   this->lf_cached_base_tm.value());
850,220✔
641
}
850,220✔
642

643
time_range
644
logfile::get_content_time_range() const
7✔
645
{
646
    if (this->lf_format == nullptr || this->lf_index.empty()) {
7✔
647
        return {
648
            std::chrono::seconds{this->lf_stat.st_ctime},
×
649
            std::chrono::seconds{this->lf_stat.st_mtime},
×
650
        };
651
    }
652

653
    return {
654
        this->lf_index.front().get_time<std::chrono::microseconds>(),
7✔
655
        this->lf_index.back().get_time<std::chrono::microseconds>(),
7✔
656
    };
7✔
657
}
658

659
bool
660
logfile::process_prefix(shared_buffer_ref& sbr,
18,405✔
661
                        const line_info& li,
662
                        scan_batch_context& sbc)
663
{
664
    static auto max_unrecognized_lines
665
        = injector::get<const lnav::logfile::config&>()
1,178✔
666
              .lc_max_unrecognized_lines;
18,405✔
667

668
    log_format::scan_result_t found = log_format::scan_no_match{};
18,405✔
669
    size_t prescan_size = this->lf_index.size();
18,405✔
670
    auto prescan_time = std::chrono::microseconds{0};
18,405✔
671
    bool retval = false;
18,405✔
672

673
    if (this->lf_options.loo_detect_format
36,810✔
674
        && (this->lf_format == nullptr
32,014✔
675
            || this->lf_index.size() < RETRY_MATCH_SIZE))
13,609✔
676
    {
677
        const auto& root_formats = log_format::get_root_formats();
12,048✔
678
        std::optional<std::pair<log_format*, log_format::scan_match>>
679
            best_match;
12,048✔
680
        size_t scan_count = 0;
12,048✔
681

682
        if (!this->lf_index.empty()) {
12,048✔
683
            prescan_time = this->lf_index[prescan_size - 1]
10,939✔
684
                               .get_time<std::chrono::microseconds>();
10,939✔
685
        }
686
        if (this->lf_format != nullptr) {
12,048✔
687
            best_match = std::make_pair(
×
688
                this->lf_format.get(),
10,024✔
689
                log_format::scan_match{this->lf_format_quality});
20,048✔
690
        }
691

692
        /*
693
         * Try each scanner until we get a match.  Fortunately, the formats
694
         * tend to be sufficiently different that there are few ambiguities...
695
         */
696
        log_trace("logfile[%s]: scanning line %zu (offset: %lld; size: %lld)",
12,048✔
697
                  this->lf_filename_as_string.c_str(),
698
                  this->lf_index.size(),
699
                  li.li_file_range.fr_offset,
700
                  li.li_file_range.fr_size);
701
        auto starting_index_size = this->lf_index.size();
12,048✔
702
        size_t prev_index_size = this->lf_index.size();
12,048✔
703
        for (const auto& curr : root_formats) {
880,788✔
704
            if (this->lf_index.size()
868,740✔
705
                >= curr->lf_max_unrecognized_lines.value_or(
868,740✔
706
                    max_unrecognized_lines))
707
            {
708
                continue;
19,082✔
709
            }
710

711
            if (this->lf_mismatched_formats.count(curr->get_name()) > 0) {
868,740✔
712
                continue;
17,943✔
713
            }
714

715
            auto match_res = curr->match_name(this->lf_filename_as_string);
850,797✔
716
            if (match_res.is<log_format::name_mismatched>()) {
850,797✔
717
                auto nm = match_res.get<log_format::name_mismatched>();
1,139✔
718
                if (li.li_file_range.fr_offset == 0) {
1,139✔
719
                    log_debug("(%s) does not match file name: %s",
1,072✔
720
                              curr->get_name().get(),
721
                              this->lf_filename_as_string.c_str());
722
                }
723
                auto regex_al = attr_line_t(nm.nm_pattern);
1,139✔
724
                lnav::snippets::regex_highlighter(
1,139✔
725
                    regex_al, -1, line_range{0, (int) regex_al.length()});
1,139✔
726
                auto note = attr_line_t("pattern: ")
1,139✔
727
                                .append(regex_al)
1,139✔
728
                                .append("\n  ")
1,139✔
729
                                .append(lnav::roles::quoted_code(
2,278✔
730
                                    fmt::to_string(this->get_filename())))
2,278✔
731
                                .append("\n")
1,139✔
732
                                .append(nm.nm_partial + 2, ' ')
1,139✔
733
                                .append("^ matched up to here"_snippet_border);
1,139✔
734
                auto match_um = lnav::console::user_message::info(
1,139✔
735
                                    attr_line_t()
1,139✔
736
                                        .append(lnav::roles::identifier(
2,278✔
737
                                            curr->get_name().to_string()))
2,278✔
738
                                        .append(" file name pattern required "
1,139✔
739
                                                "by format does not match"))
740
                                    .with_note(note)
1,139✔
741
                                    .move();
1,139✔
742
                this->lf_format_match_messages.emplace_back(match_um);
1,139✔
743
                this->lf_mismatched_formats.insert(curr->get_name());
1,139✔
744
                continue;
1,139✔
745
            }
1,139✔
746
            if (this->lf_options.loo_format_name
849,658✔
747
                && !(curr->get_name()
849,658✔
748
                     == this->lf_options.loo_format_name.value()))
1,699,316✔
749
            {
750
                if (li.li_file_range.fr_offset == 0) {
×
751
                    log_debug("(%s) does not match file format: %s",
×
752
                              curr->get_name().get(),
753
                              fmt::to_string(this->lf_options.loo_file_format)
754
                                  .c_str());
755
                }
756
                continue;
×
757
            }
758

759
            scan_count += 1;
849,658✔
760
            curr->clear();
849,658✔
761
            this->set_format_base_time(curr.get(), li);
849,658✔
762
            log_format::scan_result_t scan_res{mapbox::util::no_init{}};
849,658✔
763
            pattern_locks line_locks;
849,658✔
764
            scan_batch_context sbc_tmp{
849,658✔
765
                this->lf_allocator,
849,658✔
766
                line_locks,
767
            };
849,658✔
768
            if (this->lf_format != nullptr
849,658✔
769
                && this->lf_format->lf_root_format == curr.get())
849,658✔
770
            {
771
                scan_res = this->lf_format->scan(
20,048✔
772
                    *this, this->lf_index, li, sbr, sbc);
10,024✔
773
            } else {
774
                scan_res = curr->scan(*this, this->lf_index, li, sbr, sbc_tmp);
839,634✔
775
            }
776

777
            scan_res.match(
849,658✔
778
                [this,
849,658✔
779
                 &sbc,
780
                 &sbc_tmp,
781
                 &found,
782
                 &curr,
783
                 &best_match,
784
                 &prev_index_size,
785
                 starting_index_size](const log_format::scan_match& sm) {
786
                    if (best_match && this->lf_format != nullptr
24,572✔
787
                        && this->lf_format->lf_root_format == curr.get()
11,819✔
788
                        && best_match->first == this->lf_format.get())
24,572✔
789
                    {
790
                        prev_index_size = this->lf_index.size();
9,123✔
791
                        found = best_match->second;
9,123✔
792
                    } else if (!best_match
3,444✔
793
                               || (sm.sm_quality > best_match->second.sm_quality
6,293✔
794
                                   || (sm.sm_quality
5,698✔
795
                                           == best_match->second.sm_quality
2,849✔
796
                                       && sm.sm_strikes
258✔
797
                                           < best_match->second.sm_strikes)))
129✔
798
                    {
799
                        log_info(
595✔
800
                            "  scan with format (%s) matched with quality of "
801
                            "%d and %d strikes",
802
                            curr->get_name().c_str(),
803
                            sm.sm_quality,
804
                            sm.sm_strikes);
805

806
                        sbc.sbc_opids = sbc_tmp.sbc_opids;
595✔
807
                        sbc.sbc_tids = sbc_tmp.sbc_tids;
595✔
808
                        sbc.sbc_value_stats = sbc_tmp.sbc_value_stats;
595✔
809
                        sbc.sbc_pattern_locks = sbc_tmp.sbc_pattern_locks;
595✔
810
                        auto match_um
811
                            = lnav::console::user_message::info(
×
812
                                  attr_line_t()
595✔
813
                                      .append(lnav::roles::identifier(
595✔
814
                                          curr->get_name().to_string()))
1,190✔
815
                                      .append(" matched line ")
595✔
816
                                      .append(lnav::roles::number(
1,190✔
817
                                          fmt::to_string(starting_index_size))))
1,190✔
818
                                  .with_note(
1,190✔
819
                                      attr_line_t("match quality is ")
1,190✔
820
                                          .append(lnav::roles::number(
595✔
821
                                              fmt::to_string(sm.sm_quality)))
1,190✔
822
                                          .append(" with ")
595✔
823
                                          .append(lnav::roles::number(
1,190✔
824
                                              fmt::to_string(sm.sm_strikes)))
1,190✔
825
                                          .append(" strikes"))
595✔
826
                                  .move();
595✔
827
                        this->lf_format_match_messages.emplace_back(match_um);
595✔
828
                        if (best_match) {
595✔
829
                            auto starting_iter = std::next(
66✔
830
                                this->lf_index.begin(), starting_index_size);
33✔
831
                            auto last_iter = std::next(this->lf_index.begin(),
66✔
832
                                                       prev_index_size);
33✔
833
                            this->lf_index.erase(starting_iter, last_iter);
33✔
834
                        }
835
                        best_match = std::make_pair(curr.get(), sm);
595✔
836
                        prev_index_size = this->lf_index.size();
595✔
837
                    } else {
595✔
838
                        log_trace(
2,849✔
839
                            "  scan with format (%s) matched, but "
840
                            "is lower quality (%d < %d) or more strikes (%d "
841
                            "vs. %d)",
842
                            curr->get_name().c_str(),
843
                            sm.sm_quality,
844
                            best_match->second.sm_quality,
845
                            sm.sm_strikes,
846
                            best_match->second.sm_strikes);
847
                        while (this->lf_index.size() > prev_index_size) {
6,379✔
848
                            this->lf_index.pop_back();
3,530✔
849
                        }
850
                    }
851
                },
12,567✔
852
                [curr](const log_format::scan_incomplete& si) {
849,658✔
853
                    log_trace(
19✔
854
                        "  scan with format (%s) is incomplete, "
855
                        "more data required",
856
                        curr->get_name().c_str());
857
                },
19✔
858
                [this, curr, prescan_size](
1,699,316✔
859
                    const log_format::scan_no_match& snm) {
860
                    if (this->lf_format == nullptr && prescan_size < 5) {
837,072✔
861
                        log_trace(
61,993✔
862
                            "  scan with format (%s) does not match -- %s",
863
                            curr->get_name().c_str(),
864
                            snm.snm_reason);
865
                    }
866
                });
837,072✔
867
        }
850,797✔
868

869
        if (!scan_count) {
12,048✔
870
            log_info("%s: no formats available to scan, no longer detecting",
×
871
                     this->lf_filename_as_string.c_str());
872
            this->lf_options.loo_detect_format = false;
×
873
        }
874

875
        if (best_match
12,048✔
876
            && (this->lf_format == nullptr
22,072✔
877
                || ((this->lf_format->lf_root_format != best_match->first)
10,024✔
878
                    && best_match->second.sm_quality
10,024✔
879
                        > this->lf_format_quality)))
22,072✔
880
        {
881
            auto winner = best_match.value();
562✔
882
            auto* curr = winner.first;
562✔
883
            log_info("%s:%zu:log format found -- %s",
562✔
884
                     this->lf_filename_as_string.c_str(),
885
                     this->lf_index.size(),
886
                     curr->get_name().get());
887

888
            auto match_um = lnav::console::user_message::ok(
889
                attr_line_t()
562✔
890
                    .append(lnav::roles::identifier(
562✔
891
                        winner.first->get_name().to_string()))
1,124✔
892
                    .append(" is the best match for line ")
562✔
893
                    .append(lnav::roles::number(
1,124✔
894
                        fmt::to_string(starting_index_size))));
1,686✔
895
            this->lf_format_match_messages.emplace_back(match_um);
562✔
896
            this->lf_text_format = text_format_t::TF_LOG;
562✔
897
            this->lf_format = curr->specialized();
562✔
898
            this->lf_format_quality = winner.second.sm_quality;
562✔
899
            this->set_format_base_time(this->lf_format.get(), li);
562✔
900
            if (this->lf_format->lf_date_time.dts_fmt_lock != -1) {
562✔
901
                this->lf_content_id
902
                    = hasher().update(sbr.get_data(), sbr.length()).to_string();
543✔
903
            }
904

905
            this->lf_applicable_taggers.clear();
562✔
906
            for (auto& td_pair : this->lf_format->lf_tag_defs) {
575✔
907
                bool matches = td_pair.second->ftd_paths.empty();
13✔
908
                for (const auto& pr : td_pair.second->ftd_paths) {
18✔
909
                    if (pr.matches(this->lf_filename_as_string.c_str())) {
5✔
910
                        matches = true;
×
911
                        break;
×
912
                    }
913
                }
914
                if (!matches) {
13✔
915
                    continue;
5✔
916
                }
917

918
                log_info("%s: found applicable tag definition /%s/tags/%s",
8✔
919
                         this->lf_filename_as_string.c_str(),
920
                         this->lf_format->get_name().get(),
921
                         td_pair.second->ftd_name.c_str());
922
                this->lf_applicable_taggers.emplace_back(td_pair.second);
8✔
923
            }
924

925
            this->lf_applicable_partitioners.clear();
562✔
926
            for (auto& pd_pair : this->lf_format->lf_partition_defs) {
575✔
927
                bool matches = pd_pair.second->fpd_paths.empty();
13✔
928
                for (const auto& pr : pd_pair.second->fpd_paths) {
18✔
929
                    if (pr.matches(this->lf_filename_as_string.c_str())) {
5✔
930
                        matches = true;
×
931
                        break;
×
932
                    }
933
                }
934
                if (!matches) {
13✔
935
                    continue;
5✔
936
                }
937

938
                log_info(
8✔
939
                    "%s: found applicable partition definition "
940
                    "/%s/partitions/%s",
941
                    this->lf_filename_as_string.c_str(),
942
                    this->lf_format->get_name().get(),
943
                    pd_pair.second->fpd_name.c_str());
944
                this->lf_applicable_partitioners.emplace_back(pd_pair.second);
8✔
945
            }
946

947
            /*
948
             * We'll go ahead and assume that any previous lines were
949
             * written out at the same time as the last one, so we need to
950
             * go back and update everything.
951
             */
952
            const auto& last_line = this->lf_index.back();
562✔
953

954
            require_lt(starting_index_size, this->lf_index.size());
562✔
955
            for (size_t lpc = 0; lpc < starting_index_size; lpc++) {
809✔
956
                if (this->lf_format->lf_multiline) {
247✔
957
                    this->lf_index[lpc].set_time(
247✔
958
                        last_line.get_time<std::chrono::microseconds>());
959
                    if (this->lf_format->lf_structured) {
247✔
960
                        this->lf_index[lpc].set_ignore(true);
240✔
961
                    }
962
                } else {
963
                    this->lf_index[lpc].set_time(
×
964
                        last_line.get_time<std::chrono::microseconds>());
965
                    this->lf_index[lpc].set_level(LEVEL_INVALID);
×
966
                }
967
                retval = true;
247✔
968
            }
969

970
            found = best_match->second;
562✔
971
        }
562✔
972
    } else if (this->lf_format.get() != nullptr) {
6,357✔
973
        if (!this->lf_index.empty()) {
3,585✔
974
            prescan_time = this->lf_index[prescan_size - 1]
3,585✔
975
                               .get_time<std::chrono::microseconds>();
3,585✔
976
        }
977
        /* We've locked onto a format, just use that scanner. */
978
        found = this->lf_format->scan(*this, this->lf_index, li, sbr, sbc);
3,585✔
979
    }
980

981
    if (found.is<log_format::scan_match>()) {
18,405✔
982
        if (!this->lf_index.empty()) {
12,172✔
983
            auto& last_line = this->lf_index.back();
12,172✔
984

985
            last_line.set_valid_utf(last_line.is_valid_utf()
24,344✔
986
                                    && li.li_utf8_scan_result.is_valid());
12,172✔
987
            last_line.set_has_ansi(last_line.has_ansi()
24,344✔
988
                                   || li.li_utf8_scan_result.usr_has_ansi);
12,172✔
989
            if (last_line.get_msg_level() == LEVEL_INVALID) {
12,172✔
990
                if (this->lf_invalid_lines.ili_lines.size()
9✔
991
                    < invalid_line_info::MAX_INVALID_LINES)
9✔
992
                {
993
                    this->lf_invalid_lines.ili_lines.push_back(
18✔
994
                        this->lf_index.size() - 1);
9✔
995
                }
996
                this->lf_invalid_lines.ili_total += 1;
9✔
997
            }
998
        }
999
        if (prescan_size > 0 && this->lf_index.size() >= prescan_size
11,172✔
1000
            && prescan_time
23,344✔
1001
                != this->lf_index[prescan_size - 1]
22,344✔
1002
                       .get_time<std::chrono::microseconds>())
23,344✔
1003
        {
1004
            retval = true;
56✔
1005
        }
1006
        if (prescan_size > 0 && prescan_size < this->lf_index.size()) {
12,172✔
1007
            auto& second_to_last = this->lf_index[prescan_size - 1];
11,149✔
1008
            auto& latest = this->lf_index[prescan_size];
11,149✔
1009

1010
            if (!second_to_last.is_ignored() && latest < second_to_last) {
11,149✔
1011
                if (this->lf_format->lf_time_ordered) {
1,288✔
1012
                    this->lf_out_of_time_order_count += 1;
16✔
1013
                    for (size_t lpc = prescan_size; lpc < this->lf_index.size();
32✔
1014
                         lpc++)
1015
                    {
1016
                        auto& line_to_update = this->lf_index[lpc];
16✔
1017

1018
                        line_to_update.set_time_skew(true);
16✔
1019
                        line_to_update.set_time(
16✔
1020
                            second_to_last
1021
                                .get_time<std::chrono::microseconds>());
1022
                    }
1023
                } else {
1024
                    retval = true;
1,272✔
1025
                }
1026
            }
1027
        }
1028
    } else if (found.is<log_format::scan_no_match>()) {
6,233✔
1029
        log_level_t last_level = LEVEL_UNKNOWN;
6,233✔
1030
        auto last_time = this->lf_index_time;
6,233✔
1031
        uint8_t last_opid = 0;
6,233✔
1032

1033
        if (this->lf_format == nullptr && li.li_timestamp.tv_sec != 0) {
6,233✔
1034
            last_time = std::chrono::duration_cast<std::chrono::microseconds>(
36✔
1035
                            std::chrono::seconds{li.li_timestamp.tv_sec})
36✔
1036
                + std::chrono::microseconds(li.li_timestamp.tv_usec);
72✔
1037
            last_level = li.li_level;
36✔
1038
        } else if (!this->lf_index.empty()) {
6,197✔
1039
            const auto& ll = this->lf_index.back();
6,095✔
1040

1041
            /*
1042
             * Assume this line is part of the previous one(s) and copy the
1043
             * metadata over.
1044
             */
1045
            last_time = ll.get_time<std::chrono::microseconds>();
6,095✔
1046
            if (this->lf_format.get() != nullptr) {
6,095✔
1047
                last_level = (log_level_t) (ll.get_level_and_flags()
1,999✔
1048
                                            | LEVEL_CONTINUED);
1,999✔
1049
            }
1050
            last_opid = ll.get_opid();
6,095✔
1051
        }
1052
        this->lf_index.emplace_back(
6,233✔
1053
            li.li_file_range.fr_offset, last_time, last_level, last_opid);
6,233✔
1054
        this->lf_index.back().set_valid_utf(li.li_utf8_scan_result.is_valid());
6,233✔
1055
        this->lf_index.back().set_has_ansi(li.li_utf8_scan_result.usr_has_ansi);
6,233✔
1056
    }
1057

1058
    if (this->lf_format != nullptr
18,405✔
1059
        && this->lf_index.back().get_time<std::chrono::microseconds>()
46,747✔
1060
            > this->lf_options.loo_time_range.tr_end)
28,342✔
1061
    {
1062
        if (!this->lf_upper_bound_size) {
×
1063
            this->lf_upper_bound_size = this->lf_index.back().get_offset();
×
1064
            log_debug("%s:%zu: upper found in file found %llu",
×
1065
                      this->lf_filename_as_string.c_str(),
1066
                      this->lf_index.size(),
1067
                      this->lf_upper_bound_size.value());
1068
        }
1069
        this->lf_index.pop_back();
×
1070
    }
1071

1072
    return retval;
18,405✔
1073
}
18,405✔
1074

1075
logfile::rebuild_result_t
1076
logfile::rebuild_index(std::optional<ui_clock::time_point> deadline)
4,277✔
1077
{
1078
    static const auto& dts_cfg
1079
        = injector::get<const date_time_scanner_ns::config&>();
4,277✔
1080

1081
    if (!this->lf_invalidated_opids.empty()) {
4,277✔
1082
        auto writeOpids = this->lf_opids.writeAccess();
×
1083

1084
        for (auto bm_pair : this->lf_bookmark_metadata) {
×
1085
            if (bm_pair.second.bm_opid.empty()) {
×
1086
                continue;
×
1087
            }
1088

1089
            if (!this->lf_invalidated_opids.contains(bm_pair.second.bm_opid)) {
×
1090
                continue;
×
1091
            }
1092

1093
            auto opid_iter
1094
                = writeOpids->los_opid_ranges.find(bm_pair.second.bm_opid);
×
1095
            if (opid_iter == writeOpids->los_opid_ranges.end()) {
×
1096
                log_warning("opid not in ranges: %s",
×
1097
                            bm_pair.second.bm_opid.c_str());
1098
                continue;
×
1099
            }
1100

1101
            if (bm_pair.first >= this->lf_index.size()) {
×
1102
                log_warning("stale bookmark: %d", bm_pair.first);
×
1103
                continue;
×
1104
            }
1105

1106
            auto& ll = this->lf_index[bm_pair.first];
×
1107
            opid_iter->second.otr_range.extend_to(
×
1108
                ll.get_time<std::chrono::microseconds>());
×
1109
            opid_iter->second.otr_level_stats.update_msg_count(
×
1110
                ll.get_msg_level());
1111
        }
1112
        this->lf_invalidated_opids.clear();
×
1113
    }
1114

1115
    if (!this->lf_indexing) {
4,277✔
1116
        if (this->lf_sort_needed) {
19✔
1117
            this->lf_sort_needed = false;
×
1118
            return rebuild_result_t::NEW_ORDER;
×
1119
        }
1120
        return rebuild_result_t::NO_NEW_LINES;
19✔
1121
    }
1122

1123
    if (this->file_options_have_changed()
4,258✔
1124
        || (this->lf_format != nullptr
7,302✔
1125
            && (this->lf_zoned_to_local_state != dts_cfg.c_zoned_to_local
3,044✔
1126
                || this->lf_format->format_changed())))
3,044✔
1127
    {
1128
        log_info("%s: format has changed, rebuilding",
5✔
1129
                 this->lf_filename_as_string.c_str());
1130
        this->lf_index.clear();
5✔
1131
        this->lf_index_size = 0;
5✔
1132
        this->lf_partial_line = false;
5✔
1133
        this->lf_longest_line = 0;
5✔
1134
        this->lf_sort_needed = true;
5✔
1135
        this->lf_pattern_locks.pl_lines.clear();
5✔
1136
        this->lf_value_stats.clear();
5✔
1137
        {
1138
            safe::WriteAccess<safe_opid_state> writable_opid_map(
1139
                this->lf_opids);
5✔
1140

1141
            writable_opid_map->los_opid_ranges.clear();
5✔
1142
            writable_opid_map->los_sub_in_use.clear();
5✔
1143
        }
5✔
1144
        {
1145
            auto tids = this->lf_thread_ids.writeAccess();
5✔
1146

1147
            tids->ltis_tid_ranges.clear();
5✔
1148
        }
5✔
1149
        this->lf_allocator.reset();
5✔
1150
        if (this->lf_logline_observer) {
5✔
1151
            this->lf_logline_observer->logline_clear(*this);
5✔
1152
        }
1153
    }
1154
    this->lf_zoned_to_local_state = dts_cfg.c_zoned_to_local;
4,258✔
1155

1156
    auto retval = rebuild_result_t::NO_NEW_LINES;
4,258✔
1157
    struct stat st;
1158

1159
    this->lf_activity.la_polls += 1;
4,258✔
1160

1161
    if (fstat(this->lf_line_buffer.get_fd(), &st) == -1) {
4,258✔
1162
        if (errno == EINTR) {
×
1163
            return rebuild_result_t::NO_NEW_LINES;
×
1164
        }
1165
        return rebuild_result_t::INVALID;
×
1166
    }
1167

1168
    const auto is_truncated = st.st_size < this->lf_stat.st_size;
4,258✔
1169
    const auto is_user_provided_and_rewritten = (
4,258✔
1170
        // files from other sources can have their mtimes monkeyed with
1171
        this->lf_options.loo_source == logfile_name_source::USER
4,258✔
1172
        && this->lf_stat.st_size == st.st_size
4,258✔
1173
        && this->lf_stat.st_mtime != st.st_mtime);
8,516✔
1174

1175
    // Check the previous stat against the last to see if things are wonky.
1176
    if (this->lf_named_file && (is_truncated || is_user_provided_and_rewritten))
4,258✔
1177
    {
1178
        auto is_overwritten = true;
1✔
1179
        if (this->lf_format != nullptr) {
1✔
1180
            const auto first_line = this->lf_index.begin();
1✔
1181
            const auto first_line_range
1182
                = this->get_file_range(first_line, false);
1✔
1183
            auto read_res = this->read_range(first_line_range);
1✔
1184
            if (read_res.isOk()) {
1✔
1185
                auto sbr = read_res.unwrap();
1✔
1186
                if (first_line->has_ansi()) {
1✔
1187
                    sbr.erase_ansi();
×
1188
                }
1189
                auto curr_content_id
1190
                    = hasher().update(sbr.get_data(), sbr.length()).to_string();
1✔
1191

1192
                log_info(
1✔
1193
                    "%s: overwrite content_id double check: old:%s; now:%s",
1194
                    this->lf_filename_as_string.c_str(),
1195
                    this->lf_content_id.c_str(),
1196
                    curr_content_id.c_str());
1197
                if (this->lf_content_id == curr_content_id) {
1✔
1198
                    is_overwritten = false;
1✔
1199
                }
1200
            } else {
1✔
1201
                auto errmsg = read_res.unwrapErr();
×
1202
                log_error("unable to read first line for overwrite check: %s",
×
1203
                          errmsg.c_str());
1204
            }
1205
        }
1✔
1206

1207
        if (is_truncated || is_overwritten) {
1✔
1208
            log_info("overwritten file detected, closing -- %s  new: %" PRId64
1✔
1209
                     "/%" PRId64 "  old: %" PRId64 "/%" PRId64,
1210
                     this->lf_filename_as_string.c_str(),
1211
                     st.st_size,
1212
                     st.st_mtime,
1213
                     this->lf_stat.st_size,
1214
                     this->lf_stat.st_mtime);
1215
            this->close();
1✔
1216
            return rebuild_result_t::NO_NEW_LINES;
1✔
1217
        }
1218
    }
1219

1220
    if (this->lf_text_format == text_format_t::TF_BINARY) {
4,257✔
1221
        this->lf_index_size = st.st_size;
22✔
1222
        this->lf_stat = st;
22✔
1223
    } else if (this->lf_upper_bound_size) {
4,235✔
1224
        this->lf_index_size = this->get_content_size();
×
1225
        this->lf_stat = st;
×
1226
    } else if (this->lf_line_buffer.is_data_available(this->lf_index_size,
4,235✔
1227
                                                      st.st_size))
1228
    {
1229
        this->lf_activity.la_reads += 1;
1,168✔
1230

1231
        // We haven't reached the end of the file.  Note that we use the
1232
        // line buffer's notion of the file size since it may be compressed.
1233
        bool has_format = this->lf_format.get() != nullptr;
1,168✔
1234
        struct rusage begin_rusage;
1235
        file_off_t off;
1236
        size_t begin_size = this->lf_index.size();
1,168✔
1237
        bool record_rusage = this->lf_index.size() == 1;
1,168✔
1238
        off_t begin_index_size = this->lf_index_size;
1,168✔
1239
        size_t rollback_size = 0, rollback_index_start = 0;
1,168✔
1240

1241
        if (record_rusage) {
1,168✔
1242
            getrusage(RUSAGE_SELF, &begin_rusage);
451✔
1243
        }
1244

1245
        if (begin_size == 0 && !has_format) {
1,168✔
1246
            log_debug("scanning file... %s",
649✔
1247
                      this->lf_filename_as_string.c_str());
1248
        }
1249

1250
        if (!this->lf_index.empty()) {
1,168✔
1251
            off = this->lf_index.back().get_offset();
514✔
1252

1253
            /*
1254
             * Drop the last line we read since it might have been a partial
1255
             * read.
1256
             */
1257
            while (this->lf_index.back().get_sub_offset() != 0) {
624✔
1258
                this->lf_index.pop_back();
110✔
1259
                rollback_size += 1;
110✔
1260
            }
1261
            this->lf_index.pop_back();
514✔
1262
            rollback_index_start = this->lf_index.size();
514✔
1263
            rollback_size += 1;
514✔
1264

1265
            if (!this->lf_index.empty()) {
514✔
1266
                auto last_line = std::prev(this->lf_index.end());
41✔
1267
                if (last_line != this->lf_index.begin()) {
41✔
1268
                    auto prev_line = std::prev(last_line);
40✔
1269
                    this->lf_line_buffer.flush_at(prev_line->get_offset());
40✔
1270
                    auto prev_len_res
1271
                        = this->message_byte_length(prev_line, false);
40✔
1272

1273
                    auto read_result = this->lf_line_buffer.read_range({
1274
                        prev_line->get_offset(),
40✔
1275
                        prev_len_res.mlr_length + 1,
40✔
1276
                    });
80✔
1277
                    if (read_result.isErr()) {
40✔
1278
                        log_info(
×
1279
                            "overwritten file detected, closing -- %s (%s)",
1280
                            this->lf_filename_as_string.c_str(),
1281
                            read_result.unwrapErr().c_str());
1282
                        this->close();
×
1283
                        return rebuild_result_t::INVALID;
×
1284
                    }
1285

1286
                    auto sbr = read_result.unwrap();
40✔
1287
                    if (!sbr.to_string_fragment().endswith("\n")) {
40✔
1288
                        log_info("overwritten file detected, closing -- %s",
×
1289
                                 this->lf_filename_as_string.c_str());
1290
                        this->close();
×
1291
                        return rebuild_result_t::INVALID;
×
1292
                    }
1293
                } else {
40✔
1294
                    this->lf_line_buffer.flush_at(last_line->get_offset());
1✔
1295
                }
1296
                auto last_length_res
1297
                    = this->message_byte_length(last_line, false);
41✔
1298

1299
                auto read_result = this->lf_line_buffer.read_range({
1300
                    last_line->get_offset(),
41✔
1301
                    last_length_res.mlr_length,
41✔
1302
                });
82✔
1303

1304
                if (read_result.isErr()) {
41✔
1305
                    log_info("overwritten file detected, closing -- %s (%s)",
×
1306
                             this->lf_filename_as_string.c_str(),
1307
                             read_result.unwrapErr().c_str());
1308
                    this->close();
×
1309
                    return rebuild_result_t::INVALID;
×
1310
                }
1311
            } else {
41✔
1312
                this->lf_line_buffer.flush_at(0);
473✔
1313
            }
1314
        } else {
1315
            this->lf_line_buffer.flush_at(0);
654✔
1316
            off = this->lf_index_size;
654✔
1317
        }
1318
        if (this->lf_logline_observer != nullptr) {
1,168✔
1319
            this->lf_logline_observer->logline_restart(*this, rollback_size);
1,079✔
1320
        }
1321

1322
        bool sort_needed = std::exchange(this->lf_sort_needed, false);
1,168✔
1323
        size_t limit = SIZE_MAX;
1,168✔
1324

1325
        if (deadline) {
1,168✔
1326
            if (ui_clock::now() > deadline.value()) {
2✔
1327
                if (has_format) {
×
1328
                    log_warning("with format ran past deadline! -- %s",
×
1329
                                this->lf_filename_as_string.c_str());
1330
                    limit = 1000;
×
1331
                } else {
1332
                    limit = 100;
×
1333
                }
1334
            } else if (this->lf_options.loo_detect_format && !has_format) {
2✔
1335
                limit = 1000;
1✔
1336
            } else {
1337
                limit = 1000 * 1000;
1✔
1338
            }
1339
        }
1340
        if (!has_format) {
1,168✔
1341
            log_debug("loading file... %s:%zu",
649✔
1342
                      this->lf_filename_as_string.c_str(),
1343
                      begin_size);
1344
        }
1345
        scan_batch_context sbc{this->lf_allocator, this->lf_pattern_locks};
1,168✔
1346
        sbc.sbc_opids.los_opid_ranges.reserve(32);
1,168✔
1347
        sbc.sbc_tids.ltis_tid_ranges.reserve(8);
1,168✔
1348
        auto prev_range = file_range{off};
1,168✔
1349
        while (limit > 0) {
18,990✔
1350
            auto load_result = this->lf_line_buffer.load_next_line(prev_range);
18,990✔
1351

1352
            if (load_result.isErr()) {
18,990✔
1353
                log_error("%s: load next line failure -- %s",
×
1354
                          this->lf_filename_as_string.c_str(),
1355
                          load_result.unwrapErr().c_str());
1356
                this->close();
×
1357
                return rebuild_result_t::INVALID;
×
1358
            }
1359

1360
            auto li = load_result.unwrap();
18,990✔
1361

1362
            if (li.li_file_range.empty()) {
18,990✔
1363
                break;
585✔
1364
            }
1365
            prev_range = li.li_file_range;
18,405✔
1366

1367
            if (this->lf_format == nullptr
18,405✔
1368
                && !this->lf_options.loo_non_utf_is_visible
4,796✔
1369
                && !li.li_utf8_scan_result.is_valid())
23,201✔
1370
            {
1371
                log_info("file is not utf, hiding: %s",
×
1372
                         this->lf_filename_as_string.c_str());
1373
                this->lf_indexing = false;
×
1374
                this->lf_options.loo_is_visible = false;
×
1375
                auto utf8_error_um
1376
                    = lnav::console::user_message::error("invalid UTF-8")
×
1377
                          .with_reason(
×
1378
                              attr_line_t(li.li_utf8_scan_result.usr_message)
×
1379
                                  .append(" at line ")
×
1380
                                  .append(lnav::roles::number(fmt::to_string(
×
1381
                                      this->lf_index.size() + 1)))
×
1382
                                  .append(" column ")
×
1383
                                  .append(lnav::roles::number(fmt::to_string(
×
1384
                                      li.li_utf8_scan_result.usr_valid_frag
1385
                                          .sf_end))))
1386
                          .move();
×
1387
                auto note_um = lnav::console::user_message::warning(
×
1388
                                   "skipping indexing for file")
1389
                                   .with_reason(utf8_error_um)
×
1390
                                   .move();
×
1391
                this->lf_notes.writeAccess()->insert(note_type::not_utf,
×
1392
                                                     note_um);
1393
                if (this->lf_logfile_observer != nullptr) {
×
1394
                    this->lf_logfile_observer->logfile_indexing(this, 0, 0);
×
1395
                }
1396
                break;
×
1397
            }
1398
            size_t old_size = this->lf_index.size();
18,405✔
1399

1400
            if (old_size == 0
18,405✔
1401
                && this->lf_text_format == text_format_t::TF_UNKNOWN)
1,127✔
1402
            {
1403
                auto fr = this->lf_line_buffer.get_available();
634✔
1404
                auto avail_data = this->lf_line_buffer.read_range(fr);
634✔
1405

1406
                this->lf_text_format
1407
                    = avail_data
634✔
1408
                          .map([path = this->get_path(),
1,268✔
1409
                                this](const shared_buffer_ref& avail_sbr)
1410
                                   -> text_format_t {
1411
                              constexpr auto DETECT_LIMIT = 16 * 1024;
634✔
1412
                              auto sbr_str = to_string(avail_sbr);
634✔
1413
                              if (sbr_str.size() > DETECT_LIMIT) {
634✔
1414
                                  sbr_str.resize(DETECT_LIMIT);
34✔
1415
                              }
1416

1417
                              if (this->lf_line_buffer.is_piper()) {
634✔
1418
                                  auto lines
1419
                                      = string_fragment::from_str(sbr_str)
46✔
1420
                                            .split_lines();
46✔
1421
                                  for (auto line_iter = lines.rbegin();
46✔
1422
                                       // XXX rejigger read_range() for
1423
                                       // multi-line reads
1424
                                       std::next(line_iter) != lines.rend();
266✔
1425
                                       ++line_iter)
87✔
1426
                                  {
1427
                                      sbr_str.erase(line_iter->sf_begin, 22);
87✔
1428
                                  }
1429
                              }
46✔
1430
                              auto utf8_res = is_utf8(sbr_str);
634✔
1431
                              if (!utf8_res.is_valid()) {
634✔
1432
                                  return text_format_t::TF_BINARY;
5✔
1433
                              }
1434
                              if (utf8_res.usr_has_ansi) {
629✔
1435
                                  auto new_size = erase_ansi_escapes(sbr_str);
16✔
1436
                                  sbr_str.resize(new_size);
16✔
1437
                              }
1438
                              return detect_text_format(sbr_str, path);
629✔
1439
                          })
634✔
1440
                          .unwrapOr(text_format_t::TF_UNKNOWN);
634✔
1441
                log_debug("setting text format to %s",
634✔
1442
                          fmt::to_string(this->lf_text_format).c_str());
1443
                switch (this->lf_text_format) {
634✔
1444
                    case text_format_t::TF_DIFF:
18✔
1445
                    case text_format_t::TF_MAN:
1446
                    case text_format_t::TF_MARKDOWN:
1447
                        log_debug(
18✔
1448
                            "  file is text, disabling log format detection");
1449
                        this->lf_options.loo_detect_format = false;
18✔
1450
                        break;
18✔
1451
                    default:
616✔
1452
                        break;
616✔
1453
                }
1454
            }
634✔
1455
            if (!li.li_utf8_scan_result.is_valid()
18,405✔
1456
                && this->lf_text_format != text_format_t::TF_MARKDOWN
53✔
1457
                && this->lf_text_format != text_format_t::TF_LOG)
18,458✔
1458
            {
1459
                this->lf_text_format = text_format_t::TF_BINARY;
53✔
1460
            }
1461

1462
            auto read_result
1463
                = this->lf_line_buffer.read_range(li.li_file_range);
18,405✔
1464
            if (read_result.isErr()) {
18,405✔
1465
                log_error("%s:read failure -- %s",
×
1466
                          this->lf_filename_as_string.c_str(),
1467
                          read_result.unwrapErr().c_str());
1468
                this->close();
×
1469
                return rebuild_result_t::INVALID;
×
1470
            }
1471

1472
            auto sbr = read_result.unwrap();
18,405✔
1473

1474
            if (!li.li_utf8_scan_result.is_valid()) {
18,405✔
1475
                log_warning(
53✔
1476
                    "%s: invalid UTF-8 detected at L%zu:C%d/%lld (O:%lld) -- "
1477
                    "%s",
1478
                    this->lf_filename_as_string.c_str(),
1479
                    this->lf_index.size() + 1,
1480
                    li.li_utf8_scan_result.usr_valid_frag.sf_end,
1481
                    li.li_file_range.fr_size,
1482
                    li.li_file_range.fr_offset,
1483
                    li.li_utf8_scan_result.usr_message);
1484
                if (lnav_log_level <= lnav_log_level_t::TRACE) {
53✔
1485
                    attr_line_t al;
×
1486
                    attr_line_builder alb(al);
×
1487
                    alb.append_as_hexdump(
×
1488
                        sbr.to_string_fragment().sub_range(0, 256));
×
1489
                    log_warning("  dump: %s", al.al_string.c_str());
×
1490
                }
1491
            }
1492

1493
            sbr.rtrim(is_line_ending);
18,405✔
1494

1495
            if (li.li_utf8_scan_result.is_valid()
18,405✔
1496
                && li.li_utf8_scan_result.usr_has_ansi)
18,405✔
1497
            {
1498
                sbr.erase_ansi();
105✔
1499
            }
1500

1501
            this->lf_longest_line
1502
                = std::max(this->lf_longest_line,
18,405✔
1503
                           li.li_utf8_scan_result.usr_column_width_guess);
1504
            this->lf_partial_line = li.li_partial;
18,405✔
1505
            sort_needed = this->process_prefix(sbr, li, sbc) || sort_needed;
18,405✔
1506

1507
            if (old_size > this->lf_index.size()) {
18,405✔
1508
                old_size = 0;
×
1509
            }
1510

1511
            // Update this early so that line_length() works
1512
            this->lf_index_size = li.li_file_range.next_offset();
18,405✔
1513

1514
            if (this->lf_logline_observer != nullptr) {
18,405✔
1515
                auto nl_rc = this->lf_logline_observer->logline_new_lines(
35,528✔
1516
                    *this, this->begin() + old_size, this->end(), sbr);
35,528✔
1517
                if (rollback_size > 0 && old_size == rollback_index_start
17,764✔
1518
                    && nl_rc)
470✔
1519
                {
1520
                    log_debug(
3✔
1521
                        "%s: rollbacked line %zu matched filter, forcing "
1522
                        "full sort",
1523
                        this->lf_filename_as_string.c_str(),
1524
                        rollback_index_start);
1525
                    sort_needed = true;
3✔
1526
                }
1527
            }
1528

1529
            if (this->lf_logfile_observer != nullptr) {
18,405✔
1530
                auto indexing_res = this->lf_logfile_observer->logfile_indexing(
17,764✔
1531
                    this,
1532
                    this->lf_line_buffer.get_read_offset(
1533
                        li.li_file_range.next_offset()),
1534
                    this->get_content_size());
1535

1536
                if (indexing_res == lnav::progress_result_t::interrupt) {
17,764✔
1537
                    break;
×
1538
                }
1539
            }
1540

1541
            if (!has_format && this->lf_format != nullptr) {
18,405✔
1542
                break;
562✔
1543
            }
1544
            if (begin_size == 0 && !has_format
17,843✔
1545
                && li.li_file_range.fr_offset > 16 * 1024)
4,234✔
1546
            {
1547
                break;
1✔
1548
            }
1549
#if 0
1550
            if (this->lf_line_buffer.is_likely_to_flush(prev_range)
1551
                && this->lf_index.size() - begin_size > 1)
1552
            {
1553
                log_debug("likely to flush, breaking");
1554
                break;
1555
            }
1556
#endif
1557
            if (this->lf_format) {
17,842✔
1558
                auto sf = sbr.to_string_fragment();
13,609✔
1559

1560
                for (const auto& td : this->lf_applicable_taggers) {
13,753✔
1561
                    auto curr_ll = this->end() - 1;
144✔
1562

1563
                    if (td->ftd_level != LEVEL_UNKNOWN
144✔
1564
                        && td->ftd_level != curr_ll->get_msg_level())
144✔
1565
                    {
1566
                        continue;
×
1567
                    }
1568

1569
                    if (td->ftd_pattern.pp_value
144✔
1570
                            ->find_in(sf, PCRE2_NO_UTF_CHECK)
288✔
1571
                            .ignore_error()
288✔
1572
                            .has_value())
144✔
1573
                    {
1574
                        while (curr_ll->is_continued()) {
4✔
1575
                            --curr_ll;
×
1576
                        }
1577
                        curr_ll->set_meta_mark(true);
4✔
1578
                        auto line_number = static_cast<uint32_t>(
1579
                            std::distance(this->begin(), curr_ll));
4✔
1580

1581
                        this->lf_bookmark_metadata[line_number].add_tag(
4✔
1582
                            td->ftd_name);
4✔
1583
                    }
1584
                }
1585

1586
                for (const auto& pd : this->lf_applicable_partitioners) {
13,728✔
1587
                    thread_local auto part_md
1588
                        = lnav::pcre2pp::match_data::unitialized();
119✔
1589

1590
                    auto curr_ll = this->end() - 1;
119✔
1591

1592
                    if (pd->fpd_level != LEVEL_UNKNOWN
119✔
1593
                        && pd->fpd_level != curr_ll->get_msg_level())
119✔
1594
                    {
1595
                        continue;
×
1596
                    }
1597

1598
                    auto match_res = pd->fpd_pattern.pp_value->capture_from(sf)
119✔
1599
                                         .into(part_md)
119✔
1600
                                         .matches(PCRE2_NO_UTF_CHECK)
238✔
1601
                                         .ignore_error();
119✔
1602
                    if (match_res) {
119✔
1603
                        while (curr_ll->is_continued()) {
8✔
1604
                            --curr_ll;
×
1605
                        }
1606
                        curr_ll->set_meta_mark(true);
8✔
1607
                        auto line_number = static_cast<uint32_t>(
1608
                            std::distance(this->begin(), curr_ll));
8✔
1609

1610
                        this->lf_bookmark_metadata[line_number].bm_name
8✔
1611
                            = part_md.to_string();
16✔
1612
                    }
1613
                }
1614

1615
                if (!this->back().is_continued()) {
13,609✔
1616
                    lnav::log::watch::eval_with(*this, this->end() - 1);
11,316✔
1617
                }
1618
            }
1619

1620
            if (li.li_partial) {
17,842✔
1621
                // The last read was at the end of the file, so break.  We'll
1622
                // need to cycle back around to pop off this partial line in
1623
                // order to continue reading correctly.
1624
                break;
20✔
1625
            }
1626

1627
            if (this->lf_upper_bound_size) {
17,822✔
1628
                break;
×
1629
            }
1630

1631
            limit -= 1;
17,822✔
1632
        }
20,156✔
1633

1634
        if (this->lf_format == nullptr
1,168✔
1635
            && this->lf_options.loo_visible_size_limit > 0
87✔
1636
            && prev_range.fr_offset > 256 * 1024
×
1637
            && st.st_size >= this->lf_options.loo_visible_size_limit)
1,255✔
1638
        {
1639
            log_info("file has unknown format and is too large: %s",
×
1640
                     this->lf_filename_as_string.c_str());
1641
            this->lf_indexing = false;
×
1642
            auto note_um
1643
                = lnav::console::user_message::warning(
×
1644
                      "skipping indexing for file")
1645
                      .with_reason(
×
1646
                          "file is large and has no discernible log format")
1647
                      .move();
×
1648
            this->lf_notes.writeAccess()->insert(note_type::indexing_disabled,
×
1649
                                                 note_um);
1650
            if (this->lf_logfile_observer != nullptr) {
×
1651
                this->lf_logfile_observer->logfile_indexing(this, 0, 0);
×
1652
            }
1653
        }
1654

1655
        if (this->lf_logline_observer != nullptr) {
1,168✔
1656
            this->lf_logline_observer->logline_eof(*this);
1,079✔
1657
        }
1658

1659
        if (record_rusage
1,168✔
1660
            && (prev_range.fr_offset - begin_index_size) > (500 * 1024))
451✔
1661
        {
1662
            rusage end_rusage;
1663

1664
            getrusage(RUSAGE_SELF, &end_rusage);
×
1665
            rusagesub(end_rusage,
×
1666
                      begin_rusage,
1667
                      this->lf_activity.la_initial_index_rusage);
×
1668
            log_info("Resource usage for initial indexing of file: %s:%zu-%zu",
×
1669
                     this->lf_filename_as_string.c_str(),
1670
                     begin_size,
1671
                     this->lf_index.size());
1672
            log_rusage(lnav_log_level_t::INFO,
×
1673
                       this->lf_activity.la_initial_index_rusage);
1674
        }
1675

1676
        /*
1677
         * The file can still grow between the above fstat and when we're
1678
         * doing the scanning, so use the line buffer's notion of the file
1679
         * size.
1680
         */
1681
        this->lf_index_size = prev_range.next_offset();
1,168✔
1682
        this->lf_stat = st;
1,168✔
1683

1684
        this->lf_value_stats.resize(sbc.sbc_value_stats.size());
1,168✔
1685
        for (size_t lpc = 0; lpc < sbc.sbc_value_stats.size(); lpc++) {
16,008✔
1686
            this->lf_value_stats[lpc].merge(sbc.sbc_value_stats[lpc]);
14,840✔
1687
        }
1688
        {
1689
            safe::WriteAccess<safe_opid_state> writable_opid_map(
1690
                this->lf_opids);
1,168✔
1691

1692
            for (const auto& opid_pair : sbc.sbc_opids.los_opid_ranges) {
4,987✔
1693
                auto opid_iter
1694
                    = writable_opid_map->los_opid_ranges.find(opid_pair.first);
3,819✔
1695

1696
                if (opid_iter == writable_opid_map->los_opid_ranges.end()) {
3,819✔
1697
                    writable_opid_map->los_opid_ranges.emplace(opid_pair);
3,454✔
1698
                } else {
1699
                    opid_iter->second |= opid_pair.second;
365✔
1700
                }
1701
            }
1702
            log_debug(
1,168✔
1703
                "%s: opid_map size: count=%zu; sizeof(otr)=%zu; alloc=%zu",
1704
                this->lf_filename_as_string.c_str(),
1705
                writable_opid_map->los_opid_ranges.size(),
1706
                sizeof(opid_time_range),
1707
                this->lf_allocator.getNumBytesAllocated());
1708
        }
1,168✔
1709
        {
1710
            auto tids = this->lf_thread_ids.writeAccess();
1,168✔
1711

1712
            for (const auto& tid_pair : sbc.sbc_tids.ltis_tid_ranges) {
2,433✔
1713
                auto tid_iter = tids->ltis_tid_ranges.find(tid_pair.first);
1,265✔
1714
                if (tid_iter == tids->ltis_tid_ranges.end()) {
1,265✔
1715
                    tids->ltis_tid_ranges.emplace(tid_pair);
794✔
1716
                } else {
1717
                    tid_iter->second |= tid_pair.second;
471✔
1718
                }
1719
            }
1720
            log_debug("%s: tid_map size: count=%zu; sizeof(otr)=%zu; alloc=%zu",
1,168✔
1721
                      this->lf_filename_as_string.c_str(),
1722
                      tids->ltis_tid_ranges.size(),
1723
                      sizeof(opid_time_range),
1724
                      this->lf_allocator.getNumBytesAllocated());
1725
        }
1,168✔
1726

1727
        if (begin_size > this->lf_index.size()) {
1,168✔
1728
            log_info("overwritten file detected, closing -- %s",
×
1729
                     this->lf_filename_as_string.c_str());
1730
            this->close();
×
1731
            return rebuild_result_t::INVALID;
×
1732
        }
1733

1734
        if (sort_needed || begin_size > this->lf_index.size()) {
1,168✔
1735
            retval = rebuild_result_t::NEW_ORDER;
93✔
1736
        } else {
1737
            retval = rebuild_result_t::NEW_LINES;
1,075✔
1738
        }
1739

1740
        {
1741
            auto est_rem = this->estimated_remaining_lines();
1,168✔
1742
            if (est_rem > 0) {
1,168✔
1743
                this->lf_index.reserve(this->lf_index.size() + est_rem);
473✔
1744
            }
1745
        }
1746

1747
        if (this->lf_format != nullptr
1,168✔
1748
            && (this->lf_index.size() >= RETRY_MATCH_SIZE
1,081✔
1749
                || this->lf_index_size == this->get_content_size())
1,071✔
1750
            && this->lf_file_size_at_map_time != this->lf_index_size)
2,249✔
1751
        {
1752
            switch (this->build_content_map()) {
566✔
1753
                case rebuild_result_t::NEW_ORDER:
×
1754
                    retval = rebuild_result_t::NEW_ORDER;
×
1755
                    break;
×
1756
                default:
566✔
1757
                    break;
566✔
1758
            }
1759
        }
1760
    } else {
1,168✔
1761
        this->lf_stat = st;
3,067✔
1762
        if (this->lf_sort_needed) {
3,067✔
1763
            retval = rebuild_result_t::NEW_ORDER;
13✔
1764
            this->lf_sort_needed = false;
13✔
1765
        }
1766
    }
1767

1768
    this->lf_index_time
1769
        = std::chrono::seconds{this->lf_line_buffer.get_file_time()};
4,257✔
1770
    if (this->lf_index_time.count() == 0) {
4,257✔
1771
        this->lf_index_time = std::chrono::seconds{st.st_mtime};
4,235✔
1772
    }
1773

1774
    if (this->lf_out_of_time_order_count) {
4,257✔
1775
        log_info("Detected %d out-of-time-order lines in file: %s",
8✔
1776
                 this->lf_out_of_time_order_count,
1777
                 this->lf_filename_as_string.c_str());
1778
        this->lf_out_of_time_order_count = 0;
8✔
1779
    }
1780

1781
    return retval;
4,257✔
1782
}
1783

1784
Result<shared_buffer_ref, std::string>
1785
logfile::read_line(iterator ll, subline_options opts)
27,707✔
1786
{
1787
    try {
1788
        auto get_range_res = this->get_file_range(ll, false);
27,707✔
1789
        return this->lf_line_buffer.read_range(get_range_res)
55,414✔
1790
            .map([&ll, &get_range_res, &opts, this](auto sbr) {
27,707✔
1791
                sbr.rtrim(is_line_ending);
27,707✔
1792
                if (!get_range_res.fr_metadata.m_valid_utf) {
27,707✔
1793
                    scrub_to_utf8(sbr.get_writable_data(), sbr.length());
6✔
1794
                    sbr.get_metadata().m_valid_utf = true;
6✔
1795
                }
1796

1797
                if (this->lf_format != nullptr) {
27,707✔
1798
                    this->lf_format->get_subline(
47,044✔
1799
                        {this->lf_value_stats, this->lf_pattern_locks},
23,522✔
1800
                        *ll,
23,522✔
1801
                        sbr,
1802
                        opts);
1803
                }
1804

1805
                return sbr;
27,707✔
1806
            });
27,707✔
1807
    } catch (const line_buffer::error& e) {
×
1808
        return Err(std::error_code{e.e_err, std::generic_category()}.message());
×
1809
    }
×
1810
}
1811

1812
Result<logfile::read_file_result, std::string>
1813
logfile::read_file(read_format_t format)
112✔
1814
{
1815
    if (this->lf_stat.st_size > line_buffer::MAX_LINE_BUFFER_SIZE) {
112✔
1816
        return Err(std::string("file is too large to read"));
×
1817
    }
1818

1819
    auto retval = read_file_result{};
112✔
1820
    retval.rfr_content.reserve(this->lf_stat.st_size);
112✔
1821

1822
    if (format == read_format_t::with_framing) {
112✔
1823
        retval.rfr_content.append(this->lf_line_buffer.get_piper_header_size(),
95✔
1824
                                  '\x16');
1825
    }
1826
    for (auto iter = this->begin(); iter != this->end(); ++iter) {
6,355✔
1827
        const auto fr = this->get_file_range(iter);
6,243✔
1828
        retval.rfr_range.fr_metadata |= fr.fr_metadata;
6,243✔
1829
        retval.rfr_range.fr_size = fr.next_offset();
6,243✔
1830
        auto sbr = TRY(this->lf_line_buffer.read_range(fr));
6,243✔
1831

1832
        if (format == read_format_t::with_framing
6,243✔
1833
            && this->lf_line_buffer.is_piper())
6,243✔
1834
        {
1835
            retval.rfr_content.append(22, '\x16');
34✔
1836
        }
1837
        retval.rfr_content.append(sbr.get_data(), sbr.length());
6,243✔
1838
        if ((file_ssize_t) retval.rfr_content.size() < this->lf_stat.st_size) {
6,243✔
1839
            retval.rfr_content.push_back('\n');
6,237✔
1840
        }
1841
    }
6,243✔
1842

1843
    return Ok(std::move(retval));
112✔
1844
}
112✔
1845

1846
Result<shared_buffer_ref, std::string>
1847
logfile::read_range(const file_range& fr)
1,617✔
1848
{
1849
    return this->lf_line_buffer.read_range(fr);
1,617✔
1850
}
1851

1852
void
1853
logfile::read_full_message(const_iterator ll,
35,204✔
1854
                           shared_buffer_ref& msg_out,
1855
                           line_buffer::scan_direction dir,
1856
                           read_format_t format)
1857
{
1858
    require(ll->get_sub_offset() == 0);
35,204✔
1859

1860
#if 0
1861
    log_debug(
1862
        "%s: reading msg at %d", this->lf_filename_as_string.c_str(), ll->get_offset());
1863
#endif
1864

1865
    msg_out.disown();
35,204✔
1866
    auto mlr = this->message_byte_length(ll);
35,204✔
1867
    auto range_for_line
1868
        = file_range{ll->get_offset(), mlr.mlr_length, mlr.mlr_metadata};
35,204✔
1869
    try {
1870
        if (range_for_line.fr_size > line_buffer::MAX_LINE_BUFFER_SIZE) {
35,204✔
1871
            range_for_line.fr_size = line_buffer::MAX_LINE_BUFFER_SIZE;
×
1872
        }
1873
        if (format == read_format_t::plain && mlr.mlr_line_count > 1
35,204✔
1874
            && this->lf_line_buffer.is_piper())
70,408✔
1875
        {
1876
            this->lf_plain_msg_shared.invalidate_refs();
41✔
1877
            this->lf_plain_msg_buffer.expand_to(mlr.mlr_length);
41✔
1878
            this->lf_plain_msg_buffer.clear();
41✔
1879
            auto curr_ll = ll;
41✔
1880
            do {
1881
                const auto curr_range = this->get_file_range(curr_ll, false);
51✔
1882
                auto read_result
1883
                    = this->lf_line_buffer.read_range(curr_range, dir);
51✔
1884

1885
                if (curr_ll != ll) {
51✔
1886
                    this->lf_plain_msg_buffer.push_back('\n');
10✔
1887
                }
1888
                if (read_result.isErr()) {
51✔
1889
                    auto errmsg = read_result.unwrapErr();
×
1890
                    log_error("%s:%zu:unable to read range %lld:%lld -- %s",
×
1891
                              this->get_unique_path().c_str(),
1892
                              std::distance(this->cbegin(), ll),
1893
                              range_for_line.fr_offset,
1894
                              range_for_line.fr_size,
1895
                              errmsg.c_str());
1896
                    return;
×
1897
                }
1898

1899
                auto curr_buf = read_result.unwrap();
51✔
1900
                this->lf_plain_msg_buffer.append(curr_buf.to_string_view());
51✔
1901

1902
                ++curr_ll;
51✔
1903
            } while (curr_ll != this->end() && curr_ll->is_continued()
152✔
1904
                     && curr_ll->get_sub_offset() == 0);
101✔
1905
            msg_out.share(this->lf_plain_msg_shared,
82✔
1906
                          this->lf_plain_msg_buffer.data(),
41✔
1907
                          this->lf_plain_msg_buffer.size());
1908
        } else {
1909
            auto read_result
1910
                = this->lf_line_buffer.read_range(range_for_line, dir);
35,163✔
1911

1912
            if (read_result.isErr()) {
35,163✔
1913
                auto errmsg = read_result.unwrapErr();
×
1914
                log_error("%s:%zu:unable to read range %lld:%lld -- %s",
×
1915
                          this->get_unique_path().c_str(),
1916
                          std::distance(this->cbegin(), ll),
1917
                          range_for_line.fr_offset,
1918
                          range_for_line.fr_size,
1919
                          errmsg.c_str());
1920
                return;
×
1921
            }
1922
            msg_out = read_result.unwrap();
35,163✔
1923
            msg_out.get_metadata() = range_for_line.fr_metadata;
35,163✔
1924
        }
35,163✔
1925
        if (this->lf_format.get() != nullptr) {
35,204✔
1926
            this->lf_format->get_subline(
70,408✔
1927
                {this->lf_value_stats, this->lf_pattern_locks},
35,204✔
1928
                *ll,
35,204✔
1929
                msg_out,
1930
                {true});
1931
        }
1932
    } catch (const line_buffer::error& e) {
×
1933
        log_error("failed to read line");
×
1934
    }
×
1935
}
1936

1937
void
1938
logfile::set_logline_observer(logline_observer* llo)
1,750✔
1939
{
1940
    this->lf_logline_observer = llo;
1,750✔
1941
    if (llo != nullptr) {
1,750✔
1942
        this->reobserve_from(this->begin());
1,133✔
1943
    }
1944
}
1,750✔
1945

1946
void
1947
logfile::reobserve_from(iterator iter)
1,254✔
1948
{
1949
    for (; iter != this->end(); ++iter) {
2,366✔
1950
        off_t offset = std::distance(this->begin(), iter);
1,112✔
1951

1952
        if (iter->get_sub_offset() > 0) {
1,112✔
1953
            continue;
133✔
1954
        }
1955

1956
        if (this->lf_logfile_observer != nullptr) {
979✔
1957
            auto indexing_res = this->lf_logfile_observer->logfile_indexing(
979✔
1958
                this, offset, this->size());
979✔
1959
            if (indexing_res == lnav::progress_result_t::interrupt) {
979✔
1960
                break;
×
1961
            }
1962
        }
1963

1964
        this->read_line(iter).then([this, iter](auto sbr) {
979✔
1965
            auto iter_end = iter + 1;
979✔
1966

1967
            while (iter_end != this->end() && iter_end->get_sub_offset() != 0) {
1,112✔
1968
                ++iter_end;
133✔
1969
            }
1970
            this->lf_logline_observer->logline_new_lines(
1,958✔
1971
                *this, iter, iter_end, sbr);
979✔
1972
        });
979✔
1973
    }
1974
    if (this->lf_logfile_observer != nullptr) {
1,254✔
1975
        this->lf_logfile_observer->logfile_indexing(
1,254✔
1976
            this, this->size(), this->size());
1,254✔
1977
        this->lf_logline_observer->logline_eof(*this);
1,254✔
1978
    }
1979
}
1,254✔
1980

1981
std::filesystem::path
1982
logfile::get_path() const
1,946✔
1983
{
1984
    return this->lf_filename;
1,946✔
1985
}
1986

1987
const logline_value_stats*
1988
logfile::stats_for_value(intern_string_t name) const
106✔
1989
{
1990
    const logline_value_stats* retval = nullptr;
106✔
1991
    if (this->lf_format != nullptr) {
106✔
1992
        auto index_opt = this->lf_format->stats_index_for_value(name);
106✔
1993
        if (index_opt.has_value()) {
106✔
1994
            retval = &this->lf_value_stats[index_opt.value()];
106✔
1995
        }
1996
    }
1997

1998
    return retval;
106✔
1999
}
2000

2001
logfile::message_length_result
2002
logfile::message_byte_length(logfile::const_iterator ll, bool include_continues)
69,590✔
2003
{
2004
    auto next_line = ll;
69,590✔
2005
    file_range::metadata meta;
69,590✔
2006
    file_ssize_t retval;
2007
    size_t line_count = 0;
69,590✔
2008

2009
    if (!include_continues && this->lf_next_line_cache) {
69,590✔
2010
        if (ll->get_offset() == (*this->lf_next_line_cache).first) {
25,977✔
2011
            return {
2012
                (file_ssize_t) this->lf_next_line_cache->second,
3,346✔
2013
                1,
2014
                {ll->is_valid_utf(), ll->has_ansi()},
6,692✔
2015
            };
10,038✔
2016
        }
2017
    }
2018

2019
    do {
2020
        line_count += 1;
69,758✔
2021
        meta.m_has_ansi = meta.m_has_ansi || next_line->has_ansi();
69,758✔
2022
        meta.m_valid_utf = meta.m_valid_utf && next_line->is_valid_utf();
69,758✔
2023
        ++next_line;
69,758✔
2024
    } while ((next_line != this->end())
69,758✔
2025
             && ((ll->get_offset() == next_line->get_offset())
132,684✔
2026
                 || (include_continues && next_line->is_continued())));
62,926✔
2027

2028
    if (next_line == this->end()) {
66,244✔
2029
        if (this->lf_upper_bound_size) {
5,044✔
2030
            retval = this->lf_upper_bound_size.value() - ll->get_offset();
×
2031
        } else {
2032
            retval = this->lf_index_size - ll->get_offset();
5,044✔
2033
        }
2034
        if (retval > line_buffer::MAX_LINE_BUFFER_SIZE) {
5,044✔
2035
            retval = line_buffer::MAX_LINE_BUFFER_SIZE;
×
2036
        }
2037
        if (retval > 0 && !this->lf_partial_line) {
5,044✔
2038
            retval -= 1;
4,711✔
2039
        }
2040
    } else {
2041
        retval = next_line->get_offset() - ll->get_offset() - 1;
61,200✔
2042
        if (!include_continues) {
61,200✔
2043
            this->lf_next_line_cache
2044
                = std::make_optional(std::make_pair(ll->get_offset(), retval));
20,282✔
2045
        }
2046
    }
2047

2048
    require_ge(retval, 0);
66,244✔
2049

2050
    return {retval, line_count, meta};
66,244✔
2051
}
2052

2053
Result<shared_buffer_ref, std::string>
2054
logfile::read_raw_message(logfile::const_iterator ll)
73✔
2055
{
2056
    require(ll->get_sub_offset() == 0);
73✔
2057

2058
    return this->lf_line_buffer.read_range(this->get_file_range(ll));
73✔
2059
}
2060

2061
intern_string_t
2062
logfile::get_format_name() const
54,416✔
2063
{
2064
    if (this->lf_format) {
54,416✔
2065
        return this->lf_format->get_name();
54,416✔
2066
    }
2067

2068
    return {};
×
2069
}
2070

2071
std::optional<logfile::const_iterator>
2072
logfile::find_from_time(const timeval& tv) const
×
2073
{
2074
    auto retval
2075
        = std::lower_bound(this->lf_index.begin(), this->lf_index.end(), tv);
×
2076
    if (retval == this->lf_index.end()) {
×
2077
        return std::nullopt;
×
2078
    }
2079

2080
    return retval;
×
2081
}
2082

2083
bool
2084
logfile::mark_as_duplicate(const std::string& name)
1✔
2085
{
2086
    safe::WriteAccess<safe_notes> notes(this->lf_notes);
1✔
2087

2088
    if (notes->contains(note_type::duplicate)) {
1✔
2089
        return false;
×
2090
    }
2091

2092
    this->lf_indexing = false;
1✔
2093
    this->lf_options.loo_is_visible = false;
1✔
2094
    auto note_um
2095
        = lnav::console::user_message::warning("hiding duplicate file")
2✔
2096
              .with_reason(
2✔
2097
                  attr_line_t("this file appears to have the same content as ")
2✔
2098
                      .append(lnav::roles::file(name)))
2✔
2099
              .move();
1✔
2100
    notes->insert(note_type::duplicate, note_um);
1✔
2101
    return true;
1✔
2102
}
1✔
2103

2104
void
2105
logfile::adjust_content_time(int line, const timeval& tv, bool abs_offset)
21✔
2106
{
2107
    if (this->lf_time_offset == tv) {
21✔
2108
        return;
8✔
2109
    }
2110

2111
    auto old_time = this->lf_time_offset;
13✔
2112

2113
    this->lf_time_offset_line = line;
13✔
2114
    if (abs_offset) {
13✔
2115
        this->lf_time_offset = tv;
9✔
2116
    } else {
2117
        timeradd(&old_time, &tv, &this->lf_time_offset);
4✔
2118
    }
2119
    for (auto& iter : *this) {
85✔
2120
        timeval curr, diff, new_time;
2121

2122
        curr = iter.get_timeval();
72✔
2123
        timersub(&curr, &old_time, &diff);
72✔
2124
        timeradd(&diff, &this->lf_time_offset, &new_time);
72✔
2125
        iter.set_time(new_time);
72✔
2126
    }
2127
    this->lf_sort_needed = true;
13✔
2128
    this->lf_index_generation += 1;
13✔
2129
}
2130

2131
std::filesystem::path
2132
logfile::get_path_for_key() const
113✔
2133
{
2134
    if (this->lf_options.loo_temp_dev == 0 && this->lf_options.loo_temp_ino == 0
113✔
2135
        && this->lf_line_buffer.is_piper())
226✔
2136
    {
2137
        return this->lf_actual_path.value_or(this->lf_filename);
25✔
2138
    }
2139
    return this->lf_filename;
88✔
2140
}
2141

2142
void
2143
logfile::set_filename(const std::string& filename)
139✔
2144
{
2145
    if (this->lf_filename != filename) {
139✔
2146
        this->lf_filename = filename;
106✔
2147
        this->lf_filename_as_string = this->lf_filename.string();
106✔
2148
        std::filesystem::path p(filename);
106✔
2149
        this->lf_basename = p.filename();
106✔
2150
    }
106✔
2151
}
139✔
2152

2153
time_t
NEW
2154
logfile::get_origin_mtime() const
×
2155
{
NEW
2156
    if (!this->is_valid_filename()) {
×
2157
        struct stat st;
NEW
2158
        if (lnav::filesystem::statp(this->lf_filename, &st) == 0) {
×
NEW
2159
            return st.st_mtime;
×
2160
        }
2161
    }
2162

NEW
2163
    return this->lf_stat.st_mtime;
×
2164
}
2165

2166
struct timeval
2167
logfile::original_line_time(iterator ll)
172✔
2168
{
2169
    if (this->is_time_adjusted()) {
172✔
2170
        auto line_time = ll->get_timeval();
11✔
2171
        timeval retval;
2172

2173
        timersub(&line_time, &this->lf_time_offset, &retval);
11✔
2174
        return retval;
11✔
2175
    }
2176

2177
    return ll->get_timeval();
161✔
2178
}
2179

2180
std::optional<logfile::const_iterator>
2181
logfile::line_for_offset(file_off_t off) const
5✔
2182
{
2183
    struct cmper {
2184
        bool operator()(const file_off_t& lhs, const logline& rhs) const
2185
        {
2186
            return lhs < rhs.get_offset();
2187
        }
2188

2189
        bool operator()(const logline& lhs, const file_off_t& rhs) const
20✔
2190
        {
2191
            return lhs.get_offset() < rhs;
20✔
2192
        }
2193
    };
2194

2195
    if (this->lf_index.empty()) {
5✔
2196
        return std::nullopt;
×
2197
    }
2198

2199
    auto iter = std::lower_bound(
5✔
2200
        this->lf_index.begin(), this->lf_index.end(), off, cmper{});
2201
    if (iter == this->lf_index.end()) {
5✔
2202
        if (this->lf_index.back().get_offset() <= off
×
2203
            && off < this->lf_index_size)
×
2204
        {
2205
            return std::make_optional(iter);
×
2206
        }
2207
        return std::nullopt;
×
2208
    }
2209

2210
    if (off < iter->get_offset() && iter != this->lf_index.begin()) {
5✔
2211
        --iter;
5✔
2212
    }
2213

2214
    return std::make_optional(iter);
5✔
2215
}
2216

2217
void
2218
logfile::dump_stats()
897✔
2219
{
2220
    const auto buf_stats = this->lf_line_buffer.consume_stats();
897✔
2221

2222
    if (buf_stats.empty()) {
897✔
2223
        return;
617✔
2224
    }
2225
    log_info("line buffer stats for file: %s",
280✔
2226
             this->lf_filename_as_string.c_str());
2227
    log_info("  file_size=%lld", this->lf_line_buffer.get_file_size());
280✔
2228
    log_info("  buffer_size=%ld", this->lf_line_buffer.get_buffer_size());
280✔
2229
    log_info("  read_hist=[%4u %4u %4u %4u %4u %4u %4u %4u %4u %4u]",
280✔
2230
             buf_stats.s_hist[0],
2231
             buf_stats.s_hist[1],
2232
             buf_stats.s_hist[2],
2233
             buf_stats.s_hist[3],
2234
             buf_stats.s_hist[4],
2235
             buf_stats.s_hist[5],
2236
             buf_stats.s_hist[6],
2237
             buf_stats.s_hist[7],
2238
             buf_stats.s_hist[8],
2239
             buf_stats.s_hist[9]);
2240
    log_info("  decompressions=%u", buf_stats.s_decompressions);
280✔
2241
    log_info("  preads=%u", buf_stats.s_preads);
280✔
2242
    log_info("  requested_preloads=%u", buf_stats.s_requested_preloads);
280✔
2243
    log_info("  used_preloads=%u", buf_stats.s_used_preloads);
280✔
2244
}
2245

2246
void
2247
logfile::set_logline_opid(uint32_t line_number, string_fragment opid)
17✔
2248
{
2249
    if (line_number >= this->lf_index.size()) {
17✔
2250
        log_error("invalid line number: %u", line_number);
×
2251
        return;
×
2252
    }
2253

2254
    auto bm_iter = this->lf_bookmark_metadata.find(line_number);
17✔
2255
    if (bm_iter != this->lf_bookmark_metadata.end()) {
17✔
2256
        if (bm_iter->second.bm_opid == opid) {
×
2257
            return;
×
2258
        }
2259
    }
2260

2261
    auto write_opids = this->lf_opids.writeAccess();
17✔
2262

2263
    if (bm_iter != this->lf_bookmark_metadata.end()
17✔
2264
        && !bm_iter->second.bm_opid.empty())
17✔
2265
    {
2266
        auto old_opid_iter = write_opids->los_opid_ranges.find(opid);
×
2267
        if (old_opid_iter != write_opids->los_opid_ranges.end()) {
×
2268
            this->lf_invalidated_opids.insert(old_opid_iter->first);
×
2269
        }
2270
    }
2271

2272
    auto& ll = this->lf_index[line_number];
17✔
2273
    auto log_us = ll.get_time<std::chrono::microseconds>();
17✔
2274
    auto opid_iter = write_opids->insert_op(
17✔
2275
        this->lf_allocator, opid, log_us, timestamp_point_of_reference_t::send);
17✔
2276
    auto& otr = opid_iter->second;
17✔
2277

2278
    otr.otr_level_stats.update_msg_count(ll.get_msg_level());
17✔
2279
    ll.set_opid(opid.hash());
17✔
2280
    this->lf_bookmark_metadata[line_number].bm_opid = opid.to_string();
17✔
2281
}
17✔
2282

2283
void
2284
logfile::set_opid_description(string_fragment opid, string_fragment desc)
4✔
2285
{
2286
    auto opid_guard = this->lf_opids.writeAccess();
4✔
2287

2288
    auto opid_iter = opid_guard->los_opid_ranges.find(opid);
4✔
2289
    if (opid_iter == opid_guard->los_opid_ranges.end()) {
4✔
2290
        return;
×
2291
    }
2292
    opid_iter->second.otr_description.lod_index = std::nullopt;
4✔
2293
    opid_iter->second.otr_description.lod_elements.clear();
4✔
2294
    opid_iter->second.otr_description.lod_elements.insert(0, desc.to_string());
4✔
2295
}
4✔
2296

2297
void
2298
logfile::clear_logline_opid(uint32_t line_number)
×
2299
{
2300
    if (line_number >= this->lf_index.size()) {
×
2301
        return;
×
2302
    }
2303

2304
    auto iter = this->lf_bookmark_metadata.find(line_number);
×
2305
    if (iter == this->lf_bookmark_metadata.end()) {
×
2306
        return;
×
2307
    }
2308

2309
    if (iter->second.bm_opid.empty()) {
×
2310
        return;
×
2311
    }
2312

2313
    auto& ll = this->lf_index[line_number];
×
2314
    ll.set_opid(0);
×
2315
    auto opid = std::move(iter->second.bm_opid);
×
2316
    auto opid_sf = string_fragment::from_str(opid);
×
2317

2318
    if (iter->second.empty(bookmark_metadata::categories::any)) {
×
2319
        this->lf_bookmark_metadata.erase(iter);
×
2320

2321
        auto writeOpids = this->lf_opids.writeAccess();
×
2322

2323
        auto otr_iter = writeOpids->los_opid_ranges.find(opid_sf);
×
2324
        if (otr_iter == writeOpids->los_opid_ranges.end()) {
×
2325
            return;
×
2326
        }
2327

2328
        if (otr_iter->second.otr_range.tr_begin
×
2329
                != ll.get_time<std::chrono::microseconds>()
×
2330
            && otr_iter->second.otr_range.tr_end
×
2331
                != ll.get_time<std::chrono::microseconds>())
×
2332
        {
2333
            otr_iter->second.otr_level_stats.update_msg_count(
×
2334
                ll.get_msg_level(), -1);
2335
            return;
×
2336
        }
2337

2338
        otr_iter->second.clear();
×
2339
        this->lf_invalidated_opids.insert(opid_sf);
×
2340
    }
2341
}
2342

2343
size_t
2344
logfile::estimated_remaining_lines() const
6,477✔
2345
{
2346
    if (this->lf_index.empty() || this->is_compressed()) {
6,477✔
2347
        return 10;
644✔
2348
    }
2349

2350
    const auto bytes_per_line = this->lf_index_size / this->lf_index.size();
5,833✔
2351
    if (this->lf_index_size > this->lf_stat.st_size) {
5,833✔
2352
        return 0;
15✔
2353
    }
2354
    const auto remaining_bytes = this->lf_stat.st_size - this->lf_index_size;
5,818✔
2355

2356
    return remaining_bytes / bytes_per_line;
5,818✔
2357
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc