Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ngscopeclient/scopehal-apps
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 00dab719f639
Choose a base ref
...
head repository: ngscopeclient/scopehal-apps
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2e1777b60fcc
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on May 2, 2021

  1. Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    06e5336 View commit details
  2. Significant improvements (3x or more typical) to performance of wavef…

    …orm serialization by batching data in RAM then writing interleaved data vs lots of tiny fwrite() calls.
    azonenberg committed May 2, 2021

    Unverified

    This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
    Copy the full SHA
    2e1777b View commit details
Showing with 214 additions and 41 deletions.
  1. +1 −1 lib
  2. +183 −36 src/glscopeclient/HistoryWindow.cpp
  3. +16 −2 src/glscopeclient/HistoryWindow.h
  4. +14 −2 src/glscopeclient/OscilloscopeWindow.cpp
2 changes: 1 addition & 1 deletion lib
Submodule lib updated 330 files
219 changes: 183 additions & 36 deletions src/glscopeclient/HistoryWindow.cpp
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
* *
* glscopeclient *
* *
* Copyright (c) 2012-2020 Andrew D. Zonenberg *
* Copyright (c) 2012-2021 Andrew D. Zonenberg *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
@@ -35,6 +35,7 @@
#include "glscopeclient.h"
#include "OscilloscopeWindow.h"
#include "HistoryWindow.h"
#include "FileProgressDialog.h"

using namespace std;

@@ -325,8 +326,15 @@ void HistoryWindow::JumpToHistory(TimePoint timestamp)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Serialization

void HistoryWindow::SerializeWaveforms(string dir, IDTable& table)
void HistoryWindow::SerializeWaveforms(
string dir,
IDTable& table,
FileProgressDialog& progress,
float base_progress,
float progress_range)
{
progress.Update("Saving waveform metadata", base_progress);

//Figure out file name, and make the waveform directory
char tmp[512];
snprintf(tmp, sizeof(tmp), "%s/scope_%d_metadata.yml", dir.c_str(), table[m_scope]);
@@ -345,6 +353,8 @@ void HistoryWindow::SerializeWaveforms(string dir, IDTable& table)
string config = "waveforms:\n";
auto children = m_model->children();
int id = 1;
size_t iwave = 0;
float waveform_progress = progress_range / children.size();
for(auto it : children)
{
TimePoint key = (*it)[m_columns.m_capturekey];
@@ -370,62 +380,90 @@ void HistoryWindow::SerializeWaveforms(string dir, IDTable& table)

string wname = tmp;

//Save waveform data
//Kick off a thread to save data for each channel
vector<thread*> threads;
WaveformHistory history = (*it)[m_columns.m_history];
size_t nchans = history.size();
volatile float* channel_progress = new float[nchans];
volatile int* channel_done = new int[nchans];
size_t i=0;
for(auto jt : history)
{
channel_progress[i] = 0;
channel_done[i] = 0;

threads.push_back(new thread(
&HistoryWindow::DoSaveWaveformDataForStream,
wname,
jt.first,
jt.second,
channel_progress + i,
channel_done + i
));
i++;

//Save channel metadata
auto chan = jt.first.m_channel;
int index = chan->GetIndex();
size_t stream = jt.first.m_stream;
auto wave = jt.second;
if(wave == NULL) //trigger, disabled, etc
size_t nstream = jt.first.m_stream;
if(wave == NULL)
continue;

//First stream has no suffix for compat
if(stream == 0)
snprintf(tmp, sizeof(tmp), "%s/channel_%d.bin", wname.c_str(), index);
else
snprintf(tmp, sizeof(tmp), "%s/channel_%d_stream%zu.bin", wname.c_str(), index, stream);

FILE* fp = fopen(tmp, "wb");

//Save channel metadata
config += " :\n";
snprintf(tmp, sizeof(tmp), " index: %d\n", index);
config += tmp;
snprintf(tmp, sizeof(tmp), " stream: %zu\n", stream);
snprintf(tmp, sizeof(tmp), " stream: %zu\n", nstream);
config += tmp;
snprintf(tmp, sizeof(tmp), " timescale: %ld\n", wave->m_timescale);
config += tmp;
snprintf(tmp, sizeof(tmp), " trigphase: %zd\n", wave->m_triggerPhase);
config += tmp;
}

//Save channel data
auto achan = dynamic_cast<AnalogWaveform*>(wave);
auto dchan = dynamic_cast<DigitalWaveform*>(wave);
size_t len = wave->m_offsets.size();
for(size_t i=0; i<len; i++)
//Process events and update the display with each thread's progress
while(true)
{
//Figure out total progress across each channel. Stop if all threads are done
bool done = true;
float frac = 0;
for(size_t j=0; j<nchans; j++)
{
int64_t times[2] = { wave->m_offsets[i], wave->m_durations[i] };
if(2 != fwrite(times, sizeof(int64_t), 2, fp))
LogError("file write error\n");
if(achan)
{
if(1 != fwrite(&achan->m_samples[i], sizeof(float), 1, fp))
LogError("file write error\n");
}
else if(dchan)
{
bool b = dchan->m_samples[i];
if(1 != fwrite(&b, sizeof(bool), 1, fp))
LogError("file write error\n");
}
if(!channel_done[j])
done = false;
frac += channel_progress[j];
}
//TODO: support other waveform types (buses, eyes, etc)
fclose(fp);
if(done)
break;
frac /= nchans;

//Update the UI
snprintf(
tmp,
sizeof(tmp),
"Saving waveform %zu/%zu for instrument %s: %.0f %% complete",
iwave+1,
(size_t)children.size(),
m_scope->m_nickname.c_str(),
frac * 100);
progress.Update(tmp, base_progress + (iwave+frac)*waveform_progress);
std::this_thread::sleep_for(std::chrono::microseconds(1000 * 50));

g_app->DispatchPendingEvents();
}

delete[] channel_progress;
delete[] channel_done;

//Wait for threads to complete
for(auto t : threads)
{
t->join();
delete t;
}

id ++;
iwave ++;
}

//Save waveform metadata
@@ -448,6 +486,115 @@ void HistoryWindow::SerializeWaveforms(string dir, IDTable& table)
fclose(fp);
}

void HistoryWindow::DoSaveWaveformDataForStream(
std::string wname,
StreamDescriptor stream,
WaveformBase* wave,
volatile float* progress,
volatile int* done
)
{
auto chan = stream.m_channel;
int index = chan->GetIndex();
size_t nstream = stream.m_stream;
if(wave == NULL) //trigger, disabled, etc
{
*done = 1;
*progress = 1;
return;
}

//First stream has no suffix for compat
char tmp[512];
if(nstream == 0)
snprintf(tmp, sizeof(tmp), "%s/channel_%d.bin", wname.c_str(), index);
else
snprintf(tmp, sizeof(tmp), "%s/channel_%d_stream%zu.bin", wname.c_str(), index, nstream);

FILE* fp = fopen(tmp, "wb");

auto achan = dynamic_cast<AnalogWaveform*>(wave);
auto dchan = dynamic_cast<DigitalWaveform*>(wave);
size_t len = wave->m_offsets.size();

//Analog channels
const size_t samples_per_block = 10000;
if(achan)
{
#pragma pack(push, 1)
class asample_t
{
public:
int64_t off;
int64_t dur;
float voltage;

asample_t(int64_t o=0, int64_t d=0, float v=0)
: off(o), dur(d), voltage(v)
{}
};
#pragma pack(pop)

//Copy sample data
vector<asample_t, AlignedAllocator<asample_t, 64 > > samples;
samples.reserve(len);
for(size_t i=0; i<len; i++)
samples.push_back(asample_t(wave->m_offsets[i], wave->m_durations[i], achan->m_samples[i]));

//Write it
for(size_t i=0; i<len; i+= samples_per_block)
{
*progress = i * 1.0 / len;
size_t blocklen = min(len-i, samples_per_block);

if(blocklen != fwrite(&samples[i], sizeof(asample_t), blocklen, fp))
LogError("file write error\n");
}
}
else if(dchan)
{
#pragma pack(push, 1)
class dsample_t
{
public:
int64_t off;
int64_t dur;
bool voltage;

dsample_t(int64_t o=0, int64_t d=0, bool v=0)
: off(o), dur(d), voltage(v)
{}
};
#pragma pack(pop)

//Copy sample data
vector<dsample_t, AlignedAllocator<dsample_t, 64 > > samples;
samples.reserve(len);
for(size_t i=0; i<len; i++)
samples.push_back(dsample_t(wave->m_offsets[i], wave->m_durations[i], dchan->m_samples[i]));

//Write it
for(size_t i=0; i<len; i+= samples_per_block)
{
*progress = i * 1.0 / len;
size_t blocklen = min(len-i, samples_per_block);

if(blocklen != fwrite(&samples[i], sizeof(dsample_t), blocklen, fp))
LogError("file write error\n");
}
}
else
{
//TODO: support other waveform types (buses, eyes, etc)
LogError("unrecognized sample type\n");
}

fclose(fp);

*done = 1;
*progress = 1;
}

void HistoryWindow::ReplayHistory()
{
//TODO: is there a way to binary search a tree view?
18 changes: 16 additions & 2 deletions src/glscopeclient/HistoryWindow.h
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
* *
* glscopeclient *
* *
* Copyright (c) 2012-2020 Andrew D. Zonenberg *
* Copyright (c) 2012-2021 Andrew D. Zonenberg *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
@@ -37,6 +37,7 @@
#define HistoryWindow_h

class OscilloscopeWindow;
class FileProgressDialog;

typedef std::map<StreamDescriptor, WaveformBase*> WaveformHistory;

@@ -66,12 +67,25 @@ class HistoryWindow : public Gtk::Dialog

void SetMaxWaveforms(int n);

void SerializeWaveforms(std::string dir, IDTable& table);
void SerializeWaveforms(
std::string dir,
IDTable& table,
FileProgressDialog& progress,
float base_progress,
float progress_range);

protected:
virtual bool on_delete_event(GdkEventAny* ignored);
virtual void OnSelectionChanged();

static void DoSaveWaveformDataForStream(
std::string wname,
StreamDescriptor stream,
WaveformBase* wave,
volatile float* progress,
volatile int* done
);

Gtk::HBox m_hbox;
Gtk::Label m_maxLabel;
Gtk::Entry m_maxBox;
16 changes: 14 additions & 2 deletions src/glscopeclient/OscilloscopeWindow.cpp
Original file line number Diff line number Diff line change
@@ -1945,9 +1945,21 @@ void OscilloscopeWindow::SerializeWaveforms(IDTable& table)

chdir(cwd);

//Create and show progress dialog
FileProgressDialog progress;
progress.show();
float progress_per_scope = 1.0f / m_scopes.size();

//Serialize waveforms for each of our instruments
for(auto it : m_historyWindows)
it.second->SerializeWaveforms(m_currentDataDirName, table);
for(size_t i=0; i<m_scopes.size(); i++)
{
m_historyWindows[m_scopes[i]]->SerializeWaveforms(
m_currentDataDirName,
table,
progress,
i*progress_per_scope,
progress_per_scope);
}
}

void OscilloscopeWindow::OnAlphaChanged()