1 //
2 // Copyright 2010-2013 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 //
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 
8 #include "Responder.hpp"
9 #include <uhd/property_tree.hpp>
10 #include <uhd/utils/thread.hpp>
11 #include <boost/algorithm/string.hpp>
12 #include <boost/filesystem.hpp>
13 #include <boost/format.hpp>
14 #include <boost/thread/condition_variable.hpp>
15 #include <cmath>
16 #include <complex>
17 #include <csignal>
18 #include <fstream>
19 #include <iomanip>
20 #include <iostream>
21 #include <sstream>
22 
23 const std::string _eth_file("eths_info.txt");
24 
25 
26 // Redirect output to stderr
27 struct cerr_redirect
28 {
cerr_redirectcerr_redirect29     cerr_redirect(std::streambuf* new_buffer) : old(std::cerr.rdbuf(new_buffer)) {}
30 
~cerr_redirectcerr_redirect31     ~cerr_redirect()
32     {
33         std::cerr.rdbuf(old);
34     }
35 
36 private:
37     std::streambuf* old;
38 };
39 
40 
41 // Catch keyboard interrupts for clean manual abort
42 static bool s_stop_signal_called = false;
43 static int s_signal              = 0;
sig_int_handler(int signal)44 static void sig_int_handler(int signal)
45 {
46     s_stop_signal_called = true;
47     s_signal             = signal;
48 }
49 
50 // member of Responder to register sig int handler
register_stop_signal_handler()51 void Responder::register_stop_signal_handler()
52 {
53     std::signal(SIGINT, &sig_int_handler);
54 }
55 
56 // For ncurses. Print everything in stream to screen
FLUSH_SCREEN()57 void Responder::FLUSH_SCREEN()
58 {
59     printw(_ss.str().c_str());
60     refresh();
61     _ss.str("");
62 }
63 
64 // Like FLUSH_SCREEN but with new line
FLUSH_SCREEN_NL()65 void Responder::FLUSH_SCREEN_NL()
66 {
67     do {
68         int y, x;
69         getyx(_window, y, x);
70         if (x > 0) {
71             printw("\n");
72             y++;
73         }
74         FLUSH_SCREEN();
75     } while (0);
76 }
77 
78 // Constructor
Responder(Options & opt)79 Responder::Responder(Options& opt)
80     : _opt(opt)
81     , _stats_filename(opt.stats_filename)
82     , _delay(opt.delay)
83     , _samps_per_packet(opt.samps_per_packet)
84     , _delay_step(opt.delay_step)
85     , _simulate_frequency(opt.simulate_frequency)
86     , _allow_late_bursts(opt.allow_late_bursts)
87     , _no_delay(opt.no_delay)
88     ,
89     // Initialize atributes not given by Options
90     _num_total_samps(0)
91     , // printed on exit
92     _overruns(0)
93     , // printed on exit
94     _max_success(0)
95     , // < 0 --> write results to file
96     _return_code(RETCODE_OK)
97     , _stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS)
98     , _timeout_burst_count(0)
99     , _timeout_eob_count(0)
100     , _y_delay_pos(-1)
101     , _x_delay_pos(-1)
102     , // Remember the cursor position of delay line.
103     _last_overrun_count(0)
104 {
105     time(&_dbginfo.start_time); // for debugging
106 
107     // Disable logging to console
108     uhd::log::set_console_level(uhd::log::off);
109 
110     if (uhd::set_thread_priority_safe(_opt.rt_priority, _opt.realtime)
111         == false) // try to set realtime scheduling
112     {
113         cerr << "Failed to set real-time" << endl;
114     }
115 
116     _return_code = calculate_dependent_values();
117 
118 
119     // From this point on, everything is written to a ncurses window!
120     create_ncurses_window();
121 
122     print_create_usrp_msg();
123     try {
124         _usrp = create_usrp_device();
125     } catch (const std::runtime_error& e) {
126         print_msg(e.what());
127         _return_code = RETCODE_RUNTIME_ERROR;
128     } catch (...) {
129         print_msg("unhandled ERROR");
130         _return_code = RETCODE_UNKNOWN_EXCEPTION;
131         print_msg_and_wait("create USRP device failed!\nPress key to abort test...");
132         return;
133     }
134 
135     // Prepare array with response burst data.
136     _pResponse = alloc_response_buffer_with_data(_response_length);
137 
138     // ensure that filename is set
139     string test_id = _usrp->get_mboard_name();
140     if (set_stats_filename(test_id)) {
141         _return_code = RETCODE_BAD_ARGS; // make sure run() does return!
142         FLUSH_SCREEN();
143         if (_opt.batch_mode == false) {
144             print_msg_and_wait("Press any key to end...");
145         }
146         return;
147     }
148 
149     cerr_redirect(_ss_cerr.rdbuf());
150     register_stop_signal_handler();
151 }
152 
calculate_dependent_values()153 int Responder::calculate_dependent_values()
154 {
155     _response_length             = _opt.response_length();
156     _init_delay_count            = (int64_t)(_opt.sample_rate * _opt.init_delay);
157     _dc_offset_countdown         = (int64_t)(_opt.sample_rate * _opt.dc_offset_delay);
158     _level_calibration_countdown = (int64_t)_opt.level_calibration_count();
159     _original_simulate_duration  = _simulate_duration =
160         _opt.simulate_duration(_simulate_frequency);
161 
162     if (_simulate_duration > 0) {
163         // Skip settling period and calibration
164         _init_delay_count            = 0;
165         _dc_offset_countdown         = 0;
166         _level_calibration_countdown = 0;
167 
168         double highest_delay = 0.0;
169         if (_opt.test_iterations > 0)
170             highest_delay = max(_opt.delay_max, _opt.delay_min);
171         else if (_no_delay == false)
172             highest_delay = _delay;
173 
174         uint64_t highest_delay_samples = _opt.highest_delay_samples(highest_delay);
175         if ((highest_delay_samples + _response_length + _opt.flush_count)
176             > _simulate_duration) {
177             if (_opt.adjust_simulation_rate) // This is now done DURING the simulation
178                                              // based on active delay
179             {
180                 //_simulate_frequency = max_possible_rate;
181                 //_simulate_duration = (uint64_t)((double)sample_rate /
182                 //_simulate_frequency);
183             } else {
184                 cerr << boost::format(
185                             "Highest delay and response duration will exceed the pulse "
186                             "simulation rate (%ld + %ld > %ld samples)")
187                             % highest_delay_samples % _response_length
188                             % _simulate_duration
189                      << endl;
190                 int max_possible_rate = (int)get_max_possible_frequency(
191                     highest_delay_samples, _response_length);
192                 double max_possible_delay =
193                     (double)(_simulate_duration - (_response_length + _opt.flush_count))
194                     / (double)_opt.sample_rate;
195                 cerr << boost::format("Simulation rate must be less than %i Hz, or "
196                                       "maximum delay must be less than %f s")
197                             % max_possible_rate % max_possible_delay
198                      << endl;
199 
200                 if (_opt.ignore_simulation_check == 0)
201                     return RETCODE_BAD_ARGS;
202             }
203         }
204     } else {
205         boost::format fmt(
206             "Simulation frequency too high (%f Hz with sample_rate %f Msps)");
207         fmt % _simulate_frequency % (_opt.sample_rate / 1e6);
208         cerr << fmt << endl;
209         return RETCODE_BAD_ARGS;
210     }
211 
212     if (_opt.test_iterations > 0) // Force certain settings during test mode
213     {
214         _no_delay          = false;
215         _allow_late_bursts = false;
216         _delay             = _opt.delay_min;
217     }
218     return RETCODE_OK; // default return code
219 }
220 
221 // print test title to ncurses window
print_test_title()222 void Responder::print_test_title()
223 {
224     if (_opt.test_title.empty() == false) {
225         std::string title(_opt.test_title);
226         boost::replace_all(title, "%", "%%");
227         print_msg(title + "\n");
228     }
229 }
230 
print_usrp_status()231 void Responder::print_usrp_status()
232 {
233     std::string msg;
234     msg += (boost::format("Using device:\n%s\n") % _usrp->get_pp_string()).str();
235     msg += (boost::format("Setting RX rate: %f Msps\n") % (_opt.sample_rate / 1e6)).str();
236     msg += (boost::format("Actual RX rate:  %f Msps\n") % (_usrp->get_rx_rate() / 1e6))
237                .str();
238     msg += (boost::format("Setting TX rate: %f Msps\n") % (_opt.sample_rate / 1e6)).str();
239     msg +=
240         (boost::format("Actual TX rate:  %f Msps") % (_usrp->get_tx_rate() / 1e6)).str();
241     print_msg(msg);
242     print_tx_stream_status();
243     print_rx_stream_status();
244 }
245 
print_test_parameters()246 void Responder::print_test_parameters()
247 {
248     // Some status output shoud be printed here!
249     size_t rx_max_num_samps = _rx_stream->get_max_num_samps();
250     size_t tx_max_num_samps = _tx_stream->get_max_num_samps();
251     std::string msg;
252 
253     msg += (boost::format("Samples per buffer: %d\n") % _opt.samps_per_buff).str();
254     msg += (boost::format("Maximum number of samples: RX = %d, TX = %d\n")
255             % rx_max_num_samps % tx_max_num_samps)
256                .str();
257     msg += (boost::format("Response length: %ld samples (%f us)") % _response_length
258             % (_opt.response_duration * 1e6))
259                .str();
260 
261     if (_simulate_duration > 0)
262         msg += (boost::format("\nSimulating pulses at %f Hz (every %ld samples)")
263                 % _simulate_frequency % _simulate_duration)
264                    .str();
265 
266     if (_opt.test_iterations > 0) {
267         msg += (boost::format("\nTest coverage: %f -> %f (%f steps)") % _opt.delay_min
268                 % _opt.delay_max % _opt.delay_step)
269                    .str();
270 
271         if (_opt.end_test_after_success_count > 0)
272             msg += (boost::format("\nTesting will end after %d successful delays")
273                     % _opt.end_test_after_success_count)
274                        .str();
275     }
276 
277     if ((_dc_offset_countdown == 0) && (_simulate_frequency == 0.0)) {
278         msg += "\nDC offset disabled";
279     }
280     print_msg(msg);
281 }
282 
283 // e.g. B200 doesn't support this command. Check if possible and only set rx_dc_offset if
284 // available
set_usrp_rx_dc_offset(uhd::usrp::multi_usrp::sptr usrp,bool ena)285 void Responder::set_usrp_rx_dc_offset(uhd::usrp::multi_usrp::sptr usrp, bool ena)
286 {
287     uhd::property_tree::sptr tree = usrp->get_tree();
288     // FIXME: Path needs to be build in a programmatic way.
289     bool dc_offset_exists =
290         tree->exists(uhd::fs_path("/mboards/0/rx_frontends/A/dc_offset"));
291     if (dc_offset_exists) {
292         usrp->set_rx_dc_offset(ena);
293     }
294 }
295 
print_create_usrp_msg()296 void Responder::print_create_usrp_msg()
297 {
298     std::string msg("Creating the USRP device");
299     if (_opt.device_args.empty() == false)
300         msg.append((boost::format(" with args \"%s\"") % _opt.device_args).str());
301     msg.append("...");
302     print_msg(msg);
303 }
304 
create_usrp_device()305 uhd::usrp::multi_usrp::sptr Responder::create_usrp_device()
306 {
307     uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(_opt.device_args);
308     usrp->set_rx_rate(_opt.sample_rate); // set the rx sample rate
309     usrp->set_tx_rate(_opt.sample_rate); // set the tx sample rate
310     _tx_stream = create_tx_streamer(usrp);
311     _rx_stream = create_rx_streamer(usrp);
312     if ((_dc_offset_countdown == 0) && (_simulate_frequency == 0.0))
313         set_usrp_rx_dc_offset(usrp, false);
314     return usrp;
315 }
316 
create_rx_streamer(uhd::usrp::multi_usrp::sptr usrp)317 uhd::rx_streamer::sptr Responder::create_rx_streamer(uhd::usrp::multi_usrp::sptr usrp)
318 {
319     uhd::stream_args_t stream_args("fc32"); // complex floats
320     if (_samps_per_packet > 0) {
321         stream_args.args["spp"] = str(boost::format("%d") % _samps_per_packet);
322     }
323     uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args);
324     _samps_per_packet                = rx_stream->get_max_num_samps();
325 
326     return rx_stream;
327 }
328 
print_rx_stream_status()329 void Responder::print_rx_stream_status()
330 {
331     std::string msg;
332     msg += (boost::format("Samples per packet set to: %d\n") % _samps_per_packet).str();
333     msg += (boost::format("Flushing burst with %d samples") % _opt.flush_count).str();
334     if (_opt.skip_eob)
335         msg += "\nSkipping End-Of-Burst";
336     print_msg(msg);
337 }
338 
create_tx_streamer(uhd::usrp::multi_usrp::sptr usrp)339 uhd::tx_streamer::sptr Responder::create_tx_streamer(uhd::usrp::multi_usrp::sptr usrp)
340 {
341     uhd::stream_args_t tx_stream_args("fc32"); // complex floats
342     if (_allow_late_bursts == false) {
343         tx_stream_args.args["underflow_policy"] = "next_burst";
344     }
345     uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(tx_stream_args);
346     return tx_stream;
347 }
348 
print_tx_stream_status()349 void Responder::print_tx_stream_status()
350 {
351     std::string msg;
352     if (_allow_late_bursts == false) {
353         msg += "Underflow policy set to drop late bursts";
354     } else
355         msg += "Underflow policy set to allow late bursts";
356     if (_opt.skip_send)
357         msg += "\nNOT sending bursts";
358     else if (_opt.combine_eob)
359         msg += "\nCombining EOB into first send";
360     print_msg(msg);
361 }
362 
363 // handle transmit timeouts properly
handle_tx_timeout(int burst,int eob)364 void Responder::handle_tx_timeout(int burst, int eob)
365 {
366     if (_timeout_burst_count == 0 && _timeout_eob_count == 0)
367         time(&_dbginfo.first_send_timeout);
368     _timeout_burst_count += burst;
369     _timeout_eob_count += eob;
370     print_timeout_msg();
371 }
372 
print_timeout_msg()373 void Responder::print_timeout_msg()
374 {
375     move(_y_delay_pos + 3, _x_delay_pos);
376     print_msg((boost::format("Send timeout, burst_count = %ld\teob_count = %ld\n")
377                % _timeout_burst_count % _timeout_eob_count)
378                   .str());
379 }
380 
get_tx_metadata(uhd::time_spec_t rx_time,size_t n)381 uhd::tx_metadata_t Responder::get_tx_metadata(uhd::time_spec_t rx_time, size_t n)
382 {
383     uhd::tx_metadata_t tx_md;
384     tx_md.start_of_burst = true;
385     tx_md.end_of_burst   = false;
386     if ((_opt.skip_eob == false) && (_opt.combine_eob)) {
387         tx_md.end_of_burst = true;
388     }
389 
390     if (_no_delay == false) {
391         tx_md.has_time_spec = true;
392         tx_md.time_spec =
393             rx_time + uhd::time_spec_t(0, n, _opt.sample_rate) + uhd::time_spec_t(_delay);
394     } else {
395         tx_md.has_time_spec = false;
396     }
397     return tx_md;
398 }
399 
send_tx_burst(uhd::time_spec_t rx_time,size_t n)400 bool Responder::send_tx_burst(uhd::time_spec_t rx_time, size_t n)
401 {
402     if (_opt.skip_send == true) {
403         return false;
404     }
405     // send a single packet
406     uhd::tx_metadata_t tx_md = get_tx_metadata(rx_time, n);
407     const size_t length_to_send =
408         _response_length + (_opt.flush_count - (tx_md.end_of_burst ? 0 : 1));
409 
410     size_t num_tx_samps =
411         _tx_stream->send(_pResponse, length_to_send, tx_md, _opt.timeout); // send pulse!
412     if (num_tx_samps < length_to_send) {
413         handle_tx_timeout(1, 0);
414     }
415     if (_opt.skip_eob == false && _opt.combine_eob == false) {
416         tx_md.start_of_burst = false;
417         tx_md.end_of_burst   = true;
418         tx_md.has_time_spec  = false;
419 
420         const size_t eob_length_to_send = 1;
421 
422         size_t eob_num_tx_samps = _tx_stream->send(
423             &_pResponse[length_to_send], eob_length_to_send, tx_md); // send EOB
424         if (eob_num_tx_samps < eob_length_to_send) {
425             handle_tx_timeout(0, 1);
426         }
427     }
428 
429     return true;
430 }
431 
432 // ensure that stats_filename is not empty.
set_stats_filename(string test_id)433 bool Responder::set_stats_filename(string test_id)
434 {
435     if (_stats_filename.empty()) {
436         string file_friendly_test_id(test_id);
437         boost::replace_all(file_friendly_test_id, " ", "_");
438         boost::format fmt = boost::format("%slatency-stats.id_%s-rate_%i-spb_%i-spp_%i%s")
439                             % _opt.stats_filename_prefix % file_friendly_test_id
440                             % (int)_opt.sample_rate % _opt.samps_per_buff
441                             % _samps_per_packet % _opt.stats_filename_suffix;
442         _stats_filename     = str(fmt) + ".txt";
443         _stats_log_filename = str(fmt) + ".log";
444     }
445     return check_for_existing_results();
446 }
447 
448 // Check if results file can be overwritten
check_for_existing_results()449 bool Responder::check_for_existing_results()
450 {
451     bool ex = false;
452     if ((_opt.skip_if_results_exist) && (boost::filesystem::exists(_stats_filename))) {
453         print_msg((boost::format("Skipping invocation as results file already exists: %s")
454                    % _stats_filename)
455                       .str());
456         ex = true;
457     }
458     return ex;
459 }
460 
461 // Allocate an array with a burst response
alloc_response_buffer_with_data(uint64_t response_length)462 float* Responder::alloc_response_buffer_with_data(
463     uint64_t response_length) // flush_count, output_value, output_scale are const
464 {
465     float* pResponse = new float[(response_length + _opt.flush_count) * 2];
466     for (unsigned int i = 0; i < (response_length * 2); ++i)
467         pResponse[i] = _opt.output_value * _opt.output_scale;
468     for (unsigned int i = (response_length * 2);
469          i < ((response_length + _opt.flush_count) * 2);
470          ++i)
471         pResponse[i] = 0.0f;
472     return pResponse;
473 }
474 
475 // print test parameters for current delay time
print_formatted_delay_line(const uint64_t simulate_duration,const uint64_t old_simulate_duration,const STATS & statsPrev,const double delay,const double simulate_frequency)476 void Responder::print_formatted_delay_line(const uint64_t simulate_duration,
477     const uint64_t old_simulate_duration,
478     const STATS& statsPrev,
479     const double delay,
480     const double simulate_frequency)
481 {
482     if (_y_delay_pos < 0
483         || _x_delay_pos < 0) { // make sure it gets printed to the same position everytime
484         getyx(_window, _y_delay_pos, _x_delay_pos);
485     }
486     double score = 0.0;
487     if (statsPrev.detected > 0)
488         score = 100.0 * (double)(statsPrev.detected - statsPrev.missed)
489                 / (double)statsPrev.detected;
490     std::string form;
491     boost::format fmt0("Delay now: %.6f (previous delay %.6f scored %.1f%% [%ld / %ld])");
492     fmt0 % delay % statsPrev.delay % score % (statsPrev.detected - statsPrev.missed)
493         % statsPrev.detected;
494     form += fmt0.str();
495     if (old_simulate_duration != simulate_duration) {
496         boost::format fmt1(" [Simulation rate now: %.1f Hz (%ld samples)]");
497         fmt1 % simulate_frequency % simulate_duration;
498         form = form + fmt1.str();
499     }
500     move(_y_delay_pos, _x_delay_pos);
501     print_msg(form);
502 }
503 
504 // print message and wait for user interaction
print_msg_and_wait(std::string msg)505 void Responder::print_msg_and_wait(std::string msg)
506 {
507     msg = "\n" + msg;
508     print_msg(msg);
509     timeout(-1);
510     getch();
511     timeout(0);
512 }
513 
514 // print message to ncurses window
print_msg(std::string msg)515 void Responder::print_msg(std::string msg)
516 {
517     _ss << msg << endl;
518     FLUSH_SCREEN();
519 }
520 
521 // Check if error occured during call to receive
handle_rx_errors(uhd::rx_metadata_t::error_code_t err,size_t num_rx_samps)522 bool Responder::handle_rx_errors(
523     uhd::rx_metadata_t::error_code_t err, size_t num_rx_samps)
524 {
525     // handle errors
526     if (err == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) {
527         std::string msg = (boost::format("Timeout while streaming (received %ld samples)")
528                            % _num_total_samps)
529                               .str();
530         print_error_msg(msg);
531         _return_code = RETCODE_RECEIVE_TIMEOUT;
532         return true;
533     } else if (err == uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET) {
534         std::string msg =
535             (boost::format("Bad packet (received %ld samples)") % _num_total_samps).str();
536         print_error_msg(msg);
537         _return_code = RETCODE_BAD_PACKET;
538         return true;
539     } else if ((num_rx_samps == 0) && (err == uhd::rx_metadata_t::ERROR_CODE_NONE)) {
540         print_error_msg("Received no samples");
541         _return_code = RETCODE_RECEIVE_FAILED;
542         return true;
543     } else if (err == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) {
544         ++_overruns;
545         print_overrun_msg(); // update overrun info on console.
546     } else if (err != uhd::rx_metadata_t::ERROR_CODE_NONE) {
547         throw std::runtime_error(str(boost::format("Unexpected error code 0x%x") % err));
548     }
549     return false;
550 }
551 
552 // print overrun status message.
print_overrun_msg()553 void Responder::print_overrun_msg()
554 {
555     if (_num_total_samps > (_last_overrun_count + (uint64_t)(_opt.sample_rate * 1.0))) {
556         int y, x, y_max, x_max;
557         getyx(_window, y, x);
558         getmaxyx(_window, y_max, x_max);
559         move(y_max - 1, 0);
560         print_msg((boost::format("Overruns: %d") % _overruns).str());
561         move(y, x);
562         _last_overrun_count = _num_total_samps;
563     }
564 }
565 
566 // print error message on last line of ncurses window
print_error_msg(std::string msg)567 void Responder::print_error_msg(std::string msg)
568 {
569     int y, x, y_max, x_max;
570     getyx(_window, y, x);
571     getmaxyx(_window, y_max, x_max);
572     move(y_max - 2, 0);
573     clrtoeol();
574     print_msg(msg);
575     move(y, x);
576 }
577 
578 // calculate simulate frequency
get_simulate_frequency(double delay,uint64_t response_length,uint64_t original_simulate_duration)579 double Responder::get_simulate_frequency(
580     double delay, uint64_t response_length, uint64_t original_simulate_duration)
581 {
582     double simulate_frequency      = _simulate_frequency;
583     uint64_t highest_delay_samples = _opt.highest_delay_samples(delay);
584     if ((_opt.optimize_simulation_rate)
585         || ((highest_delay_samples + response_length + _opt.flush_count)
586                > original_simulate_duration)) {
587         simulate_frequency =
588             get_max_possible_frequency(highest_delay_samples, response_length);
589     }
590     return simulate_frequency;
591 }
592 
593 // calculate max possible simulate frequency
get_max_possible_frequency(uint64_t highest_delay_samples,uint64_t response_length)594 double Responder::get_max_possible_frequency(uint64_t highest_delay_samples,
595     uint64_t response_length) // only 2 args, others are all const!
596 {
597     return std::floor((double)_opt.sample_rate
598                       / (double)(highest_delay_samples + response_length
599                                  + _opt.flush_count + _opt.optimize_padding));
600 }
601 
602 // Check if conditions to finish test are met.
test_finished(size_t success_count)603 bool Responder::test_finished(size_t success_count)
604 {
605     if (success_count == _opt.end_test_after_success_count) {
606         print_msg(
607             (boost::format("\nTest complete after %d successes.") % success_count).str());
608         return true;
609     }
610     if (((_opt.delay_min <= _opt.delay_max) && (_delay >= _opt.delay_max))
611         || ((_opt.delay_min > _opt.delay_max) && (_delay <= _opt.delay_max))) {
612         print_msg("\nTest complete.");
613         return true;
614     }
615     return false;
616 }
617 
618 // handle keyboard input in interactive mode
handle_interactive_control()619 bool Responder::handle_interactive_control()
620 {
621     std::string msg = "";
622     int c           = wgetch(_window);
623     if (c > -1) {
624         // UP/DOWN Keys control delay step width
625         if ((c == KEY_DOWN) || (c == KEY_UP)) {
626             double dMag = log10(_delay_step);
627             int iMag    = (int)floor(dMag);
628             iMag += ((c == KEY_UP) ? 1 : -1);
629             _delay_step = pow(10.0, iMag);
630             msg += (boost::format("Step: %f") % _delay_step).str();
631         }
632         // LEFT/RIGHT Keys control absolute delay length
633         if ((c == KEY_LEFT) || (c == KEY_RIGHT)) {
634             double step = _delay_step * ((c == KEY_RIGHT) ? 1 : -1);
635             if ((_delay + step) >= 0.0)
636                 _delay += step;
637             msg += (boost::format("Delay: %f") % _delay).str();
638         }
639         // Enable/disable fixed delay <--> best effort mode
640         if (c == 'd') {
641             _no_delay = !_no_delay;
642 
643             if (_no_delay)
644                 msg += "Delay disabled (best effort)";
645             else
646                 msg += (boost::format("Delay: %f") % _delay).str();
647         } else if (c == 'q') // exit test
648         {
649             return true; // signal test to stop
650         } else if (c == 'l') // change late burst policy
651         {
652             _allow_late_bursts = !_allow_late_bursts;
653 
654             if (_allow_late_bursts)
655                 msg += "Allowing late bursts";
656             else
657                 msg += "Dropping late bursts";
658         }
659         print_interactive_msg(msg);
660     }
661     return false; // signal test to continue with updated values
662 }
663 
664 // print updated interactive control value
print_interactive_msg(std::string msg)665 void Responder::print_interactive_msg(std::string msg)
666 {
667     if (msg != "") {
668         // move cursor back to beginning of line
669         int y, x;
670         getyx(_window, y, x);
671         if (x > 0) {
672             move(y, 0);
673             clrtoeol();
674         }
675         print_msg(msg);
676         move(y, 0);
677     }
678 }
679 
680 // check if transmit burst is late
tx_burst_is_late()681 bool Responder::tx_burst_is_late()
682 {
683     uhd::async_metadata_t async_md;
684     if (_usrp->get_device()->recv_async_msg(async_md, 0)) {
685         if (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_TIME_ERROR) {
686             return true;
687         }
688     }
689     return false;
690 }
691 
create_ncurses_window()692 void Responder::create_ncurses_window()
693 {
694     _window = initscr();
695     cbreak(); // Unbuffered key input, except for signals (cf. 'raw')
696     noecho();
697     nonl();
698     intrflush(_window, FALSE);
699     keypad(_window, TRUE); // Enable function keys, arrow keys, ...
700     nodelay(_window, 0);
701     timeout(0);
702 }
703 
704 // print all fixed test parameters
print_init_test_status()705 void Responder::print_init_test_status()
706 {
707     // Clear the window and write new data.
708     erase();
709     refresh();
710     print_test_title();
711     print_usrp_status();
712     print_test_parameters();
713 
714     std::string msg("");
715     if (_opt.test_iterations > 0)
716         msg.append("Press Ctrl + C to abort test");
717     else
718         msg.append("Press Q stop streaming");
719     msg.append("\n");
720     print_msg(msg);
721 
722     _y_delay_pos = -1; // reset delay display line pos.
723     _x_delay_pos = -1;
724 }
725 
726 // in interactive mode with second usrp sending bursts. calibrate trigger level
calibrate_usrp_for_test_run()727 float Responder::calibrate_usrp_for_test_run()
728 {
729     bool calibration_finished = false;
730     float threshold           = 0.0f;
731     double ave_high = 0, ave_low = 0;
732     int ave_high_count = 0, ave_low_count = 0;
733     bool level_calibration_stage_2 =
734         false; // 1. stage = rough calibration ; 2. stage = fine calibration
735 
736     std::vector<std::complex<float>> buff(_opt.samps_per_buff);
737     while (
738         not s_stop_signal_called && !calibration_finished && _return_code == RETCODE_OK) {
739         uhd::rx_metadata_t rx_md;
740         size_t num_rx_samps =
741             _rx_stream->recv(&buff.front(), buff.size(), rx_md, _opt.timeout);
742 
743         // handle errors
744         if (handle_rx_errors(rx_md.error_code, num_rx_samps)) {
745             break;
746         }
747 
748         // Wait for USRP for DC offset calibration
749         if (_dc_offset_countdown > 0) {
750             _dc_offset_countdown -= (int64_t)num_rx_samps;
751             if (_dc_offset_countdown > 0)
752                 continue;
753             set_usrp_rx_dc_offset(_usrp, false);
754             print_msg("DC offset calibration complete");
755         }
756 
757         // Wait for certain time to minimize POWER UP effects
758         if (_init_delay_count > 0) {
759             _init_delay_count -= (int64_t)num_rx_samps;
760             if (_init_delay_count > 0)
761                 continue;
762             print_msg("Initial settling period elapsed");
763         }
764 
765         ////////////////////////////////////////////////////////////
766         // detect falling edges and calibrate detection values
767         if (_level_calibration_countdown > 0) {
768             if (level_calibration_stage_2 == false) {
769                 float average = 0.0f;
770                 for (size_t n = 0; n < num_rx_samps; n++)
771                     average += buff[n].real() * _opt.invert;
772                 average /= (float)num_rx_samps;
773 
774                 if (ave_low_count == 0) {
775                     ave_low = average;
776                     ++ave_low_count;
777                 } else if (average < ave_low) {
778                     ave_low = average;
779                     ++ave_low_count;
780                 }
781 
782                 if (ave_high_count == 0) {
783                     ave_high = average;
784                     ++ave_high_count;
785                 } else if (average > ave_high) {
786                     ave_high = average;
787                     ++ave_high_count;
788                 }
789             } else {
790                 for (size_t n = 0; n < num_rx_samps; n++) {
791                     float f = buff[n].real() * _opt.invert;
792                     if (f >= threshold) {
793                         ave_high += f;
794                         ave_high_count++;
795                     } else {
796                         ave_low += f;
797                         ave_low_count++;
798                     }
799                 }
800             }
801 
802             _level_calibration_countdown -= (int64_t)num_rx_samps;
803 
804             if (_level_calibration_countdown <= 0) {
805                 if (level_calibration_stage_2 == false) {
806                     level_calibration_stage_2    = true;
807                     _level_calibration_countdown = _opt.level_calibration_count();
808                     threshold                    = ave_low + ((ave_high - ave_low) / 2.0);
809                     print_msg((boost::format("Phase #1: Ave low: %.3f (#%d), ave high: "
810                                              "%.3f (#%d), threshold: %.3f")
811                                % ave_low % ave_low_count % ave_high % ave_high_count
812                                % threshold)
813                                   .str());
814                     ave_low_count = ave_high_count = 0;
815                     ave_low = ave_high = 0.0f;
816                     continue;
817                 } else {
818                     ave_low /= (double)ave_low_count;
819                     ave_high /= (double)ave_high_count;
820                     threshold = ave_low + ((ave_high - ave_low) * _opt.trigger_level);
821                     print_msg((boost::format("Phase #2: Ave low: %.3f (#%d), ave high: "
822                                              "%.3f (#%d), threshold: %.3f\n")
823                                % ave_low % ave_low_count % ave_high % ave_high_count
824                                % threshold)
825                                   .str());
826 
827                     _stream_cmd.stream_mode =
828                         uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
829                     _stream_cmd.stream_now = true;
830                     _usrp->issue_stream_cmd(_stream_cmd);
831 
832                     double diff = std::abs(ave_high - ave_low);
833                     if (diff < _opt.pulse_detection_threshold) {
834                         _return_code = RETCODE_BAD_ARGS;
835                         print_error_msg(
836                             (boost::format("Did not detect any pulses (difference %.6f < "
837                                            "detection threshold %.6f)")
838                                 % diff % _opt.pulse_detection_threshold)
839                                 .str());
840                         break;
841                     }
842 
843                     _stream_cmd.stream_mode =
844                         uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
845                     _stream_cmd.stream_now = true;
846                     _usrp->issue_stream_cmd(_stream_cmd);
847                 }
848             } else
849                 continue;
850         } // calibration finished
851         calibration_finished = true;
852     }
853     return threshold;
854 }
855 
856 // try to stop USRP properly after tests
stop_usrp_stream()857 void Responder::stop_usrp_stream()
858 {
859     try {
860         if (_usrp) {
861             _stream_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS;
862             _stream_cmd.stream_now  = true;
863             _usrp->issue_stream_cmd(_stream_cmd);
864         }
865     } catch (...) {
866         //
867     }
868 }
869 
870 // after each delay length update test parameters and print them
update_and_print_parameters(const STATS & statsPrev,const double delay)871 void Responder::update_and_print_parameters(const STATS& statsPrev, const double delay)
872 {
873     uint64_t old_simulate_duration = _simulate_duration;
874     _simulate_frequency =
875         get_simulate_frequency(delay, _response_length, _original_simulate_duration);
876     _simulate_duration = _opt.simulate_duration(_simulate_frequency);
877     print_formatted_delay_line(
878         _simulate_duration, old_simulate_duration, statsPrev, delay, _simulate_frequency);
879     _timeout_burst_count = 0;
880     _timeout_eob_count   = 0;
881 }
882 
883 // detect or simulate burst level.
get_new_state(uint64_t total_samps,uint64_t simulate_duration,float val,float threshold)884 bool Responder::get_new_state(
885     uint64_t total_samps, uint64_t simulate_duration, float val, float threshold)
886 {
887     bool new_state = false;
888     if (simulate_duration > 0) // only simulated input bursts!
889         new_state = (((total_samps) % simulate_duration) == 0);
890     else
891         new_state = (val >= threshold); // TODO: Just measure difference in fall
892     return new_state;
893 }
894 
895 // detect a pulse, respond to it and count number of pulses.
896 // statsCurrent holds parameters.
detect_respond_pulse_count(STATS & statsCurrent,std::vector<std::complex<float>> & buff,uint64_t trigger_count,size_t num_rx_samps,float threshold,uhd::time_spec_t rx_time)897 uint64_t Responder::detect_respond_pulse_count(STATS& statsCurrent,
898     std::vector<std::complex<float>>& buff,
899     uint64_t trigger_count,
900     size_t num_rx_samps,
901     float threshold,
902     uhd::time_spec_t rx_time)
903 {
904     // buff, threshold
905     bool input_state = false;
906     for (size_t n = 0; n < num_rx_samps; n++) {
907         float f = buff[n].real() * _opt.invert;
908 
909         bool new_state =
910             get_new_state(_num_total_samps + n, _simulate_duration, f, threshold);
911 
912         if ((new_state == false) && (input_state)) // == falling_edge
913         {
914             trigger_count++;
915             statsCurrent.detected++;
916 
917             if ((_opt.test_iterations > 0) && (_opt.skip_iterations > 0)
918                 && (statsCurrent.skipped == 0)
919                 && (_opt.skip_iterations == statsCurrent.detected)) {
920                 memset(&statsCurrent, 0x00, sizeof(STATS));
921                 statsCurrent.delay    = _delay;
922                 statsCurrent.detected = 1;
923                 statsCurrent.skipped  = _opt.skip_iterations;
924 
925                 trigger_count = 1;
926             }
927 
928             if (!send_tx_burst(rx_time, n)) {
929                 statsCurrent.missed++;
930             }
931 
932             if (tx_burst_is_late()) {
933                 statsCurrent.missed++;
934             }
935         }
936 
937         input_state = new_state;
938     }
939     return trigger_count;
940 }
941 
942 // this is the actual "work" function. All the fun happens here
run_test(float threshold)943 void Responder::run_test(float threshold)
944 {
945     STATS statsCurrent; //, statsPrev;
946     memset(&statsCurrent, 0x00, sizeof(STATS));
947     if (_opt.test_iterations > 0) {
948         update_and_print_parameters(statsCurrent, _delay);
949         statsCurrent.delay = _opt.delay_min;
950     }
951     ///////////////////////////////////////////////////////////////////////////
952     uint64_t trigger_count        = 0;
953     size_t success_count          = 0;
954     uint64_t num_total_samps_test = 0;
955 
956     std::vector<std::complex<float>> buff(_opt.samps_per_buff);
957     while (not s_stop_signal_called && _return_code == RETCODE_OK) {
958         // get samples from rx stream.
959         uhd::rx_metadata_t rx_md;
960         size_t num_rx_samps =
961             _rx_stream->recv(&buff.front(), buff.size(), rx_md, _opt.timeout);
962         // handle errors
963         if (handle_rx_errors(rx_md.error_code, num_rx_samps)) {
964             break;
965         }
966         // detect falling edges, send respond pulse and check if response could be sent in
967         // time
968         trigger_count = detect_respond_pulse_count(
969             statsCurrent, buff, trigger_count, num_rx_samps, threshold, rx_md.time_spec);
970 
971         // increase counters for single test and overall test samples count.
972         _num_total_samps += num_rx_samps;
973         num_total_samps_test += num_rx_samps;
974 
975         // control section for interactive mode
976         if (_opt.test_iterations == 0) // == "interactive'
977         {
978             if (handle_interactive_control())
979                 break;
980         }
981 
982         // control section for test mode
983         if (_opt.test_iterations > 0) // == test mode / batch-mode
984         {
985             int step_return = test_step_finished(
986                 trigger_count, num_total_samps_test, statsCurrent, success_count);
987             if (step_return == -2) // == test is finished with all desired delay steps
988                 break;
989             else if (step_return == -1) // just continue test
990                 continue;
991             else // test with one delay is finished
992             {
993                 success_count        = (size_t)step_return;
994                 trigger_count        = 0;
995                 num_total_samps_test = 0;
996                 memset(&statsCurrent,
997                     0x00,
998                     sizeof(STATS)); // reset current stats for next test iteration
999                 statsCurrent.delay = _delay;
1000             }
1001         } // end test mode control section
1002     } // exit outer loop after stop signal is called, test is finished or other break
1003       // condition is met
1004 
1005     if (s_stop_signal_called)
1006         _return_code = RETCODE_MANUAL_ABORT;
1007 }
1008 
1009 // check if test with one specific delay is finished
test_step_finished(uint64_t trigger_count,uint64_t num_total_samps_test,STATS statsCurrent,size_t success_count)1010 int Responder::test_step_finished(uint64_t trigger_count,
1011     uint64_t num_total_samps_test,
1012     STATS statsCurrent,
1013     size_t success_count)
1014 {
1015     if (((_opt.test_iterations_is_sample_count == false)
1016             && (trigger_count >= _opt.test_iterations))
1017         || ((_opt.test_iterations_is_sample_count)
1018                && (num_total_samps_test > _opt.test_iterations))) {
1019         add_stats_to_results(statsCurrent, _delay);
1020 
1021         if (statsCurrent.missed == 0) // == NO late bursts
1022             ++success_count;
1023         else
1024             success_count = 0;
1025 
1026         if (test_finished(success_count))
1027             return -2; // test is completely finished
1028 
1029         _delay += _delay_step; // increase delay by one step
1030 
1031         update_and_print_parameters(statsCurrent, _delay);
1032         return success_count; // test is finished for one delay step
1033     }
1034     return -1; // == continue test
1035 }
1036 
1037 // save test results
add_stats_to_results(STATS statsCurrent,double delay)1038 void Responder::add_stats_to_results(STATS statsCurrent, double delay)
1039 {
1040     _max_success   = max(_max_success,
1041         (statsCurrent.detected - statsCurrent.missed)); // > 0 --> save results
1042     uint64_t key   = (uint64_t)(delay * 1e6);
1043     _mapStats[key] = statsCurrent;
1044 }
1045 
1046 // run tests and handle errors
run()1047 int Responder::run()
1048 {
1049     if (_return_code != RETCODE_OK)
1050         return _return_code;
1051     if (_opt.pause)
1052         print_msg_and_wait("Press any key to begin...");
1053     time(&_dbginfo.start_time_test);
1054 
1055     // Put some info about the test on the console
1056     print_init_test_status();
1057     try {
1058         // setup streaming
1059         _stream_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS;
1060         _stream_cmd.stream_now  = true;
1061         _usrp->issue_stream_cmd(_stream_cmd);
1062 
1063         if (!_opt.batch_mode) {
1064             float threshold = calibrate_usrp_for_test_run();
1065             if (_return_code != RETCODE_OK) {
1066                 return _return_code;
1067             }
1068             run_test(threshold);
1069         } else {
1070             run_test();
1071         }
1072     } catch (const std::runtime_error& e) {
1073         print_msg(e.what());
1074         _return_code = RETCODE_RUNTIME_ERROR;
1075     } catch (...) {
1076         print_msg("Unhandled exception");
1077         _return_code = RETCODE_UNKNOWN_EXCEPTION;
1078     }
1079 
1080     stop_usrp_stream();
1081     time(&_dbginfo.end_time_test);
1082     return (_return_code < 0 ? _return_code : _overruns);
1083 }
1084 
1085 /*
1086  *  Following functions are intended to be used by destructor only!
1087  */
1088 
1089 // This method should print statistics after ncurses endwin.
print_final_statistics()1090 void Responder::print_final_statistics()
1091 {
1092     cout << boost::format("Received %ld samples during test run") % _num_total_samps;
1093     if (_overruns > 0)
1094         cout << boost::format(" (%d overruns)") % _overruns;
1095     cout << endl;
1096 }
1097 
1098 // safe test results to a log file if enabled
write_log_file()1099 void Responder::write_log_file()
1100 {
1101     try {
1102         if (_opt.log_file) {
1103             std::map<std::string, std::string> hw_info = get_hw_info();
1104             ofstream logs(_stats_log_filename.c_str());
1105 
1106             logs << boost::format("title=%s") % _opt.test_title << endl;
1107             logs << boost::format("device=%s") % _usrp->get_mboard_name() << endl;
1108             logs << boost::format("device_args=%s") % _opt.device_args << endl;
1109             logs << boost::format("type=%s") % hw_info["type"] << endl;
1110             if (hw_info.size() > 0) {
1111                 logs << boost::format("usrp_addr=%s") % hw_info["usrp_addr"] << endl;
1112                 logs << boost::format("usrp_name=%s") % hw_info["name"] << endl;
1113                 logs << boost::format("serial=%s") % hw_info["serial"] << endl;
1114                 logs << boost::format("host_interface=%s") % hw_info["interface"] << endl;
1115                 logs << boost::format("host_addr=%s") % hw_info["host_addr"] << endl;
1116                 logs << boost::format("host_mac=%s") % hw_info["mac"] << endl;
1117                 logs << boost::format("host_vendor=%s (id=%s)") % hw_info["vendor"]
1118                             % hw_info["vendor_id"]
1119                      << endl;
1120                 logs << boost::format("host_device=%s (id=%s)") % hw_info["device"]
1121                             % hw_info["device_id"]
1122                      << endl;
1123             }
1124             logs << boost::format("sample_rate=%f") % _opt.sample_rate << endl;
1125             logs << boost::format("samps_per_buff=%i") % _opt.samps_per_buff << endl;
1126             logs << boost::format("samps_per_packet=%i") % _samps_per_packet << endl;
1127             logs << boost::format("delay_min=%f") % _opt.delay_min << endl;
1128             logs << boost::format("delay_max=%f") % _opt.delay_max << endl;
1129             logs << boost::format("delay_step=%f") % _delay_step << endl;
1130             logs << boost::format("delay=%f") % _delay << endl;
1131             logs << boost::format("init_delay=%f") % _opt.init_delay << endl;
1132             logs << boost::format("response_duration=%f") % _opt.response_duration
1133                  << endl;
1134             logs << boost::format("response_length=%ld") % _response_length << endl;
1135             logs << boost::format("timeout=%f") % _opt.timeout << endl;
1136             logs << boost::format("timeout_burst_count=%ld") % _timeout_burst_count
1137                  << endl;
1138             logs << boost::format("timeout_eob_count=%f") % _timeout_eob_count << endl;
1139             logs << boost::format("allow_late_bursts=%s")
1140                         % (_allow_late_bursts ? "yes" : "no")
1141                  << endl;
1142             logs << boost::format("skip_eob=%s") % (_opt.skip_eob ? "yes" : "no") << endl;
1143             logs << boost::format("combine_eob=%s") % (_opt.combine_eob ? "yes" : "no")
1144                  << endl;
1145             logs << boost::format("skip_send=%s") % (_opt.skip_send ? "yes" : "no")
1146                  << endl;
1147             logs << boost::format("no_delay=%s") % (_no_delay ? "yes" : "no") << endl;
1148             logs << boost::format("simulate_frequency=%f") % _simulate_frequency << endl;
1149             logs << boost::format("simulate_duration=%ld") % _simulate_duration << endl;
1150             logs << boost::format("original_simulate_duration=%ld")
1151                         % _original_simulate_duration
1152                  << endl;
1153             logs << boost::format("realtime=%s") % (_opt.realtime ? "yes" : "no") << endl;
1154             logs << boost::format("rt_priority=%f") % _opt.rt_priority << endl;
1155             logs << boost::format("test_iterations=%ld") % _opt.test_iterations << endl;
1156             logs << boost::format("end_test_after_success_count=%i")
1157                         % _opt.end_test_after_success_count
1158                  << endl;
1159             logs << boost::format("skip_iterations=%i") % _opt.skip_iterations << endl;
1160             logs << boost::format("overruns=%i") % _overruns << endl;
1161             logs << boost::format("num_total_samps=%ld") % _num_total_samps << endl;
1162             logs << boost::format("return_code=%i\t(%s)") % _return_code
1163                         % enum2str(_return_code)
1164                  << endl;
1165             logs << endl;
1166 
1167             write_debug_info(logs);
1168         }
1169     } catch (...) {
1170         cerr << "Failed to write log file to: " << _stats_log_filename << endl;
1171     }
1172 }
1173 
1174 // write debug info to log file
write_debug_info(ofstream & logs)1175 void Responder::write_debug_info(ofstream& logs)
1176 {
1177     logs << endl << "%% DEBUG INFO %%" << endl;
1178 
1179     logs << boost::format("dbg_time_start=%s") % get_gmtime_string(_dbginfo.start_time)
1180          << endl;
1181     logs << boost::format("dbg_time_end=%s") % get_gmtime_string(_dbginfo.end_time)
1182          << endl;
1183     logs << boost::format("dbg_time_duration=%d")
1184                 % difftime(_dbginfo.end_time, _dbginfo.start_time)
1185          << endl;
1186     logs << boost::format("dbg_time_start_test=%s")
1187                 % get_gmtime_string(_dbginfo.start_time_test)
1188          << endl;
1189     logs << boost::format("dbg_time_end_test=%s")
1190                 % get_gmtime_string(_dbginfo.end_time_test)
1191          << endl;
1192     logs << boost::format("dbg_time_duration_test=%d")
1193                 % difftime(_dbginfo.end_time_test, _dbginfo.start_time_test)
1194          << endl;
1195     logs << boost::format("dbg_time_first_send_timeout=%s")
1196                 % get_gmtime_string(_dbginfo.first_send_timeout)
1197          << endl;
1198 }
1199 
1200 // convert a time string to desired format
get_gmtime_string(time_t time)1201 std::string Responder::get_gmtime_string(time_t time)
1202 {
1203     tm* ftm;
1204     ftm = gmtime(&time);
1205     std::string strtime;
1206     strtime.append((boost::format("%i") % (ftm->tm_year + 1900)).str());
1207     strtime.append((boost::format("-%02i") % ftm->tm_mon).str());
1208     strtime.append((boost::format("-%02i") % ftm->tm_mday).str());
1209     strtime.append((boost::format("-%02i") % ((ftm->tm_hour))).str());
1210     strtime.append((boost::format(":%02i") % ftm->tm_min).str());
1211     strtime.append((boost::format(":%02i") % ftm->tm_sec).str());
1212 
1213     return strtime;
1214 }
1215 
1216 // read hardware info from file if available to include it in log file
get_hw_info()1217 std::map<std::string, std::string> Responder::get_hw_info()
1218 {
1219     std::map<std::string, std::string> result;
1220     std::vector<std::map<std::string, std::string>> eths = read_eth_info();
1221     if (eths.empty()) {
1222         return result;
1223     }
1224     uhd::device_addr_t usrp_info = get_usrp_info();
1225     std::string uaddr            = get_ip_subnet_addr(usrp_info["addr"]);
1226 
1227     for (unsigned int i = 0; i < eths.size(); i++) {
1228         if (get_ip_subnet_addr(eths[i]["addr"]) == uaddr) {
1229             result["type"]      = usrp_info["type"];
1230             result["usrp_addr"] = usrp_info["addr"];
1231             result["name"]      = usrp_info["name"];
1232             result["serial"]    = usrp_info["serial"];
1233             result["interface"] = eths[i]["interface"];
1234             result["host_addr"] = eths[i]["addr"];
1235             result["mac"]       = eths[i]["mac"];
1236             result["vendor"]    = eths[i]["vendor"];
1237             result["vendor_id"] = eths[i]["vendor_id"];
1238             result["device"]    = eths[i]["device"];
1239             result["device_id"] = eths[i]["device_id"];
1240             break; // Use first item found. Imitate device discovery.
1241         }
1242     }
1243 
1244     return result;
1245 }
1246 
1247 // subnet used to identify used network interface
get_ip_subnet_addr(std::string ip)1248 std::string Responder::get_ip_subnet_addr(std::string ip)
1249 {
1250     return ip.substr(0, ip.rfind(".") + 1);
1251 }
1252 
1253 // get network interface info from file (should include all available interfaces)
read_eth_info()1254 std::vector<std::map<std::string, std::string>> Responder::read_eth_info()
1255 {
1256     const std::string eth_file(_eth_file);
1257 
1258     std::vector<std::map<std::string, std::string>> eths;
1259     try {
1260         ifstream eth_info(eth_file.c_str());
1261         if (!eth_info.is_open()) {
1262             return eths;
1263         }
1264         const int len = 256;
1265         char cline[len];
1266         for (; !eth_info.eof();) {
1267             eth_info.getline(cline, len);
1268             std::string line(cline);
1269             if (line.find("## ETH Interface") != std::string::npos) {
1270                 eth_info.getline(cline, len);
1271                 std::string eth(cline);
1272                 //                cout << "interface=" << eth << endl;
1273                 std::map<std::string, std::string> iface;
1274                 iface["interface"] = eth;
1275                 eths.push_back(iface);
1276             }
1277             const std::string ipstr("\tip ");
1278             if (line.find(ipstr) != std::string::npos) {
1279                 std::string ip(
1280                     line.replace(line.begin(), line.begin() + ipstr.length(), ""));
1281                 //                cout << "ip=" << ip << endl;
1282                 eths.back()["addr"] = ip;
1283             }
1284             const std::string macstr("\tmac ");
1285             if (line.find(macstr) != std::string::npos) {
1286                 std::string mac(
1287                     line.replace(line.begin(), line.begin() + macstr.length(), ""));
1288                 //                cout << "mac=" << mac << endl;
1289                 eths.back()["mac"] = mac;
1290             }
1291             const std::string vstr("\t\tvendor ");
1292             if (line.find(vstr) != std::string::npos) {
1293                 std::string vendor(
1294                     line.replace(line.begin(), line.begin() + vstr.length(), ""));
1295                 std::string vid(vendor.substr(0, 6));
1296                 vendor.replace(0, 7, "");
1297                 //                cout << "id=" << vid << endl;
1298                 //                cout << "vendor=" << vendor << endl;
1299                 eths.back()["vendor"]    = vendor;
1300                 eths.back()["vendor_id"] = vid;
1301             }
1302             const std::string dstr("\t\tdevice ");
1303             if (line.find(dstr) != std::string::npos) {
1304                 std::string device(
1305                     line.replace(line.begin(), line.begin() + dstr.length(), ""));
1306                 std::string did(device.substr(0, 6));
1307                 device.replace(0, 7, "");
1308                 //                cout << "id=" << did << endl;
1309                 //                cout << "device=" << device << endl;
1310                 eths.back()["device"]    = device;
1311                 eths.back()["device_id"] = did;
1312             }
1313         }
1314 
1315     } catch (...) {
1316         // nothing in yet
1317     }
1318     return eths;
1319 }
1320 
1321 // get info on used USRP
get_usrp_info()1322 uhd::device_addr_t Responder::get_usrp_info()
1323 {
1324     uhd::device_addrs_t device_addrs = uhd::device::find(_opt.device_args);
1325     uhd::device_addr_t device_addr   = device_addrs[0];
1326     return device_addr;
1327 }
1328 
1329 // write statistics of test run to file
write_statistics_to_file(StatsMap mapStats)1330 void Responder::write_statistics_to_file(StatsMap mapStats)
1331 {
1332     try {
1333         ofstream results(_stats_filename.c_str());
1334 
1335         for (StatsMap::iterator it = mapStats.begin(); it != mapStats.end(); ++it) {
1336             STATS& stats = it->second;
1337             double d     = 0.0;
1338             if (stats.detected > 0)
1339                 d = 1.0 - ((double)stats.missed / (double)stats.detected);
1340             cout << "\t" << setprecision(6) << stats.delay << "\t\t" << setprecision(6)
1341                  << d << endl;
1342 
1343             results << (stats.delay * _opt.time_mul) << " " << setprecision(6) << d
1344                     << endl;
1345         }
1346         cout << "Statistics written to: " << _stats_filename << endl;
1347 
1348     } catch (...) {
1349         cout << "Failed to write statistics to: " << _stats_filename << endl;
1350     }
1351 }
1352 
1353 // make sure write files is intended
safe_write_statistics_to_file(StatsMap mapStats,uint64_t max_success,int return_code)1354 void Responder::safe_write_statistics_to_file(
1355     StatsMap mapStats, uint64_t max_success, int return_code)
1356 {
1357     if ((_opt.test_iterations > 0) && (_stats_filename.empty() == false)
1358         && (_opt.no_stats_file == false)) {
1359         if (mapStats.empty()) {
1360             cout << "No results to output (not writing statistics file)" << endl;
1361         } else if ((max_success == 0) && (return_code == RETCODE_MANUAL_ABORT)) {
1362             cout << "Aborted before a single successful timed burst (not writing "
1363                     "statistics file)"
1364                  << endl;
1365         } else {
1366             write_statistics_to_file(mapStats);
1367         }
1368         write_log_file();
1369     }
1370 }
1371 
1372 // destructor, handle proper test shutdown
~Responder()1373 Responder::~Responder()
1374 {
1375     endwin();
1376     if (_pResponse != NULL) {
1377         delete[] _pResponse;
1378     }
1379     time(&_dbginfo.end_time);
1380     // Print final info about test run
1381     print_final_statistics();
1382     // check conditions and write statistics to file
1383     safe_write_statistics_to_file(_mapStats, _max_success, _return_code);
1384     cout << "program exited with code = " << enum2str(_return_code) << endl;
1385 }
1386 
1387 // make test output more helpful
enum2str(int return_code)1388 std::string Responder::enum2str(int return_code)
1389 {
1390     switch (return_code) {
1391         case RETCODE_OK:
1392             return "OK";
1393         case RETCODE_BAD_ARGS:
1394             return "BAD_ARGS";
1395         case RETCODE_RUNTIME_ERROR:
1396             return "RUNTIME_ERROR";
1397         case RETCODE_UNKNOWN_EXCEPTION:
1398             return "UNKNOWN_EXCEPTION";
1399         case RETCODE_RECEIVE_TIMEOUT:
1400             return "RECEIVE_TIMEOUT";
1401         case RETCODE_RECEIVE_FAILED:
1402             return "RECEIVE_FAILED";
1403         case RETCODE_MANUAL_ABORT:
1404             return "MANUAL_ABORT";
1405         case RETCODE_BAD_PACKET:
1406             return "BAD_PACKET";
1407         case RETCODE_OVERFLOW:
1408             return "OVERFLOW";
1409     }
1410     return "UNKNOWN";
1411 }
1412