1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   Benchmark.cpp
6 
7   Dominic Mazzoni
8 
9 *******************************************************************//**
10 
11 \class BenchmarkDialog
12 \brief BenchmarkDialog is used for measuring performance and accuracy
13 of sample block storage.
14 
15 *//*******************************************************************/
16 
17 
18 
19 #include "Benchmark.h"
20 
21 #include <wx/app.h>
22 #include <wx/log.h>
23 #include <wx/textctrl.h>
24 #include <wx/button.h>
25 #include <wx/checkbox.h>
26 #include <wx/choice.h>
27 #include <wx/dialog.h>
28 #include <wx/sizer.h>
29 #include <wx/stattext.h>
30 #include <wx/timer.h>
31 #include <wx/utils.h>
32 #include <wx/valgen.h>
33 #include <wx/valtext.h>
34 #include <wx/intl.h>
35 
36 #include "SampleBlock.h"
37 #include "ShuttleGui.h"
38 #include "Project.h"
39 #include "WaveClip.h"
40 #include "WaveTrack.h"
41 #include "Sequence.h"
42 #include "Prefs.h"
43 #include "ProjectRate.h"
44 #include "ViewInfo.h"
45 
46 #include "FileNames.h"
47 #include "SelectFile.h"
48 #include "widgets/AudacityMessageBox.h"
49 #include "widgets/wxPanelWrapper.h"
50 
51 // Change these to the desired format...should probably make the
52 // choice available in the dialog
53 #define SampleType short
54 #define SampleFormat int16Sample
55 
56 class BenchmarkDialog final : public wxDialogWrapper
57 {
58 public:
59    // constructors and destructors
60    BenchmarkDialog( wxWindow *parent, AudacityProject &project );
61 
62    void MakeBenchmarkDialog();
63 
64 private:
65    // WDR: handler declarations
66    void OnRun( wxCommandEvent &event );
67    void OnSave( wxCommandEvent &event );
68    void OnClear( wxCommandEvent &event );
69    void OnClose( wxCommandEvent &event );
70 
71    void Printf(const TranslatableString &str);
72    void HoldPrint(bool hold);
73    void FlushPrint();
74 
75    AudacityProject &mProject;
76    const ProjectRate &mRate;
77 
78    bool      mHoldPrint;
79    wxString  mToPrint;
80 
81    wxString  mBlockSizeStr;
82    wxString  mDataSizeStr;
83    wxString  mNumEditsStr;
84    wxString  mRandSeedStr;
85 
86    bool      mBlockDetail;
87    bool      mEditDetail;
88 
89    wxTextCtrl  *mText;
90 
91 private:
92    DECLARE_EVENT_TABLE()
93 };
94 
RunBenchmark(wxWindow * parent,AudacityProject & project)95 void RunBenchmark( wxWindow *parent, AudacityProject &project )
96 {
97    /*
98    int action = AudacityMessageBox(
99 XO("This will close all project windows (without saving)\nand open the Audacity Benchmark dialog.\n\nAre you sure you want to do this?"),
100       XO("Benchmark"),
101       wxYES_NO | wxICON_EXCLAMATION,
102       NULL);
103 
104    if (action != wxYES)
105       return;
106 
107    for ( auto pProject : AllProjects{} )
108       GetProjectFrame( *pProject ).Close();
109    */
110 
111    BenchmarkDialog dlog{ parent, project };
112 
113    dlog.CentreOnParent();
114 
115    dlog.ShowModal();
116 }
117 
118 //
119 // BenchmarkDialog
120 //
121 
122 enum {
123    RunID = 1000,
124    BSaveID,
125    ClearID,
126    StaticTextID,
127    BlockSizeID,
128    DataSizeID,
129    NumEditsID,
130    RandSeedID
131 };
132 
BEGIN_EVENT_TABLE(BenchmarkDialog,wxDialogWrapper)133 BEGIN_EVENT_TABLE(BenchmarkDialog, wxDialogWrapper)
134    EVT_BUTTON( RunID,   BenchmarkDialog::OnRun )
135    EVT_BUTTON( BSaveID,  BenchmarkDialog::OnSave )
136    EVT_BUTTON( ClearID, BenchmarkDialog::OnClear )
137    EVT_BUTTON( wxID_CANCEL, BenchmarkDialog::OnClose )
138 END_EVENT_TABLE()
139 
140 BenchmarkDialog::BenchmarkDialog(
141    wxWindow *parent, AudacityProject &project)
142    :
143       /* i18n-hint: Benchmark means a software speed test */
144       wxDialogWrapper( parent, 0, XO("Benchmark"),
145                 wxDefaultPosition, wxDefaultSize,
146                 wxDEFAULT_DIALOG_STYLE |
147                 wxRESIZE_BORDER)
148    , mProject(project)
149    , mRate{ ProjectRate::Get(project) }
150 {
151    SetName();
152 
153    mBlockSizeStr = wxT("64");
154    mNumEditsStr = wxT("100");
155    mDataSizeStr = wxT("32");
156    mRandSeedStr = wxT("234657");
157 
158    mBlockDetail = false;
159    mEditDetail = false;
160 
161    HoldPrint(false);
162 
163    MakeBenchmarkDialog();
164 }
165 
166 // WDR: handler implementations for BenchmarkDialog
167 
OnClose(wxCommandEvent & WXUNUSED (event))168 void BenchmarkDialog::OnClose(wxCommandEvent & WXUNUSED(event))
169 {
170    EndModal(0);
171 }
172 
MakeBenchmarkDialog()173 void BenchmarkDialog::MakeBenchmarkDialog()
174 {
175    ShuttleGui S(this, eIsCreating);
176 
177    // Strings don't need to be translated because this class doesn't
178    // ever get used in a stable release.
179 
180    S.StartVerticalLay(true);
181    {
182       S.SetBorder(8);
183       S.StartMultiColumn(4);
184       {
185          //
186          S.Id(BlockSizeID)
187             .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mBlockSizeStr)
188             .AddTextBox(XXO("Disk Block Size (KB):"),
189                                              wxT(""),
190                                              12);
191 
192          //
193          S.Id(NumEditsID)
194             .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mNumEditsStr)
195             .AddTextBox(XXO("Number of Edits:"),
196                                             wxT(""),
197                                             12);
198 
199          //
200          S.Id(DataSizeID)
201             .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mDataSizeStr)
202             .AddTextBox(XXO("Test Data Size (MB):"),
203                                             wxT(""),
204                                             12);
205 
206          ///
207          S.Id(RandSeedID)
208             .Validator<wxTextValidator>(wxFILTER_NUMERIC, &mRandSeedStr)
209             /* i18n-hint: A "seed" is a number that initializes a
210                pseudorandom number generating algorithm */
211             .AddTextBox(XXO("Random Seed:"),
212                                             wxT(""),
213                                             12);
214 
215       }
216       S.EndMultiColumn();
217 
218       //
219       S.Validator<wxGenericValidator>(&mBlockDetail)
220          .AddCheckBox(XXO("Show detailed info about each block file"),
221                            false);
222 
223       //
224       S.Validator<wxGenericValidator>(&mEditDetail)
225          .AddCheckBox(XXO("Show detailed info about each editing operation"),
226                            false);
227 
228       //
229       mText = S.Id(StaticTextID)
230          /* i18n-hint noun */
231          .Name(XO("Output"))
232          .Style( wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH )
233          .MinSize( { 500, 200 } )
234          .AddTextWindow(wxT(""));
235 
236       //
237       S.SetBorder(10);
238       S.StartHorizontalLay(wxALIGN_LEFT | wxEXPAND, false);
239       {
240          S.StartHorizontalLay(wxALIGN_LEFT, false);
241          {
242             S.Id(RunID).AddButton(XXO("Run"), wxALIGN_CENTRE, true);
243             S.Id(BSaveID).AddButton(XXO("Save"));
244             /* i18n-hint verb; to empty or erase */
245             S.Id(ClearID).AddButton(XXO("Clear"));
246          }
247          S.EndHorizontalLay();
248 
249          S.StartHorizontalLay(wxALIGN_CENTER, true);
250          {
251             // Spacer
252          }
253          S.EndHorizontalLay();
254 
255          S.StartHorizontalLay(wxALIGN_NOT | wxALIGN_LEFT, false);
256          {
257             /* i18n-hint verb */
258             S.Id(wxID_CANCEL).AddButton(XXO("Close"));
259          }
260          S.EndHorizontalLay();
261       }
262       S.EndHorizontalLay();
263    }
264    S.EndVerticalLay();
265 
266    Fit();
267    SetSizeHints(GetSize());
268 }
269 
OnSave(wxCommandEvent & WXUNUSED (event))270 void BenchmarkDialog::OnSave( wxCommandEvent & WXUNUSED(event))
271 {
272 /* i18n-hint: Benchmark means a software speed test;
273    leave untranslated file extension .txt */
274    auto fName = XO("benchmark.txt").Translation();
275 
276    fName = SelectFile(FileNames::Operation::Export,
277       XO("Export Benchmark Data as:"),
278       wxEmptyString,
279       fName,
280       wxT("txt"),
281       { FileNames::TextFiles },
282       wxFD_SAVE | wxRESIZE_BORDER,
283       this);
284 
285    if (fName.empty())
286       return;
287 
288    mText->SaveFile(fName);
289 }
290 
OnClear(wxCommandEvent & WXUNUSED (event))291 void BenchmarkDialog::OnClear(wxCommandEvent & WXUNUSED(event))
292 {
293    mText->Clear();
294 }
295 
Printf(const TranslatableString & str)296 void BenchmarkDialog::Printf(const TranslatableString &str)
297 {
298    auto s = str.Translation();
299    mToPrint += s;
300    if (!mHoldPrint)
301       FlushPrint();
302 }
303 
HoldPrint(bool hold)304 void BenchmarkDialog::HoldPrint(bool hold)
305 {
306    mHoldPrint = hold;
307 
308    if (!mHoldPrint)
309       FlushPrint();
310 }
311 
FlushPrint()312 void BenchmarkDialog::FlushPrint()
313 {
314    while(mToPrint.length() > 100) {
315       mText->AppendText(mToPrint.Left(100));
316       mToPrint = mToPrint.Right(mToPrint.length() - 100);
317    }
318    if (mToPrint.length() > 0)
319       mText->AppendText(mToPrint);
320    mToPrint = wxT("");
321 }
322 
OnRun(wxCommandEvent & WXUNUSED (event))323 void BenchmarkDialog::OnRun( wxCommandEvent & WXUNUSED(event))
324 {
325    TransferDataFromWindow();
326 
327    if (!Validate())
328       return;
329 
330    // This code will become part of libaudacity,
331    // and this class will be phased out.
332    long blockSize, numEdits, dataSize, randSeed;
333 
334    mBlockSizeStr.ToLong(&blockSize);
335    mNumEditsStr.ToLong(&numEdits);
336    mDataSizeStr.ToLong(&dataSize);
337    mRandSeedStr.ToLong(&randSeed);
338 
339    if (blockSize < 1 || blockSize > 1024) {
340       AudacityMessageBox(
341          XO("Block size should be in the range 1 - 1024 KB.") );
342       return;
343    }
344 
345    if (numEdits < 1 || numEdits > 10000) {
346       AudacityMessageBox(
347          XO("Number of edits should be in the range 1 - 10000.") );
348       return;
349    }
350 
351    if (dataSize < 1 || dataSize > 2000) {
352       AudacityMessageBox(
353          XO("Test data size should be in the range 1 - 2000 MB.") );
354       return;
355    }
356 
357    bool editClipCanMove = true;
358    gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove);
359    gPrefs->Write(wxT("/GUI/EditClipCanMove"), false);
360    gPrefs->Flush();
361 
362    // Remember the old blocksize, so that we can restore it later.
363    auto oldBlockSize = Sequence::GetMaxDiskBlockSize();
364    Sequence::SetMaxDiskBlockSize(blockSize * 1024);
365 
366    const auto cleanup = finally( [&] {
367       Sequence::SetMaxDiskBlockSize(oldBlockSize);
368       gPrefs->Write(wxT("/GUI/EditClipCanMove"), editClipCanMove);
369       gPrefs->Flush();
370    } );
371 
372    wxBusyCursor busy;
373 
374    HoldPrint(true);
375 
376    const auto t =
377       WaveTrackFactory{ mRate,
378                     SampleBlockFactory::New( mProject )  }
379          .NewWaveTrack(SampleFormat);
380 
381    t->SetRate(1);
382 
383    srand(randSeed);
384 
385    uint64_t nChunks, chunkSize;
386    //chunkSize = 7500ull + (rand() % 1000ull);
387    chunkSize = 200ull + (rand() % 100ull);
388    nChunks = (dataSize * 1048576ull) / (chunkSize*sizeof(SampleType));
389    while (nChunks < 20 || chunkSize > (blockSize*1024)/4)
390    {
391       chunkSize = std::max( uint64_t(1), (chunkSize / 2) + (rand() % 100) );
392       nChunks = (dataSize * 1048576ull) / (chunkSize*sizeof(SampleType));
393    }
394 
395    // The chunks are the pieces we move around in the test.
396    // They are (and are supposed to be) a different size to
397    // the blocks that make the sample blocks.  That way we get to
398    // do some testing of when edit chunks cross sample block boundaries.
399    Printf( XO("Using %lld chunks of %lld samples each, for a total of %.1f MB.\n")
400       .Format( nChunks, chunkSize, nChunks*chunkSize*sizeof(SampleType)/1048576.0 ) );
401 
402    int trials = numEdits;
403 
404    using Samples = ArrayOf<SampleType>;
405    Samples small1{nChunks};
406    Samples block{chunkSize};
407 
408    Printf( XO("Preparing...\n") );
409 
410    wxTheApp->Yield();
411    FlushPrint();
412 
413    int v;
414    int bad;
415    int z;
416    long elapsed;
417    wxString tempStr;
418    wxStopWatch timer;
419 
420    for (uint64_t i = 0; i < nChunks; i++) {
421       v = SampleType(rand());
422       small1[i] = v;
423       for (uint64_t b = 0; b < chunkSize; b++)
424          block[b] = v;
425 
426       t->Append((samplePtr)block.get(), SampleFormat, chunkSize);
427    }
428    t->Flush();
429 
430    // This forces the WaveTrack to flush all of the appends (which is
431    // only necessary if you want to access the Sequence class directly,
432    // as we're about to do).
433    t->GetEndTime();
434 
435    if (t->GetClipByIndex(0)->GetSequence()->GetNumSamples() != nChunks * chunkSize) {
436       Printf( XO("Expected len %lld, track len %lld.\n")
437          .Format(
438             nChunks * chunkSize,
439             t->GetClipByIndex(0)->GetSequence()->GetNumSamples()
440                .as_long_long() ) );
441       goto fail;
442    }
443 
444    Printf( XO("Performing %d edits...\n").Format( trials ) );
445    wxTheApp->Yield();
446    FlushPrint();
447 
448    timer.Start();
449    for (z = 0; z < trials; z++) {
450       // First chunk to cut
451       // 0 <= x0 < nChunks
452       const uint64_t x0 = rand() % nChunks;
453 
454       // Number of chunks to cut
455       // 1 <= xlen <= nChunks - x0
456       const uint64_t xlen = 1 + (rand() % (nChunks - x0));
457       if (mEditDetail)
458          Printf( XO("Cut: %lld - %lld \n")
459             .Format( x0 * chunkSize, (x0 + xlen) * chunkSize) );
460 
461       Track::Holder tmp;
462       try {
463          tmp = t->Cut(double (x0 * chunkSize), double ((x0 + xlen) * chunkSize));
464       }
465       catch (const AudacityException&) {
466          Printf( XO("Trial %d\n").Format( z ) );
467          Printf( XO("Cut (%lld, %lld) failed.\n")
468             .Format( (x0 * chunkSize), (x0 + xlen) * chunkSize) );
469          Printf( XO("Expected len %lld, track len %lld.\n")
470             .Format(
471                nChunks * chunkSize,
472                t->GetClipByIndex(0)->GetSequence()->GetNumSamples()
473                   .as_long_long() ) );
474          goto fail;
475       }
476 
477       // Position to paste
478       // 0 <= y0 <= nChunks - xlen
479       const uint64_t y0 = rand() % (nChunks - xlen + 1);
480 
481       if (mEditDetail)
482          Printf( XO("Paste: %lld\n").Format( y0 * chunkSize ) );
483 
484       try {
485          t->Paste((double)(y0 * chunkSize), tmp.get());
486       }
487       catch (const AudacityException&) {
488          Printf( XO("Trial %d\nFailed on Paste.\n").Format( z ) );
489          goto fail;
490       }
491 
492       if (t->GetClipByIndex(0)->GetSequence()->GetNumSamples() != nChunks * chunkSize) {
493          Printf( XO("Trial %d\n").Format( z ) );
494          Printf( XO("Expected len %lld, track len %lld.\n")
495             .Format(
496                nChunks * chunkSize,
497                t->GetClipByIndex(0)->GetSequence()->GetNumSamples()
498                   .as_long_long() ) );
499          goto fail;
500       }
501 
502       // Permute small1 correspondingly to the cut and paste
503       auto first = &small1[0];
504       if (x0 + xlen < nChunks)
505          std::rotate( first + x0, first + x0 + xlen, first + nChunks );
506       std::rotate( first + y0, first + nChunks - xlen, first + nChunks );
507    }
508 
509    elapsed = timer.Time();
510 
511    if (mBlockDetail) {
512       auto seq = t->GetClipByIndex(0)->GetSequence();
513       seq->DebugPrintf(seq->GetBlockArray(), seq->GetNumSamples(), &tempStr);
514       mToPrint += tempStr;
515    }
516    Printf( XO("Time to perform %d edits: %ld ms\n").Format( trials, elapsed ) );
517    FlushPrint();
518    wxTheApp->Yield();
519 
520 
521 #if 0
522    Printf( XO("Checking file pointer leaks:\n") );
523    Printf( XO("Track # blocks: %ld\n").Format( t->GetBlockArray()->size() ) );
524    Printf( XO("Disk # blocks: \n") );
525    system("ls .audacity_temp/* | wc --lines");
526 #endif
527 
528    Printf( XO("Doing correctness check...\n") );
529    FlushPrint();
530    wxTheApp->Yield();
531 
532    bad = 0;
533    timer.Start();
534    for (uint64_t i = 0; i < nChunks; i++) {
535       v = small1[i];
536       t->Get((samplePtr)block.get(), SampleFormat, i * chunkSize, chunkSize);
537       for (uint64_t b = 0; b < chunkSize; b++)
538          if (block[b] != v) {
539             bad++;
540             if (bad < 10)
541                Printf( XO("Bad: chunk %lld sample %lld\n").Format( i, b ) );
542             b = chunkSize;
543          }
544    }
545    if (bad == 0)
546       Printf( XO("Passed correctness check!\n") );
547    else
548       Printf( XO("Errors in %d/%lld chunks\n").Format( bad, nChunks ) );
549 
550    elapsed = timer.Time();
551 
552    Printf( XO("Time to check all data: %ld ms\n").Format( elapsed ) );
553    Printf( XO("Reading data again...\n") );
554 
555    wxTheApp->Yield();
556    FlushPrint();
557 
558    timer.Start();
559 
560    for (uint64_t i = 0; i < nChunks; i++) {
561       v = small1[i];
562       t->Get((samplePtr)block.get(), SampleFormat, i * chunkSize, chunkSize);
563       for (uint64_t b = 0; b < chunkSize; b++)
564          if (block[b] != v)
565             bad++;
566    }
567 
568    elapsed = timer.Time();
569 
570    Printf( XO("Time to check all data (2): %ld ms\n").Format( elapsed ) );
571 
572    Printf( XO("At 44100 Hz, %d bytes per sample, the estimated number of\n simultaneous tracks that could be played at once: %.1f\n" )
573       .Format( SAMPLE_SIZE(SampleFormat), (nChunks*chunkSize/44100.0)/(elapsed/1000.0) ) );
574 
575    goto success;
576 
577  fail:
578    Printf( XO("TEST FAILED!!!\n") );
579 
580  success:
581 
582    Printf( XO("Benchmark completed successfully.\n") );
583    HoldPrint(false);
584 }
585