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

tstack / lnav / 19470117905-2676

18 Nov 2025 02:44PM UTC coverage: 68.876% (+0.001%) from 68.875%
19470117905-2676

push

github

tstack
[notcurses][input] handle non-matches in escape trie properly

https://github.com/dankamongmen/notcurses/commit/dadd5d39d

51046 of 74113 relevant lines covered (68.88%)

432212.39 hits per line

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

82.76
/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,551✔
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()
21,740✔
74
        : bs_min_value(std::numeric_limits<double>::max()),
21,740✔
75
          bs_max_value(std::numeric_limits<double>::min())
21,740✔
76
    {
77
    }
21,740✔
78

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

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

90
    void update(double value)
3,841✔
91
    {
92
        this->bs_max_value = std::max(this->bs_max_value, value);
3,841✔
93
        this->bs_min_value = std::min(this->bs_min_value, value);
3,841✔
94
    }
3,841✔
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:
103

104
    struct chart_ident {
105
        explicit chart_ident(const T& ident) : ci_ident(ident) {}
20,558✔
106

107
        T ci_ident;
108
        text_attrs ci_attrs;
109
        bucket_stats_t ci_stats;
110
        ssize_t ci_last_seen_row{-1};
111
    };
112

113
    stacked_bar_chart& with_stacking_enabled(bool enabled)
×
114
    {
115
        this->sbc_do_stacking = enabled;
×
116
        return *this;
×
117
    }
118

119
    stacked_bar_chart& with_attrs_for_ident(const T& ident, text_attrs attrs)
22,739✔
120
    {
121
        auto& ci = this->find_ident(ident);
22,739✔
122
        ci.ci_attrs = attrs;
22,739✔
123
        return *this;
22,739✔
124
    }
125

126
    stacked_bar_chart& with_margins(unsigned long left, unsigned long right)
×
127
    {
128
        this->sbc_left = left;
×
129
        this->sbc_right = right;
×
130
        return *this;
×
131
    }
132

133
    stacked_bar_chart& with_show_state(show_state ss)
8,556✔
134
    {
135
        this->sbc_show_state = ss;
8,556✔
136
        return *this;
8,556✔
137
    }
138

139
    bool attrs_in_use(const text_attrs& attrs) const
711✔
140
    {
141
        for (const auto& ident : this->sbc_idents) {
711✔
142
            if (ident.ci_attrs == attrs) {
×
143
                return true;
×
144
            }
145
        }
146
        return false;
711✔
147
    }
148

149
    show_state show_next_ident(direction dir)
150
    {
151
        bool single_ident = this->sbc_idents.size() == 1;
152

153
        if (this->sbc_idents.empty()) {
154
            return this->sbc_show_state;
155
        }
156

157
        this->sbc_show_state = this->sbc_show_state.match(
158
            [&](show_none) -> show_state {
159
                switch (dir) {
160
                    case direction::forward:
161
                        if (single_ident) {
162
                            return show_all();
163
                        }
164
                        return show_one(0);
165
                    case direction::backward:
166
                        return show_all();
167
                }
168

169
                return show_all();
170
            },
171
            [&](show_one& one) -> show_state {
172
                switch (dir) {
173
                    case direction::forward:
174
                        if (one.so_index + 1 == this->sbc_idents.size()) {
175
                            return show_all();
176
                        }
177
                        return show_one(one.so_index + 1);
178
                    case direction::backward:
179
                        if (one.so_index == 0) {
180
                            return show_none();
181
                        }
182
                        return show_one(one.so_index - 1);
183
                }
184

185
                return show_all();
186
            },
187
            [&](show_all) -> show_state {
188
                switch (dir) {
189
                    case direction::forward:
190
                        return show_none();
191
                    case direction::backward:
192
                        if (single_ident) {
193
                            return show_none();
194
                        }
195
                        return show_one(this->sbc_idents.size() - 1);
196
                }
197

198
                return show_all();
199
            });
200

201
        return this->sbc_show_state;
202
    }
203

204
    void get_ident_to_show(T& ident_out) const
205
    {
206
        this->sbc_show_state.match(
207
            [](const show_none) {},
208
            [](const show_all) {},
209
            [&](const show_one& one) {
210
                ident_out = this->sbc_idents[one.so_index].ci_ident;
211
            });
212
    }
213

214
    void chart_attrs_for_value(const listview_curses& lc,
215
                               int& left,
216
                               unsigned long width,
217
                               const T& ident,
218
                               double value,
219
                               string_attrs_t& value_out,
220
                               std::optional<text_attrs> user_attrs
221
                               = std::nullopt) const;
222

223
    void clear()
4,875✔
224
    {
225
        this->sbc_idents.clear();
4,875✔
226
        this->sbc_ident_lookup.clear();
4,875✔
227
        this->sbc_show_state = show_none();
4,875✔
228
        this->sbc_row_sum = 0;
4,875✔
229
        this->sbc_row_items = 0;
4,875✔
230
        this->sbc_max_row_value = 0;
4,875✔
231
        this->sbc_max_row_items = 0;
4,875✔
232
    }
4,875✔
233

234
    chart_ident& add_value(const T& ident, double amount = 1.0)
3,841✔
235
    {
236
        auto& ci = this->find_ident(ident);
3,841✔
237
        ci.ci_stats.update(amount);
3,841✔
238
        this->sbc_row_sum += amount;
3,841✔
239
        if (ci.ci_last_seen_row != this->sbc_row_counter) {
3,841✔
240
            ci.ci_last_seen_row = this->sbc_row_counter;
3,841✔
241
            this->sbc_row_items += 1;
3,841✔
242
        }
243

244
        return ci;
3,841✔
245
    }
246

247
    void next_row()
9,549✔
248
    {
249
        if (this->sbc_row_sum > this->sbc_max_row_value) {
9,549✔
250
            this->sbc_max_row_value = this->sbc_row_sum;
1,174✔
251
        }
252
        if (this->sbc_row_items > this->sbc_max_row_items) {
9,549✔
253
            this->sbc_max_row_items = this->sbc_row_items;
1,060✔
254
        }
255
        this->sbc_row_sum = 0;
9,549✔
256
        this->sbc_row_items = 0;
9,549✔
257
        this->sbc_row_counter += 1;
9,549✔
258
    }
9,549✔
259

260
    const bucket_stats_t& get_stats_for(const T& ident)
×
261
    {
262
        const chart_ident& ci = this->find_ident(ident);
×
263

264
        return ci.ci_stats;
×
265
    }
266

267
protected:
268

269
    chart_ident& find_ident(const T& ident)
26,580✔
270
    {
271
        auto iter = this->sbc_ident_lookup.find(ident);
26,580✔
272
        if (iter == this->sbc_ident_lookup.end()) {
26,580✔
273
            this->sbc_ident_lookup[ident] = this->sbc_idents.size();
20,558✔
274
            this->sbc_idents.emplace_back(ident);
20,558✔
275
            return this->sbc_idents.back();
20,558✔
276
        }
277
        return this->sbc_idents[iter->second];
6,022✔
278
    }
279

280
    bool sbc_do_stacking{true};
281
    unsigned long sbc_left{0}, sbc_right{0};
282
    std::vector<chart_ident> sbc_idents;
283
    std::unordered_map<T, unsigned int> sbc_ident_lookup;
284
    show_state sbc_show_state{show_none()};
285

286
    ssize_t sbc_row_counter{0};
287
    double sbc_row_sum{0};
288
    size_t sbc_row_items{0};
289
    double sbc_max_row_value{0};
290
    size_t sbc_max_row_items{0};
291
};
292

293
class hist_source2
294
    : public text_sub_source
295
    , public text_time_translator {
296
public:
297
    enum class hist_type_t : uint8_t {
298
        HT_NORMAL,
299
        HT_WARNING,
300
        HT_ERROR,
301
        HT_MARK,
302

303
        HT__MAX
304
    };
305

306
    hist_source2() { this->clear(); }
1,512✔
307

308
    ~hist_source2() override = default;
756✔
309

310
    bool empty() const override { return false; }
×
311

312
    void init();
313

314
    void set_time_slice(std::chrono::microseconds slice)
640✔
315
    {
316
        this->hs_time_slice = slice;
640✔
317
    }
640✔
318

319
    std::chrono::microseconds get_time_slice() const
320
    {
321
        return this->hs_time_slice;
322
    }
323

324
    size_t text_line_count() override { return this->hs_line_count; }
9,483✔
325

326
    size_t text_line_width(textview_curses& curses) override;
327

328
    void clear();
329

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

334
    void end_of_row();
335

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

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

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

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

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

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

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

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

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

376
    static constexpr int64_t BLOCK_SIZE = 100;
377

378
    struct bucket_block {
379
        bucket_block()
614✔
380
        {
62,014✔
381
            memset(this->bb_buckets, 0, sizeof(this->bb_buckets));
614✔
382
        }
614✔
383

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

388
    bucket_t& find_bucket(int64_t index);
389

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

399
#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