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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

83.15
/src/hist_source.hh
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 hist_source.hh
30
 */
31

32
#ifndef hist_source_hh
33
#define hist_source_hh
34

35
#include <cmath>
36
#include <limits>
37
#include <string>
38
#include <unordered_map>
39
#include <vector>
40

41
#include "base/enum_util.hh"
42
#include "base/lnav_log.hh"
43
#include "mapbox/variant.hpp"
44
#include "strong_int.hh"
45
#include "textview_curses.hh"
46

47
/** Type for indexes into a group of buckets. */
48
STRONG_INT_TYPE(int, bucket_group);
49

50
/** Type used to differentiate values added to the same row in the histogram */
51
STRONG_INT_TYPE(int, bucket_type);
52

53
struct stacked_bar_chart_base {
54
    virtual ~stacked_bar_chart_base() = default;
5,191✔
55

56
    struct show_none {};
57
    struct show_all {};
58
    struct show_one {
59
        size_t so_index;
60

61
        explicit show_one(int so_index) : so_index(so_index) {}
62
    };
63

64
    using show_state = mapbox::util::variant<show_none, show_all, show_one>;
65

66
    enum class direction {
67
        forward,
68
        backward,
69
    };
70
};
71

72
struct bucket_stats_t {
73
    bucket_stats_t()
20,120✔
74
        : bs_min_value(std::numeric_limits<double>::max()),
20,120✔
75
          bs_max_value(std::numeric_limits<double>::min())
20,120✔
76
    {
77
    }
20,120✔
78

79
    void merge(const bucket_stats_t& rhs)
1,688✔
80
    {
81
        this->bs_min_value = std::min(this->bs_min_value, rhs.bs_min_value);
1,688✔
82
        this->bs_max_value = std::max(this->bs_max_value, rhs.bs_max_value);
1,688✔
83
    }
1,688✔
84

85
    double width() const
624✔
86
    {
87
        return std::fabs(this->bs_max_value - this->bs_min_value);
624✔
88
    }
89

90
    void update(double value)
3,423✔
91
    {
92
        this->bs_max_value = std::max(this->bs_max_value, value);
3,423✔
93
        this->bs_min_value = std::min(this->bs_min_value, value);
3,423✔
94
    }
3,423✔
95

96
    double bs_min_value;
97
    double bs_max_value;
98
};
99

100
template<typename T>
101
class stacked_bar_chart : public stacked_bar_chart_base {
102
public:
UNCOV
103
    stacked_bar_chart& with_stacking_enabled(bool enabled)
×
104
    {
105
        this->sbc_do_stacking = enabled;
×
UNCOV
106
        return *this;
×
107
    }
108

109
    stacked_bar_chart& with_attrs_for_ident(const T& ident, text_attrs attrs)
21,336✔
110
    {
111
        auto& ci = this->find_ident(ident);
21,336✔
112
        ci.ci_attrs = attrs;
21,336✔
113
        return *this;
21,336✔
114
    }
115

UNCOV
116
    stacked_bar_chart& with_margins(unsigned long left, unsigned long right)
×
117
    {
UNCOV
118
        this->sbc_left = left;
×
UNCOV
119
        this->sbc_right = right;
×
UNCOV
120
        return *this;
×
121
    }
122

123
    stacked_bar_chart& with_show_state(show_state ss)
7,985✔
124
    {
125
        this->sbc_show_state = ss;
7,985✔
126
        return *this;
7,985✔
127
    }
128

129
    bool attrs_in_use(const text_attrs& attrs) const
689✔
130
    {
131
        for (const auto& ident : this->sbc_idents) {
689✔
UNCOV
132
            if (ident.ci_attrs == attrs) {
×
UNCOV
133
                return true;
×
134
            }
135
        }
136
        return false;
689✔
137
    }
138

139
    show_state show_next_ident(direction dir)
140
    {
141
        bool single_ident = this->sbc_idents.size() == 1;
142

143
        if (this->sbc_idents.empty()) {
144
            return this->sbc_show_state;
145
        }
146

147
        this->sbc_show_state = this->sbc_show_state.match(
148
            [&](show_none) -> show_state {
149
                switch (dir) {
150
                    case direction::forward:
151
                        if (single_ident) {
152
                            return show_all();
153
                        }
154
                        return show_one(0);
155
                    case direction::backward:
156
                        return show_all();
157
                }
158

159
                return show_all();
160
            },
161
            [&](show_one& one) -> show_state {
162
                switch (dir) {
163
                    case direction::forward:
164
                        if (one.so_index + 1 == this->sbc_idents.size()) {
165
                            return show_all();
166
                        }
167
                        return show_one(one.so_index + 1);
168
                    case direction::backward:
169
                        if (one.so_index == 0) {
170
                            return show_none();
171
                        }
172
                        return show_one(one.so_index - 1);
173
                }
174

175
                return show_all();
176
            },
177
            [&](show_all) -> show_state {
178
                switch (dir) {
179
                    case direction::forward:
180
                        return show_none();
181
                    case direction::backward:
182
                        if (single_ident) {
183
                            return show_none();
184
                        }
185
                        return show_one(this->sbc_idents.size() - 1);
186
                }
187

188
                return show_all();
189
            });
190

191
        return this->sbc_show_state;
192
    }
193

194
    void get_ident_to_show(T& ident_out) const
195
    {
196
        this->sbc_show_state.match(
197
            [](const show_none) {},
198
            [](const show_all) {},
199
            [&](const show_one& one) {
200
                ident_out = this->sbc_idents[one.so_index].ci_ident;
201
            });
202
    }
203

204
    void chart_attrs_for_value(const listview_curses& lc,
205
                               int& left,
206
                               unsigned long width,
207
                               const T& ident,
208
                               double value,
209
                               string_attrs_t& value_out,
210
                               std::optional<text_attrs> user_attrs
211
                               = std::nullopt) const;
212

213
    void clear()
4,498✔
214
    {
215
        this->sbc_idents.clear();
4,498✔
216
        this->sbc_ident_lookup.clear();
4,498✔
217
        this->sbc_show_state = show_none();
4,498✔
218
        this->sbc_row_sum = 0;
4,498✔
219
        this->sbc_row_items = 0;
4,498✔
220
        this->sbc_max_row_value = 0;
4,498✔
221
        this->sbc_max_row_items = 0;
4,498✔
222
    }
4,498✔
223

224
    void add_value(const T& ident, double amount = 1.0)
3,423✔
225
    {
226
        struct chart_ident& ci = this->find_ident(ident);
3,423✔
227
        ci.ci_stats.update(amount);
3,423✔
228
        this->sbc_row_sum += amount;
3,423✔
229
        if (ci.ci_last_seen_row != this->sbc_row_counter) {
3,423✔
230
            ci.ci_last_seen_row = this->sbc_row_counter;
3,423✔
231
            this->sbc_row_items += 1;
3,423✔
232
        }
233
    }
3,423✔
234

235
    void next_row()
9,027✔
236
    {
237
        if (this->sbc_row_sum > this->sbc_max_row_value) {
9,027✔
238
            this->sbc_max_row_value = this->sbc_row_sum;
1,120✔
239
        }
240
        if (this->sbc_row_items > this->sbc_max_row_items) {
9,027✔
241
            this->sbc_max_row_items = this->sbc_row_items;
1,018✔
242
        }
243
        this->sbc_row_sum = 0;
9,027✔
244
        this->sbc_row_items = 0;
9,027✔
245
        this->sbc_row_counter += 1;
9,027✔
246
    }
9,027✔
247

UNCOV
248
    const bucket_stats_t& get_stats_for(const T& ident)
×
249
    {
UNCOV
250
        const chart_ident& ci = this->find_ident(ident);
×
251

UNCOV
252
        return ci.ci_stats;
×
253
    }
254

255
protected:
256
    struct chart_ident {
257
        explicit chart_ident(const T& ident) : ci_ident(ident) {}
19,006✔
258

259
        T ci_ident;
260
        text_attrs ci_attrs;
261
        bucket_stats_t ci_stats;
262
        ssize_t ci_last_seen_row{-1};
263
    };
264

265
    chart_ident& find_ident(const T& ident)
24,759✔
266
    {
267
        auto iter = this->sbc_ident_lookup.find(ident);
24,759✔
268
        if (iter == this->sbc_ident_lookup.end()) {
24,759✔
269
            this->sbc_ident_lookup[ident] = this->sbc_idents.size();
19,006✔
270
            this->sbc_idents.emplace_back(ident);
19,006✔
271
            return this->sbc_idents.back();
19,006✔
272
        }
273
        return this->sbc_idents[iter->second];
5,753✔
274
    }
275

276
    bool sbc_do_stacking{true};
277
    unsigned long sbc_left{0}, sbc_right{0};
278
    std::vector<chart_ident> sbc_idents;
279
    std::unordered_map<T, unsigned int> sbc_ident_lookup;
280
    show_state sbc_show_state{show_none()};
281

282
    ssize_t sbc_row_counter{0};
283
    double sbc_row_sum{0};
284
    size_t sbc_row_items{0};
285
    double sbc_max_row_value{0};
286
    size_t sbc_max_row_items{0};
287
};
288

289
class hist_source2
290
    : public text_sub_source
291
    , public text_time_translator {
292
public:
293
    enum class hist_type_t : uint8_t {
294
        HT_NORMAL,
295
        HT_WARNING,
296
        HT_ERROR,
297
        HT_MARK,
298

299
        HT__MAX
300
    };
301

302
    hist_source2() { this->clear(); }
1,386✔
303

304
    ~hist_source2() override = default;
693✔
305

UNCOV
306
    bool empty() const override { return false; }
×
307

308
    void init();
309

310
    void set_time_slice(std::chrono::microseconds slice)
589✔
311
    {
312
        this->hs_time_slice = slice;
589✔
313
    }
589✔
314

315
    std::chrono::microseconds get_time_slice() const
316
    {
317
        return this->hs_time_slice;
318
    }
319

320
    size_t text_line_count() override { return this->hs_line_count; }
12,871✔
321

322
    size_t text_line_width(textview_curses& curses) override
4,827✔
323
    {
324
        return 48 + 8 * 4;
4,827✔
325
    }
326

327
    void clear();
328

329
    void add_value(std::chrono::microseconds ts,
330
                   hist_type_t htype,
331
                   double value = 1.0);
332

333
    void end_of_row();
334

335
    line_info text_value_for_line(textview_curses& tc,
336
                                  int row,
337
                                  std::string& value_out,
338
                                  line_flags_t flags) override;
339

340
    void text_attrs_for_line(textview_curses& tc,
341
                             int row,
342
                             string_attrs_t& value_out) override;
343

UNCOV
344
    size_t text_size_for_line(textview_curses& tc,
×
345
                              int row,
346
                              line_flags_t flags) override
347
    {
UNCOV
348
        return 0;
×
349
    }
350

351
    std::optional<row_info> time_for_row(vis_line_t row) override;
352

353
    std::optional<vis_line_t> row_for_time(timeval tv_bucket) override;
354

355
private:
356
    struct hist_value {
357
        double hv_value;
358
    };
359

360
    struct bucket_t {
361
        std::chrono::microseconds b_time;
362
        hist_value b_values[lnav::enums::to_underlying(hist_type_t::HT__MAX)];
363

364
        hist_value& value_for(hist_type_t ht)
10,423✔
365
        {
366
            return this->b_values[lnav::enums::to_underlying(ht)];
10,423✔
367
        }
368

369
        const hist_value& value_for(hist_type_t ht) const
370
        {
371
            return this->b_values[lnav::enums::to_underlying(ht)];
372
        }
373
    };
374

375
    static constexpr int64_t BLOCK_SIZE = 100;
376

377
    struct bucket_block {
378
        bucket_block()
564✔
379
        {
56,964✔
380
            memset(this->bb_buckets, 0, sizeof(this->bb_buckets));
564✔
381
        }
564✔
382

383
        unsigned int bb_used{0};
384
        bucket_t bb_buckets[BLOCK_SIZE];
385
    };
386

387
    bucket_t& find_bucket(int64_t index);
388

389
    std::chrono::microseconds hs_time_slice{10 * 60};
693✔
390
    int64_t hs_line_count;
391
    int64_t hs_current_row;
392
    std::chrono::microseconds hs_last_ts;
393
    std::vector<bucket_block> hs_blocks;
394
    stacked_bar_chart<hist_type_t> hs_chart;
395
    bool hs_needs_flush{false};
396
};
397

398
#endif
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