cabana: support logging live stream (#27884)

support logging live stream
old-commit-hash: e3a19ff074
beeps
Dean Lee 2 years ago committed by GitHub
parent e46c9899c1
commit 6a98fd2bfd
  1. 4
      tools/cabana/cabana.cc
  2. 14
      tools/cabana/route.cc
  3. 2
      tools/cabana/route.h
  4. 46
      tools/cabana/settings.cc
  5. 7
      tools/cabana/settings.h
  6. 12
      tools/cabana/streams/livestream.cc
  7. 4
      tools/cabana/streams/replaystream.cc
  8. 5
      tools/cabana/streams/replaystream.h

@ -57,13 +57,13 @@ int main(int argc, char *argv[]) {
route = DEMO_ROUTE; route = DEMO_ROUTE;
} }
auto replay_stream = new ReplayStream(replay_flags, &app); auto replay_stream = new ReplayStream(&app);
stream.reset(replay_stream); stream.reset(replay_stream);
if (route.isEmpty()) { if (route.isEmpty()) {
if (OpenRouteDialog dlg(nullptr); !dlg.exec()) { if (OpenRouteDialog dlg(nullptr); !dlg.exec()) {
return 0; return 0;
} }
} else if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"))) { } else if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags)) {
return 0; return 0;
} }
} }

@ -2,7 +2,7 @@
#include <QButtonGroup> #include <QButtonGroup>
#include <QFileDialog> #include <QFileDialog>
#include <QHBoxLayout> #include <QGridLayout>
#include <QLabel> #include <QLabel>
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
@ -11,12 +11,13 @@
OpenRouteDialog::OpenRouteDialog(QWidget *parent) : QDialog(parent) { OpenRouteDialog::OpenRouteDialog(QWidget *parent) : QDialog(parent) {
// TODO: get route list from api.comma.ai // TODO: get route list from api.comma.ai
QHBoxLayout *edit_layout = new QHBoxLayout; QGridLayout *edit_layout = new QGridLayout();
edit_layout->addWidget(new QLabel(tr("Route:"))); edit_layout->addWidget(new QLabel(tr("Route:"), 0, 0));
edit_layout->addWidget(route_edit = new QLineEdit(this)); edit_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route")); route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
auto file_btn = new QPushButton(tr("Browse..."), this); auto file_btn = new QPushButton(tr("Browse..."), this);
edit_layout->addWidget(file_btn); edit_layout->addWidget(file_btn, 0, 2);
edit_layout->addWidget(no_vipc = new QCheckBox(tr("No video")), 1, 1);
btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel); btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
btn_box->button(QDialogButtonBox::Open)->setEnabled(false); btn_box->button(QDialogButtonBox::Open)->setEnabled(false);
@ -56,7 +57,8 @@ void OpenRouteDialog::loadRoute() {
if (!is_valid_format) { if (!is_valid_format) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route)); QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
} else { } else {
failed_to_load = !dynamic_cast<ReplayStream *>(can)->loadRoute(route, data_dir); uint32_t flags = no_vipc->isChecked() ? REPLAY_FLAG_NO_VIPC : REPLAY_FLAG_NONE;
failed_to_load = !dynamic_cast<ReplayStream *>(can)->loadRoute(route, data_dir, flags);
if (failed_to_load) { if (failed_to_load) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route)); QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));
} else { } else {

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <QCheckBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QLineEdit> #include <QLineEdit>
#include <QDialog> #include <QDialog>
@ -14,6 +15,7 @@ public:
private: private:
QLineEdit *route_edit; QLineEdit *route_edit;
QCheckBox *no_vipc;
QDialogButtonBox *btn_box; QDialogButtonBox *btn_box;
bool failed_to_load = false; bool failed_to_load = false;
}; };

@ -3,8 +3,11 @@
#include <QAbstractButton> #include <QAbstractButton>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QDir> #include <QDir>
#include <QFileDialog>
#include <QFormLayout> #include <QFormLayout>
#include <QPushButton>
#include <QSettings> #include <QSettings>
#include <QStandardPaths>
#include "tools/cabana/util.h" #include "tools/cabana/util.h"
@ -32,6 +35,8 @@ void Settings::save() {
s.setValue("theme", theme); s.setValue("theme", theme);
s.setValue("sparkline_range", sparkline_range); s.setValue("sparkline_range", sparkline_range);
s.setValue("multiple_lines_bytes", multiple_lines_bytes); s.setValue("multiple_lines_bytes", multiple_lines_bytes);
s.setValue("log_livestream", log_livestream);
s.setValue("log_path", log_path);
} }
void Settings::load() { void Settings::load() {
@ -52,13 +57,20 @@ void Settings::load() {
theme = s.value("theme", 0).toInt(); theme = s.value("theme", 0).toInt();
sparkline_range = s.value("sparkline_range", 15).toInt(); sparkline_range = s.value("sparkline_range", 15).toInt();
multiple_lines_bytes = s.value("multiple_lines_bytes", true).toBool(); multiple_lines_bytes = s.value("multiple_lines_bytes", true).toBool();
log_livestream = s.value("log_livestream", true).toBool();
log_path = s.value("log_path").toString();
if (log_path.isEmpty()) {
log_path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/cabana_live_stream/";
}
} }
// SettingsDlg // SettingsDlg
SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Settings")); setWindowTitle(tr("Settings"));
QFormLayout *form_layout = new QFormLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
QGroupBox *groupbox = new QGroupBox("General");
QFormLayout *form_layout = new QFormLayout(groupbox);
theme = new QComboBox(this); theme = new QComboBox(this);
theme->setToolTip(tr("You may need to restart cabana after changes theme")); theme->setToolTip(tr("You may need to restart cabana after changes theme"));
@ -77,7 +89,10 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
cached_minutes->setSingleStep(1); cached_minutes->setSingleStep(1);
cached_minutes->setValue(settings.max_cached_minutes); cached_minutes->setValue(settings.max_cached_minutes);
form_layout->addRow(tr("Max Cached Minutes"), cached_minutes); form_layout->addRow(tr("Max Cached Minutes"), cached_minutes);
main_layout->addWidget(groupbox);
groupbox = new QGroupBox("Chart");
form_layout = new QFormLayout(groupbox);
chart_series_type = new QComboBox(this); chart_series_type = new QComboBox(this);
chart_series_type->addItems({tr("Line"), tr("Step Line"), tr("Scatter")}); chart_series_type->addItems({tr("Line"), tr("Step Line"), tr("Scatter")});
chart_series_type->setCurrentIndex(settings.chart_series_type); chart_series_type->setCurrentIndex(settings.chart_series_type);
@ -88,12 +103,31 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) {
chart_height->setSingleStep(10); chart_height->setSingleStep(10);
chart_height->setValue(settings.chart_height); chart_height->setValue(settings.chart_height);
form_layout->addRow(tr("Chart Height"), chart_height); form_layout->addRow(tr("Chart Height"), chart_height);
main_layout->addWidget(groupbox);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply); log_livestream = new QGroupBox(tr("Enable live stream logging"), this);
form_layout->addRow(buttonBox); log_livestream->setCheckable(true);
QHBoxLayout *path_layout = new QHBoxLayout(log_livestream);
path_layout->addWidget(log_path = new QLineEdit(settings.log_path, this));
log_path->setReadOnly(true);
auto browse_btn = new QPushButton(tr("B&rowse..."));
path_layout->addWidget(browse_btn);
main_layout->addWidget(log_livestream);
setFixedWidth(360); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton *button) { main_layout->addWidget(buttonBox);
main_layout->addStretch(1);
QObject::connect(browse_btn, &QPushButton::clicked, [this]() {
QString fn = QFileDialog::getExistingDirectory(
this, tr("Log File Location"),
QStandardPaths::writableLocation(QStandardPaths::HomeLocation),
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!fn.isEmpty()) {
log_path->setText(fn);
}
});
QObject::connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton *button) {
auto role = buttonBox->buttonRole(button); auto role = buttonBox->buttonRole(button);
if (role == QDialogButtonBox::AcceptRole) { if (role == QDialogButtonBox::AcceptRole) {
save(); save();
@ -115,6 +149,8 @@ void SettingsDlg::save() {
settings.max_cached_minutes = cached_minutes->value(); settings.max_cached_minutes = cached_minutes->value();
settings.chart_series_type = chart_series_type->currentIndex(); settings.chart_series_type = chart_series_type->currentIndex();
settings.chart_height = chart_height->value(); settings.chart_height = chart_height->value();
settings.log_livestream = log_livestream->isChecked();
settings.log_path = log_path->text();
settings.save(); settings.save();
emit settings.changed(); emit settings.changed();
} }

@ -1,8 +1,11 @@
#pragma once #pragma once
#include <QByteArray> #include <QByteArray>
#include <QCheckBox>
#include <QComboBox> #include <QComboBox>
#include <QDialog> #include <QDialog>
#include <QGroupBox>
#include <QLineEdit>
#include <QSpinBox> #include <QSpinBox>
#define LIGHT_THEME 1 #define LIGHT_THEME 1
@ -25,6 +28,8 @@ public:
int theme = 0; int theme = 0;
int sparkline_range = 15; // 15 seconds int sparkline_range = 15; // 15 seconds
bool multiple_lines_bytes = true; bool multiple_lines_bytes = true;
bool log_livestream = true;
QString log_path;
QString last_dir; QString last_dir;
QString last_route_dir; QString last_route_dir;
QByteArray geometry; QByteArray geometry;
@ -48,6 +53,8 @@ public:
QSpinBox *chart_height; QSpinBox *chart_height;
QComboBox *chart_series_type; QComboBox *chart_series_type;
QComboBox *theme; QComboBox *theme;
QGroupBox *log_livestream;
QLineEdit *log_path;
}; };
extern Settings settings; extern Settings settings;

@ -19,6 +19,13 @@ void LiveStream::streamThread() {
if (!zmq_address.isEmpty()) { if (!zmq_address.isEmpty()) {
setenv("ZMQ", "1", 1); setenv("ZMQ", "1", 1);
} }
std::unique_ptr<std::ofstream> fs;
if (settings.log_livestream) {
std::string path = (settings.log_path + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd--hh-mm-ss") + "--0").toStdString();
util::create_directories(path, 0755);
fs.reset(new std::ofstream(path + "/rlog" , std::ios::binary | std::ios::out));
}
std::unique_ptr<Context> context(Context::create()); std::unique_ptr<Context> context(Context::create());
std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString();
std::unique_ptr<SubSocket> sock(SubSocket::create(context.get(), "can", address)); std::unique_ptr<SubSocket> sock(SubSocket::create(context.get(), "can", address));
@ -31,9 +38,12 @@ void LiveStream::streamThread() {
QThread::msleep(50); QThread::msleep(50);
continue; continue;
} }
if (fs) {
fs->write(msg->getData(), msg->getSize());
}
std::lock_guard lk(lock); std::lock_guard lk(lock);
handleEvent(messages.emplace_back(msg).event); handleEvent(messages.emplace_back(msg).event);
// TODO: write stream to log file to replay it with cabana --data_dir flag.
} }
} }

@ -1,6 +1,6 @@
#include "tools/cabana/streams/replaystream.h" #include "tools/cabana/streams/replaystream.h"
ReplayStream::ReplayStream(uint32_t replay_flags, QObject *parent) : replay_flags(replay_flags), AbstractStream(parent, false) { ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent, false) {
QObject::connect(&settings, &Settings::changed, [this]() { QObject::connect(&settings, &Settings::changed, [this]() {
if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes); if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes);
}); });
@ -25,7 +25,7 @@ void ReplayStream::mergeSegments() {
} }
} }
bool ReplayStream::loadRoute(const QString &route, const QString &data_dir) { bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) {
replay.reset(new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this)); replay.reset(new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this));
replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->setSegmentCacheLimit(settings.max_cached_minutes);
replay->installEventFilter(event_filter, this); replay->installEventFilter(event_filter, this);

@ -7,9 +7,9 @@ class ReplayStream : public AbstractStream {
Q_OBJECT Q_OBJECT
public: public:
ReplayStream(uint32_t replay_flags, QObject *parent); ReplayStream(QObject *parent);
~ReplayStream(); ~ReplayStream();
bool loadRoute(const QString &route, const QString &data_dir); bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
bool eventFilter(const Event *event); bool eventFilter(const Event *event);
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }; void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); };
inline QString routeName() const override { return replay->route()->name(); } inline QString routeName() const override { return replay->route()->name(); }
@ -29,6 +29,5 @@ public:
private: private:
void mergeSegments(); void mergeSegments();
std::unique_ptr<Replay> replay = nullptr; std::unique_ptr<Replay> replay = nullptr;
uint32_t replay_flags = REPLAY_FLAG_NONE;
std::set<int> processed_segments; std::set<int> processed_segments;
}; };

Loading…
Cancel
Save