cabana: colorful logs based on activity (#27008)

* color logs

* remove space

* update in updateColors
old-commit-hash: b2675cef9a
beeps
Dean Lee 2 years ago committed by GitHub
parent 802bc4e8e2
commit fc3dac373e
  1. 2
      tools/cabana/SConscript
  2. 34
      tools/cabana/historylog.cc
  3. 7
      tools/cabana/historylog.h
  4. 46
      tools/cabana/messageswidget.cc
  5. 7
      tools/cabana/messageswidget.h
  6. 62
      tools/cabana/streams/abstractstream.cc
  7. 11
      tools/cabana/streams/abstractstream.h
  8. 99
      tools/cabana/util.cc
  9. 38
      tools/cabana/util.h

@ -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'):

@ -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;
if (role == Qt::DisplayRole) {
const auto &m = messages[index.row()];
if (role == Qt::DisplayRole) {
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 <class InputIt>
std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) {
std::deque<HistoryLogModel::Message> msgs;
@ -124,7 +144,7 @@ std::deque<HistoryLogModel::Message> 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::Message> HistoryLogModel::fetchData(InputIt first, I
}
return msgs;
}
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::iterator first, std::vector<const Event*>::iterator last, uint64_t min_time);
template std::deque<HistoryLogModel::Message> HistoryLogModel::fetchData<>(std::vector<const Event*>::reverse_iterator first, std::vector<const Event*>::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);
if (can->liveStreaming()) {
dynamic_mode->setChecked(true);
if (can->liveStreaming()) {
dynamic_mode->setEnabled(false);
}
}

@ -41,12 +41,14 @@ public:
}
void setDynamicMode(int state);
void segmentsMerged();
void updateColors();
void refresh();
struct Message {
uint64_t mono_time = 0;
QVector<double> sig_values;
QString data;
QByteArray data;
QVector<QColor> colors;
};
template <class InputIt>
@ -54,6 +56,7 @@ public:
std::deque<Message> 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<bool(double, double)> filter_cmp = nullptr;
std::deque<Message> messages;
std::vector<const Signal*> sigs;
bool dynamic_mode = false;
bool dynamic_mode = true;
DisplayType display_type = HistoryLogModel::Signals;
};

@ -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<QVariant> 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<QVariant> colors = index.data(Qt::UserRole).toList();
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
const QFont font = index.data(Qt::FontRole).value<QFont>();
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<QColor>());
painter->drawText(pos, opt.displayAlignment, byte);
pos.setX(pos.x() + sz.width() + space.width());
i++;
}
}

@ -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;
};

@ -17,15 +17,9 @@ void AbstractStream::process(QHash<QString, CanData> *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<QHash<QString, CanData>>();
static QHash<QString, QByteArray> prev_dat;
static QHash<QString, QList<QColor>> colors;
static QHash<QString, QList<double>> last_change_t;
static QHash<QString, HexColors> 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();

@ -6,6 +6,7 @@
#include <QHash>
#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<QColor> colors;
QVector<QColor> colors;
};
class AbstractStream : public QObject {
@ -64,13 +65,5 @@ protected:
QHash<QString, uint32_t> 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;

@ -0,0 +1,99 @@
#include "tools/cabana/util.h"
#include <QFontDatabase>
#include <QPainter>
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<QColor> &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<QVariant> HexColors::toVariantList(const QVector<QColor> &colors) {
QList<QVariant> 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<QVariant> 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<QColor>());
}
painter->drawText(pos, opt.displayAlignment, byte);
pos.moveLeft(pos.right() + space.width());
i++;
}
}

@ -0,0 +1,38 @@
#pragma once
#include <QByteArray>
#include <QColor>
#include <QFont>
#include <QStyledItemDelegate>
#include <QVector>
class HexColors {
public:
const QVector<QColor> &compute(const QByteArray &dat, double ts, uint32_t freq);
static QList<QVariant> toVariantList(const QVector<QColor> &colors);
void clear();
private:
const int periodic_threshold = 10;
const int start_alpha = 128;
const float fade_time = 2.0;
QByteArray prev_dat;
QVector<double> last_change_t;
QVector<QColor> 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)];
}
Loading…
Cancel
Save