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

tstack / lnav / 23836075406-2909

01 Apr 2026 06:51AM UTC coverage: 69.084% (+0.007%) from 69.077%
23836075406-2909

push

github

tstack
[tags] only save user-provided tags in the session, not format-provided

60 of 76 new or added lines in 14 files covered. (78.95%)

2 existing lines in 2 files now uncovered.

53287 of 77134 relevant lines covered (69.08%)

535481.86 hits per line

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

91.36
/src/all_ids_vtabs.cc
1
/**
2
 * Copyright (c) 2025, 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

30
#include <string>
31
#include <vector>
32

33
#include "all_ids_vtabs.hh"
34

35
#include "base/distributed_slice.hh"
36
#include "base/injector.bind.hh"
37
#include "base/injector.hh"
38
#include "base/itertools.hh"
39
#include "base/time_util.hh"
40
#include "file_collection.hh"
41
#include "log_format.hh"
42
#include "logfile.hh"
43
#include "robin_hood/robin_hood.h"
44
#include "vtab_module.hh"
45

46
namespace {
47
struct all_opids {
48
    static constexpr const char* NAME = "all_opids";
49
    static constexpr const char* CREATE_STMT = R"(
50
CREATE TABLE lnav_db.all_opids (
51
    opid TEXT PRIMARY KEY,  -- The operation ID
52
    earliest DATETIME,      -- The earliest time this ID was seen
53
    latest DATETIME,        -- The latest time this ID was seen
54
    duration INTEGER,       -- The amount of time between earliest and latest in microseconds
55
    errors INTEGER,         -- The number of error messages associated with this ID
56
    warnings INTEGER,       -- The number of warning messages associated with this ID
57
    total INTEGER,          -- The total number of messages associated with this ID
58
    definition TEXT,        -- The name of the opid description from the log format, if available
59
    description TEXT        -- A description of the operation
60
);
61
)";
62

63
    struct cursor {
64
        struct opid_time_pair {
65
            std::string otp_opid;
66
            opid_time_range otp_range;
67
            intern_string_t otp_name;
68
            std::string otp_description;
69

70
            bool operator<(const opid_time_pair& rhs) const
14✔
71
            {
72
                return this->otp_range < rhs.otp_range;
14✔
73
            }
74
        };
75

76
        using pair_map = robin_hood::unordered_map<std::string, opid_time_pair>;
77

78
        sqlite3_vtab_cursor base{};
79
        std::vector<opid_time_pair> c_opids;
80
        std::vector<opid_time_pair>::const_iterator c_iter;
81

82
        explicit cursor(sqlite3_vtab* vt)
6✔
83
        {
6✔
84
            const auto& active_files = injector::get<file_collection&>();
6✔
85
            pair_map gather_map;
6✔
86

87
            for (const auto& lf : active_files.fc_files) {
12✔
88
                auto lf_opids = lf->get_opids().readAccess();
6✔
89
                for (const auto& [key, om] : lf_opids->los_opid_ranges) {
21✔
90
                    auto key_str = key.to_string();
15✔
91
                    auto gather_iter = gather_map.find(key_str);
15✔
92
                    auto earlier_desc = false;
15✔
93
                    if (gather_iter == gather_map.end()) {
15✔
94
                        auto emplace_res = gather_map.emplace(
15✔
95
                            key_str, opid_time_pair{key_str, om});
15✔
96
                        gather_iter = emplace_res.first;
15✔
97
                    } else {
NEW
98
                        if (om.otr_range
×
NEW
99
                            < gather_iter->second.otp_range.otr_range)
×
100
                        {
NEW
101
                            earlier_desc = true;
×
102
                        }
UNCOV
103
                        gather_iter->second.otp_range |= om;
×
104
                    }
105
                    if (earlier_desc
15✔
106
                        || gather_iter->second.otp_description.empty())
15✔
107
                    {
108
                        auto format = lf->get_format();
15✔
109
                        if (om.otr_description.lod_index.has_value()) {
15✔
110
                            auto desc_iter
111
                                = format->lf_opid_description_def_vec->at(
5✔
112
                                    om.otr_description.lod_index.value());
5✔
113
                            gather_iter->second.otp_name = desc_iter->od_name;
5✔
114
                            gather_iter->second.otp_description
5✔
115
                                = desc_iter->to_string(
5✔
116
                                    om.otr_description.lod_elements);
10✔
117
                        } else if (!om.otr_description.lod_elements.empty()) {
10✔
118
                            gather_iter->second.otp_description
4✔
119
                                = om.otr_description.lod_elements.values()
4✔
120
                                      .front();
8✔
121
                        }
122
                    }
15✔
123
                }
15✔
124
            }
6✔
125
            for (const auto& [key, value] : gather_map) {
21✔
126
                this->c_opids.emplace_back(std::move(value));
15✔
127
            }
128
            std::stable_sort(this->c_opids.begin(), this->c_opids.end());
6✔
129

130
            this->base.pVtab = vt;
6✔
131
        }
6✔
132

133
        int reset()
6✔
134
        {
135
            this->c_iter = this->c_opids.begin();
6✔
136

137
            return SQLITE_OK;
6✔
138
        }
139

140
        int next()
15✔
141
        {
142
            if (this->c_iter != this->c_opids.end()) {
15✔
143
                ++this->c_iter;
15✔
144
            }
145

146
            return SQLITE_OK;
15✔
147
        }
148

149
        int eof() const { return this->c_iter == this->c_opids.end(); }
21✔
150

151
        int get_rowid(sqlite_int64& rowid_out) const
8✔
152
        {
153
            rowid_out = std::distance(this->c_opids.begin(), this->c_iter);
8✔
154

155
            return SQLITE_OK;
8✔
156
        }
157
    };
158

159
    int get_column(cursor& vc, sqlite3_context* ctx, int col)
119✔
160
    {
161
        switch (col) {
119✔
162
            case 0: {
19✔
163
                to_sqlite(ctx, vc.c_iter->otp_opid);
19✔
164
                break;
19✔
165
            }
166
            case 1: {
13✔
167
                to_sqlite(ctx, vc.c_iter->otp_range.otr_range.tr_begin);
13✔
168
                break;
13✔
169
            }
170
            case 2: {
13✔
171
                to_sqlite(ctx, vc.c_iter->otp_range.otr_range.tr_end);
13✔
172
                break;
13✔
173
            }
174
            case 3: {
13✔
175
                to_sqlite(ctx,
13✔
176
                          vc.c_iter->otp_range.otr_range.duration().count());
13✔
177
                break;
13✔
178
            }
179
            case 4: {
13✔
180
                to_sqlite(ctx,
13✔
181
                          vc.c_iter->otp_range.otr_level_stats.lls_error_count);
13✔
182
                break;
13✔
183
            }
184
            case 5: {
13✔
185
                to_sqlite(
13✔
186
                    ctx,
187
                    vc.c_iter->otp_range.otr_level_stats.lls_warning_count);
13✔
188
                break;
13✔
189
            }
190
            case 6: {
13✔
191
                to_sqlite(ctx,
13✔
192
                          vc.c_iter->otp_range.otr_level_stats.lls_total_count);
13✔
193
                break;
13✔
194
            }
195
            case 7: {
13✔
196
                to_sqlite(ctx, vc.c_iter->otp_name);
13✔
197
                break;
13✔
198
            }
199
            case 8: {
9✔
200
                if (vc.c_iter->otp_description.empty()) {
9✔
201
                    sqlite3_result_null(ctx);
2✔
202
                } else {
203
                    to_sqlite(ctx, vc.c_iter->otp_description);
7✔
204
                }
205
                break;
9✔
206
            }
207
        }
208

209
        return SQLITE_OK;
119✔
210
    }
211

212
    int delete_row(sqlite3_vtab* tab, sqlite3_int64 rowid)
×
213
    {
214
        tab->zErrMsg = sqlite3_mprintf(
×
215
            "Rows cannot be deleted from the all_opids table");
216
        return SQLITE_ERROR;
×
217
    }
218

219
    int insert_row(sqlite3_vtab* tab, sqlite3_int64& rowid_out)
×
220
    {
221
        tab->zErrMsg = sqlite3_mprintf(
×
222
            "Rows cannot be inserted into the all_opids table");
223
        return SQLITE_ERROR;
×
224
    }
225

226
    int update_row(sqlite3_vtab* tab,
4✔
227
                   sqlite3_int64& index,
228
                   string_fragment opid,
229
                   string_fragment earliest,
230
                   string_fragment latest,
231
                   int64_t duration,
232
                   int64_t errors,
233
                   int64_t warnings,
234
                   int64_t total,
235
                   std::optional<string_fragment> definition,
236
                   std::optional<string_fragment> description)
237
    {
238
        if (description) {
4✔
239
            const auto& active_files = injector::get<file_collection&>();
4✔
240

241
            for (const auto& lf : active_files.fc_files) {
8✔
242
                lf->set_opid_description(opid, description.value());
4✔
243
            }
244
        }
245
        return SQLITE_OK;
4✔
246
    }
247
};
248

249
struct all_thread_ids {
250
    static constexpr const char* NAME = "all_thread_ids";
251
    static constexpr const char* CREATE_STMT = R"(
252
CREATE TABLE lnav_db.all_thread_ids (
253
    thread_id TEXT PRIMARY KEY,  -- The thread ID
254
    earliest DATETIME,           -- The earliest time this ID was seen
255
    latest DATETIME,             -- The latest time this ID was seen
256
    duration INTEGER,            -- The amount of time between earliest and latest in microseconds
257
    errors INTEGER,              -- The number of error messages associated with this ID
258
    warnings INTEGER,            -- The number of warning messages associated with this ID
259
    total INTEGER                -- The total number of messages associated with this ID
260
);
261
)";
262

263
    struct cursor {
264
        struct thread_id_time_pair {
265
            std::string titp_thread_id;
266
            thread_id_time_range titp_range;
267

268
            bool operator<(const thread_id_time_pair& rhs) const
10✔
269
            {
270
                return this->titp_range < rhs.titp_range;
10✔
271
            }
272
        };
273

274
        using pair_map
275
            = robin_hood::unordered_map<std::string, thread_id_time_pair>;
276

277
        sqlite3_vtab_cursor base{};
278
        std::vector<thread_id_time_pair> c_thread_ids;
279
        std::vector<thread_id_time_pair>::const_iterator c_iter;
280

281
        explicit cursor(sqlite3_vtab* vt)
2✔
282
        {
2✔
283
            const auto& active_files = injector::get<file_collection&>();
2✔
284
            pair_map gather_map;
2✔
285

286
            for (const auto& lf : active_files.fc_files) {
4✔
287
                auto lf_thread_ids = lf->get_thread_ids().readAccess();
2✔
288
                for (const auto& [key, om] : lf_thread_ids->ltis_tid_ranges) {
10✔
289
                    auto key_str = key.to_string();
8✔
290
                    auto gather_iter = gather_map.find(key_str);
8✔
291
                    if (gather_iter == gather_map.end()) {
8✔
292
                        gather_map[key_str].titp_thread_id = key_str;
8✔
293
                        gather_map[key_str].titp_range = om;
8✔
294
                    } else {
295
                        gather_iter->second.titp_range |= om;
×
296
                    }
297
                }
8✔
298
            }
2✔
299
            for (const auto& [key, value] : gather_map) {
10✔
300
                this->c_thread_ids.emplace_back(std::move(value));
8✔
301
            }
302
            std::stable_sort(this->c_thread_ids.begin(),
2✔
303
                             this->c_thread_ids.end());
304

305
            this->base.pVtab = vt;
2✔
306
        }
2✔
307

308
        int reset()
2✔
309
        {
310
            this->c_iter = this->c_thread_ids.begin();
2✔
311

312
            return SQLITE_OK;
2✔
313
        }
314

315
        int next()
8✔
316
        {
317
            if (this->c_iter != this->c_thread_ids.end()) {
8✔
318
                ++this->c_iter;
8✔
319
            }
320

321
            return SQLITE_OK;
8✔
322
        }
323

324
        int eof() const { return this->c_iter == this->c_thread_ids.end(); }
10✔
325

326
        int get_rowid(sqlite_int64& rowid_out) const
×
327
        {
328
            rowid_out = std::distance(this->c_thread_ids.begin(), this->c_iter);
×
329

330
            return SQLITE_OK;
×
331
        }
332
    };
333

334
    int get_column(cursor& vc, sqlite3_context* ctx, int col)
56✔
335
    {
336
        switch (col) {
56✔
337
            case 0: {
8✔
338
                to_sqlite(ctx, vc.c_iter->titp_thread_id);
8✔
339
                break;
8✔
340
            }
341
            case 1: {
8✔
342
                to_sqlite(ctx, vc.c_iter->titp_range.titr_range.tr_begin);
8✔
343
                break;
8✔
344
            }
345
            case 2: {
8✔
346
                to_sqlite(ctx, vc.c_iter->titp_range.titr_range.tr_end);
8✔
347
                break;
8✔
348
            }
349
            case 3: {
8✔
350
                to_sqlite(ctx,
8✔
351
                          vc.c_iter->titp_range.titr_range.duration().count());
8✔
352
                break;
8✔
353
            }
354
            case 4: {
8✔
355
                to_sqlite(
8✔
356
                    ctx,
357
                    vc.c_iter->titp_range.titr_level_stats.lls_error_count);
8✔
358
                break;
8✔
359
            }
360
            case 5: {
8✔
361
                to_sqlite(
8✔
362
                    ctx,
363
                    vc.c_iter->titp_range.titr_level_stats.lls_warning_count);
8✔
364
                break;
8✔
365
            }
366
            case 6: {
8✔
367
                to_sqlite(
8✔
368
                    ctx,
369
                    vc.c_iter->titp_range.titr_level_stats.lls_total_count);
8✔
370
                break;
8✔
371
            }
372
        }
373

374
        return SQLITE_OK;
56✔
375
    }
376
};
377

378
auto all_vtabs_binder = injector::bind_multiple<vtab_module_base>()
379
                            .add<vtab_module<all_opids>>()
380
                            .add<vtab_module<tvt_no_update<all_thread_ids>>>();
381

382
}  // namespace
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