From fc3dac373e32c78f8799bab458c829c989d9cb7e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 25 Jan 2023 03:36:40 +0800 Subject: [PATCH] cabana: colorful logs based on activity (#27008) * color logs * remove space * update in updateColors old-commit-hash: b2675cef9a9d4993859b0cd924a1ca74281024f9 --- tools/cabana/SConscript | 2 +- tools/cabana/historylog.cc | 34 +++++++-- tools/cabana/historylog.h | 7 +- tools/cabana/messageswidget.cc | 46 +----------- tools/cabana/messageswidget.h | 7 -- tools/cabana/streams/abstractstream.cc | 62 +--------------- tools/cabana/streams/abstractstream.h | 11 +-- tools/cabana/util.cc | 99 ++++++++++++++++++++++++++ tools/cabana/util.h | 38 ++++++++++ 9 files changed, 176 insertions(+), 130 deletions(-) create mode 100644 tools/cabana/util.cc create mode 100644 tools/cabana/util.h diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 9b172a544f..fbf3c03d36 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -21,7 +21,7 @@ cabana_env = qt_env.Clone() prev_moc_path = cabana_env['QT_MOCHPREFIX'] cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', - 'commands.cc', 'messageswidget.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + 'commands.cc', 'messageswidget.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, asset_obj], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('test'): diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index f0f40697b8..4a975afc4d 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -16,16 +16,16 @@ void HistoryLogModel::setDisplayType(HistoryLogModel::DisplayType type) { QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { const bool display_signals = display_type == HistoryLogModel::Signals; + const auto &m = messages[index.row()]; if (role == Qt::DisplayRole) { - const auto &m = messages[index.row()]; if (index.column() == 0) { return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2); } - return display_signals ? QString::number(m.sig_values[index.column() - 1]) : m.data; - } else if (role == Qt::FontRole && index.column() == 1 && !display_signals) { - return QFontDatabase::systemFont(QFontDatabase::FixedFont); + return display_signals ? QString::number(m.sig_values[index.column() - 1]) : toHex(m.data); } else if (role == Qt::ToolTipRole && index.column() > 0 && display_signals) { return tr("double click to open the chart"); + } else if (role == Qt::UserRole && index.column() == 1 && !display_signals) { + return HexColors::toVariantList(m.colors); } return {}; } @@ -45,6 +45,7 @@ void HistoryLogModel::refresh() { beginResetModel(); last_fetch_time = 0; messages.clear(); + hex_colors.clear(); updateState(); endResetModel(); } @@ -91,6 +92,7 @@ void HistoryLogModel::updateState() { if ((has_more_data = !new_msgs.empty())) { beginInsertRows({}, 0, new_msgs.size() - 1); messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); + updateColors(); endInsertRows(); } last_fetch_time = current_time; @@ -103,11 +105,29 @@ void HistoryLogModel::fetchMore(const QModelIndex &parent) { if ((has_more_data = !new_msgs.empty())) { beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1); messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); + if (!dynamic_mode) { + updateColors(); + } endInsertRows(); } } } +void HistoryLogModel::updateColors() { + if (display_type == HistoryLogModel::Hex) { + const auto freq = can->lastMessage(msg_id).freq; + if (dynamic_mode) { + for (auto it = messages.rbegin(); it != messages.rend(); ++it) { + it->colors = hex_colors.compute(it->data, it->mono_time / (double)1e9, freq); + } + } else { + for (auto it = messages.begin(); it != messages.end(); ++it) { + it->colors = hex_colors.compute(it->data, it->mono_time / (double)1e9, freq); + } + } + } +} + template std::deque HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) { std::deque msgs; @@ -124,7 +144,7 @@ std::deque HistoryLogModel::fetchData(InputIt first, I if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { auto &m = msgs.emplace_back(); m.mono_time = (*it)->mono_time; - m.data = toHex(QByteArray((char *)dat.begin(), dat.size())); + m.data = QByteArray((char *)dat.begin(), dat.size()); m.sig_values = values; if (msgs.size() >= batch_size && min_time == 0) return msgs; @@ -135,6 +155,7 @@ std::deque HistoryLogModel::fetchData(InputIt first, I } return msgs; } + template std::deque HistoryLogModel::fetchData<>(std::vector::iterator first, std::vector::iterator last, uint64_t min_time); template std::deque HistoryLogModel::fetchData<>(std::vector::reverse_iterator first, std::vector::reverse_iterator last, uint64_t min_time); @@ -183,6 +204,7 @@ HistoryLog::HistoryLog(QWidget *parent) : QTableView(parent) { horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | (Qt::Alignment)Qt::TextWordWrap); horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); verticalHeader()->setVisible(false); + setItemDelegateForColumn(1, new MessageBytesDelegate(this)); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); } @@ -225,8 +247,8 @@ LogsWidget::LogsWidget(QWidget *parent) : QWidget(parent) { QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh); QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged); + dynamic_mode->setChecked(true); if (can->liveStreaming()) { - dynamic_mode->setChecked(true); dynamic_mode->setEnabled(false); } } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 2ab204af15..34af713fd3 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -41,12 +41,14 @@ public: } void setDynamicMode(int state); void segmentsMerged(); + void updateColors(); void refresh(); struct Message { uint64_t mono_time = 0; QVector sig_values; - QString data; + QByteArray data; + QVector colors; }; template @@ -54,6 +56,7 @@ public: std::deque fetchData(uint64_t from_time, uint64_t min_time = 0); QString msg_id; + HexColors hex_colors; bool has_more_data = true; const int batch_size = 50; int filter_sig_idx = -1; @@ -62,7 +65,7 @@ public: std::function filter_cmp = nullptr; std::deque messages; std::vector sigs; - bool dynamic_mode = false; + bool dynamic_mode = true; DisplayType display_type = HistoryLogModel::Signals; }; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 0d06604fdc..353fe26340 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -79,15 +79,8 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { case 3: return can_data.count; case 4: return toHex(can_data.dat); } - } else if (role == Qt::FontRole && index.column() == columnCount() - 1) { - return QFontDatabase::systemFont(QFontDatabase::FixedFont); } else if (role == Qt::UserRole && index.column() == 4) { - - QList colors; - for (int i = 0; i < can_data.dat.size(); i++){ - colors.append(can_data.colors[i]); - } - return colors; + return HexColors::toVariantList(can_data.colors); } return {}; } @@ -174,40 +167,3 @@ void MessageListModel::sort(int column, Qt::SortOrder order) { sortMessages(); } } - - -MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) { -} - -void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - QList colors = index.data(Qt::UserRole).toList(); - - QStyleOptionViewItemV4 opt = option; - initStyleOption(&opt, index); - - const QFont font = index.data(Qt::FontRole).value(); - painter->setFont(font); - - QRect rect = opt.rect; - QString bytes = QString(opt.text); - - QRect pos = rect; - QRect space = painter->boundingRect(pos, opt.displayAlignment, " "); - pos.setX(pos.x() + space.width()); - - if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) { - painter->setPen(option.palette.color(QPalette::HighlightedText)); - } else { - painter->setPen(option.palette.color(QPalette::Text)); - } - - int i = 0; - for (auto &byte : bytes.split(" ")) { - QRect sz = painter->boundingRect(pos, opt.displayAlignment, byte); - const int m = space.width() / 2; - painter->fillRect(sz.marginsAdded(QMargins(m + 1, m, m, m)), colors[i].value()); - painter->drawText(pos, opt.displayAlignment, byte); - pos.setX(pos.x() + sz.width() + space.width()); - i++; - } -} diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 187e4715b9..49f57b78e6 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -42,10 +42,3 @@ protected: QString current_msg_id; MessageListModel *model; }; - -class MessageBytesDelegate : public QStyledItemDelegate { - Q_OBJECT -public: - MessageBytesDelegate(QObject *parent); - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; -}; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index 6ae1900184..b188c41826 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -17,15 +17,9 @@ void AbstractStream::process(QHash *messages) { processing = false; } -static QColor blend(QColor a, QColor b) { - return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); -} - bool AbstractStream::updateEvent(const Event *event) { static std::unique_ptr new_msgs = std::make_unique>(); - static QHash prev_dat; - static QHash> colors; - static QHash> last_change_t; + static QHash hex_colors; static double prev_update_ts = 0; if (event->which == cereal::Event::Which::CAN) { @@ -48,59 +42,7 @@ bool AbstractStream::updateEvent(const Event *event) { if (double delta = (current_sec - counters_begin_sec); delta > 0) { data.freq = data.count / delta; } - - // Init colors - if (colors[id].size() != data.dat.size()) { - colors[id].clear(); - for (int i = 0; i < data.dat.size(); i++){ - colors[id].append(QColor(0, 0, 0, 0)); - } - } - - // Init last_change_t - if (last_change_t[id].size() != data.dat.size()) { - last_change_t[id].clear(); - for (int i = 0; i < data.dat.size(); i++){ - last_change_t[id].append(data.ts); - } - } - - // Compute background color for the bytes based on changes - if (prev_dat[id].size() == data.dat.size()) { - for (int i = 0; i < data.dat.size(); i++){ - uint8_t last = prev_dat[id][i]; - uint8_t cur = data.dat[i]; - - const int periodic_threshold = 10; - const int start_alpha = 128; - const float fade_time = 2.0; - - if (last != cur) { - double delta_t = data.ts - last_change_t[id][i]; - - if (delta_t * data.freq > periodic_threshold) { - // Last change was while ago, choose color based on delta up or down - if (cur > last) { - colors[id][i] = QColor(0, 187, 255, start_alpha); // Cyan - } else { - colors[id][i] = QColor(255, 0, 0, start_alpha); // Red - } - } else { - // Periodic changes - colors[id][i] = blend(colors[id][i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue - } - - last_change_t[id][i] = data.ts; - } else { - // Fade out - float alpha_delta = 1.0 / (data.freq + 1) / fade_time; - colors[id][i].setAlphaF(std::max(0.0, colors[id][i].alphaF() - alpha_delta)); - } - } - } - - data.colors = colors[id]; - prev_dat[id] = data.dat; + data.colors = hex_colors[id].compute(data.dat, data.ts, data.freq); } double ts = millis_since_boot(); diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 1fe90c8595..9a0187da5e 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -6,6 +6,7 @@ #include #include "tools/cabana/settings.h" +#include "tools/cabana/util.h" #include "tools/replay/replay.h" struct CanData { @@ -15,7 +16,7 @@ struct CanData { uint32_t count = 0; uint32_t freq = 0; QByteArray dat; - QList colors; + QVector colors; }; class AbstractStream : public QObject { @@ -64,13 +65,5 @@ protected: QHash counters; }; -inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); } -inline char toHex(uint value) { return "0123456789ABCDEF"[value & 0xF]; } -inline const QString &getColor(int i) { - // TODO: add more colors - static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; - return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; -} - // A global pointer referring to the unique AbstractStream object extern AbstractStream *can; diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc new file mode 100644 index 0000000000..0684d3c59a --- /dev/null +++ b/tools/cabana/util.cc @@ -0,0 +1,99 @@ +#include "tools/cabana/util.h" + +#include +#include + +static QColor blend(QColor a, QColor b) { + return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); +} + +const QVector &HexColors::compute(const QByteArray &dat, double ts, uint32_t freq) { + if (prev_dat.size() != dat.size()) { + colors.resize(dat.size()); + last_change_t.resize(dat.size()); + std::fill(colors.begin(), colors.end(), QColor(0, 0, 0, 0)); + std::fill(last_change_t.begin(), last_change_t.end(), ts); + } else { + for (int i = 0; i < dat.size(); ++i) { + const uint8_t last = prev_dat[i]; + const uint8_t cur = dat[i]; + + if (last != cur) { + double delta_t = ts - last_change_t[i]; + if (delta_t * freq > periodic_threshold) { + // Last change was while ago, choose color based on delta up or down + if (cur > last) { + colors[i] = QColor(0, 187, 255, start_alpha); // Cyan + } else { + colors[i] = QColor(255, 0, 0, start_alpha); // Red + } + } else { + // Periodic changes + colors[i] = blend(colors[i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue + } + + last_change_t[i] = ts; + } else { + // Fade out + float alpha_delta = 1.0 / (freq + 1) / fade_time; + colors[i].setAlphaF(std::max(0.0, colors[i].alphaF() - alpha_delta)); + } + } + } + + prev_dat = dat; + return colors; +} + +void HexColors::clear() { + prev_dat.clear(); + last_change_t.clear(); + colors.clear(); +} + +QList HexColors::toVariantList(const QVector &colors) { + QList ret; + ret.reserve(colors.size()); + for (auto &c : colors) ret.append(c); + return ret; +} + +// MessageBytesDelegate + +MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) { + fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); +} + +void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + QList colors = index.data(Qt::UserRole).toList(); + if (colors.empty()) { + QStyledItemDelegate::paint(painter, option, index); + return; + } + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + + if ((option.state & QStyle::State_Selected) && (option.state & QStyle::State_Active)) { + painter->setPen(option.palette.color(QPalette::HighlightedText)); + } else { + painter->setPen(option.palette.color(QPalette::Text)); + } + + painter->setFont(fixed_font); + QRect space = painter->boundingRect(opt.rect, opt.displayAlignment, " "); + QRect pos = painter->boundingRect(opt.rect, opt.displayAlignment, "00"); + pos.moveLeft(pos.x() + space.width()); + + int m = space.width() / 2; + const QMargins margins(m + 1, m, m, m); + + int i = 0; + for (auto &byte : opt.text.split(" ")) { + if (i < colors.size()) { + painter->fillRect(pos.marginsAdded(margins), colors[i].value()); + } + painter->drawText(pos, opt.displayAlignment, byte); + pos.moveLeft(pos.right() + space.width()); + i++; + } +} diff --git a/tools/cabana/util.h b/tools/cabana/util.h new file mode 100644 index 0000000000..146410d7c6 --- /dev/null +++ b/tools/cabana/util.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + +class HexColors { +public: + const QVector &compute(const QByteArray &dat, double ts, uint32_t freq); + static QList toVariantList(const QVector &colors); + void clear(); + +private: + const int periodic_threshold = 10; + const int start_alpha = 128; + const float fade_time = 2.0; + QByteArray prev_dat; + QVector last_change_t; + QVector colors; +}; + +class MessageBytesDelegate : public QStyledItemDelegate { + Q_OBJECT +public: + MessageBytesDelegate(QObject *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QFont fixed_font; +}; + +inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); } +inline char toHex(uint value) { return "0123456789ABCDEF"[value & 0xF]; } +inline const QString &getColor(int i) { + // TODO: add more colors + static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; + return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; +}