diff --git a/apps/apputil.cpp b/apps/apputil.cpp index ecce703e8..9e9ab4f2c 100644 --- a/apps/apputil.cpp +++ b/apps/apputil.cpp @@ -351,58 +351,157 @@ string OptionHelpItem(const OptionName& o) // Stats module +template +inline SrtStatData* make_stat(SrtStatCat cat, const string& name, const string& longname, + TYPE CBytePerfMon::*field) +{ + return new SrtStatDataType(cat, name, longname, field); +} + +#define STATX(catsuf, sname, lname, field) s.emplace_back(make_stat(SSC_##catsuf, #sname, #lname, &CBytePerfMon:: field)) +#define STAT(catsuf, sname, field) STATX(catsuf, sname, field, field) + +vector> g_SrtStatsTable; + +struct SrtStatsTableInit +{ + SrtStatsTableInit(vector>& s) + { + STATX(GEN, time, Time, msTimeStamp); + + STAT(WINDOW, flow, pktFlowWindow); + STAT(WINDOW, congestion, pktCongestionWindow); + STAT(WINDOW, flight, pktFlightSize); + + STAT(LINK, rtt, msRTT); + STAT(LINK, bandwidth, mbpsBandwidth); + STAT(LINK, maxBandwidth, mbpsMaxBW); + + STAT(SEND, packets, pktSent); + STAT(SEND, packetsUnique, pktSentUnique); + STAT(SEND, packetsLost, pktSndLoss); + STAT(SEND, packetsDropped, pktSndDrop); + STAT(SEND, packetsRetransmitted, pktRetrans); + STAT(SEND, packetsFilterExtra, pktSndFilterExtra); + STAT(SEND, bytes, byteSent); + STAT(SEND, bytesUnique, byteSentUnique); + STAT(SEND, bytesDropped, byteSndDrop); + STAT(SEND, mbitRate, mbpsSendRate); + STAT(SEND, sendPeriod, usPktSndPeriod); + //STAT(SEND, msAvgResponseTime, msAvgResponseTime); + //STAT(SEND, msMaxResponseTime, msMaxResponseTime); + + STAT(RECV, packets, pktRecv); + STAT(RECV, packetsUnique, pktRecvUnique); + STAT(RECV, packetsLost, pktRcvLoss); + STAT(RECV, packetsDropped, pktRcvDrop); + STAT(RECV, packetsRetransmitted, pktRcvRetrans); + STAT(RECV, packetsBelated, pktRcvBelated); + STAT(RECV, packetsFilterExtra, pktRcvFilterExtra); + STAT(RECV, packetsFilterSupply, pktRcvFilterSupply); + STAT(RECV, packetsFilterLoss, pktRcvFilterLoss); + STAT(RECV, bytes, byteRecv); + STAT(RECV, bytesUnique, byteRecvUnique); + STAT(RECV, bytesLost, byteRcvLoss); + STAT(RECV, bytesDropped, byteRcvDrop); + STAT(RECV, mbitRate, mbpsRecvRate); + + } +} g_SrtStatsTableInit (g_SrtStatsTable); + + +#undef STAT +#undef STATX + +string srt_json_cat_names [] = { + "", + "window", + "link", + "send", + "recv" +}; + class SrtStatsJson : public SrtStatsWriter { + static string keyspec(const string& name) + { + if (name == "") + return ""; + + return R"(")" + name + R"(":)"; + } + public: - string WriteStats(int sid, const CBytePerfMon& mon) override - { + string WriteStats(int sid, const CBytePerfMon& mon) override + { std::ostringstream output; - output << "{"; - output << "\"sid\":" << sid << ","; - output << "\"time\":" << mon.msTimeStamp << ","; - output << "\"window\":{"; - output << "\"flow\":" << mon.pktFlowWindow << ","; - output << "\"congestion\":" << mon.pktCongestionWindow << ","; - output << "\"flight\":" << mon.pktFlightSize; - output << "},"; - output << "\"link\":{"; - output << "\"rtt\":" << mon.msRTT << ","; - output << "\"bandwidth\":" << mon.mbpsBandwidth << ","; - output << "\"maxBandwidth\":" << mon.mbpsMaxBW; - output << "},"; - output << "\"send\":{"; - output << "\"packets\":" << mon.pktSent << ","; - output << "\"packetsUnique\":" << mon.pktSentUnique << ","; - output << "\"packetsLost\":" << mon.pktSndLoss << ","; - output << "\"packetsDropped\":" << mon.pktSndDrop << ","; - output << "\"packetsRetransmitted\":" << mon.pktRetrans << ","; - output << "\"packetsFilterExtra\":" << mon.pktSndFilterExtra << ","; - output << "\"bytes\":" << mon.byteSent << ","; - output << "\"bytesUnique\":" << mon.byteSentUnique << ","; - output << "\"bytesDropped\":" << mon.byteSndDrop << ","; - output << "\"mbitRate\":" << mon.mbpsSendRate; - output << "},"; - output << "\"recv\": {"; - output << "\"packets\":" << mon.pktRecv << ","; - output << "\"packetsUnique\":" << mon.pktRecvUnique << ","; - output << "\"packetsLost\":" << mon.pktRcvLoss << ","; - output << "\"packetsDropped\":" << mon.pktRcvDrop << ","; - output << "\"packetsRetransmitted\":" << mon.pktRcvRetrans << ","; - output << "\"packetsBelated\":" << mon.pktRcvBelated << ","; - output << "\"packetsFilterExtra\":" << mon.pktRcvFilterExtra << ","; - output << "\"packetsFilterSupply\":" << mon.pktRcvFilterSupply << ","; - output << "\"packetsFilterLoss\":" << mon.pktRcvFilterLoss << ","; - output << "\"bytes\":" << mon.byteRecv << ","; - output << "\"bytesUnique\":" << mon.byteRecvUnique << ","; - output << "\"bytesLost\":" << mon.byteRcvLoss << ","; - output << "\"bytesDropped\":" << mon.byteRcvDrop << ","; - output << "\"mbitRate\":" << mon.mbpsRecvRate; - output << "}"; - output << "}" << endl; + static const string qt = R"(")"; + + string pretty_cr, pretty_tab; + if (Option("pretty")) + { + pretty_cr = "\n"; + pretty_tab = "\t"; + } + + SrtStatCat cat = SSC_GEN; + + // Do general manually + output << keyspec(srt_json_cat_names[cat]) << "{" << pretty_cr; + + // SID is displayed manually + output << pretty_tab << keyspec("sid") << sid; + + // Now continue with fields as specified in the table + for (auto& i: g_SrtStatsTable) + { + if (i->category == cat) + { + output << ","; // next item in same cat + output << pretty_cr; + output << pretty_tab; + if (cat != SSC_GEN) + output << pretty_tab; + } + else + { + if (cat != SSC_GEN) + { + // DO NOT close if general category, just + // enter the depth. + output << pretty_cr << pretty_tab << "}"; + } + cat = i->category; + output << ","; + output << pretty_cr; + if (cat != SSC_GEN) + output << pretty_tab; + + output << keyspec(srt_json_cat_names[cat]) << "{" << pretty_cr << pretty_tab; + if (cat != SSC_GEN) + output << pretty_tab; + } + + // Print the current field + output << keyspec(i->name); + output << qt; + i->PrintValue(output, mon); + output << qt; + } + + // Close the previous subcategory + if (cat != SSC_GEN) + { + output << pretty_cr << pretty_tab << "}" << pretty_cr; + } + + // Close the general category entity + output << "}," << pretty_cr << endl; + return output.str(); - } + } - string WriteBandwidth(double mbpsBandwidth) override + string WriteBandwidth(double mbpsBandwidth) override { std::ostringstream output; output << "{\"bandwidth\":" << mbpsBandwidth << '}' << endl; @@ -425,18 +524,19 @@ class SrtStatsCsv : public SrtStatsWriter #define HAS_PUT_TIME #endif std::ostringstream output; + + // Header if (!first_line_printed) { #ifdef HAS_PUT_TIME output << "Timepoint,"; #endif - output << "Time,SocketID,pktFlowWindow,pktCongestionWindow,pktFlightSize,"; - output << "msRTT,mbpsBandwidth,mbpsMaxBW,pktSent,pktSndLoss,pktSndDrop,"; - output << "pktRetrans,byteSent,byteSndDrop,mbpsSendRate,usPktSndPeriod,"; - output << "pktRecv,pktRcvLoss,pktRcvDrop,pktRcvRetrans,pktRcvBelated,"; - output << "byteRecv,byteRcvLoss,byteRcvDrop,mbpsRecvRate,RCVLATENCYms,"; - // Filter stats - output << "pktSndFilterExtra,pktRcvFilterExtra,pktRcvFilterSupply,pktRcvFilterLoss"; + output << "Time,SocketID"; + + for (auto& i: g_SrtStatsTable) + { + output << "," << i->longname; + } output << endl; first_line_printed = true; } @@ -444,7 +544,10 @@ class SrtStatsCsv : public SrtStatsWriter int int_len = sizeof rcv_latency; srt_getsockopt(sid, 0, SRTO_RCVLATENCY, &rcv_latency, &int_len); + // Values + #ifdef HAS_PUT_TIME + // HDR: Timepoint // Follows ISO 8601 auto print_timestamp = [&output]() { using namespace std; @@ -466,37 +569,16 @@ class SrtStatsCsv : public SrtStatsWriter print_timestamp(); #endif // HAS_PUT_TIME - output << mon.msTimeStamp << ","; - output << sid << ","; - output << mon.pktFlowWindow << ","; - output << mon.pktCongestionWindow << ","; - output << mon.pktFlightSize << ","; - output << mon.msRTT << ","; - output << mon.mbpsBandwidth << ","; - output << mon.mbpsMaxBW << ","; - output << mon.pktSent << ","; - output << mon.pktSndLoss << ","; - output << mon.pktSndDrop << ","; - output << mon.pktRetrans << ","; - output << mon.byteSent << ","; - output << mon.byteSndDrop << ","; - output << mon.mbpsSendRate << ","; - output << mon.usPktSndPeriod << ","; - output << mon.pktRecv << ","; - output << mon.pktRcvLoss << ","; - output << mon.pktRcvDrop << ","; - output << mon.pktRcvRetrans << ","; - output << mon.pktRcvBelated << ","; - output << mon.byteRecv << ","; - output << mon.byteRcvLoss << ","; - output << mon.byteRcvDrop << ","; - output << mon.mbpsRecvRate << ","; - output << rcv_latency << ","; - // Filter stats - output << mon.pktSndFilterExtra << ","; - output << mon.pktRcvFilterExtra << ","; - output << mon.pktRcvFilterSupply << ","; - output << mon.pktRcvFilterLoss; //<< ","; + // HDR: Time,SocketID + output << mon.msTimeStamp << "," << sid; + + // HDR: the loop of all values in g_SrtStatsTable + for (auto& i: g_SrtStatsTable) + { + output << ","; + i->PrintValue(output, mon); + } + output << endl; return output.str(); } @@ -555,8 +637,15 @@ shared_ptr SrtStatsWriterFactory(SrtStatsPrintFormat printformat return nullptr; } -SrtStatsPrintFormat ParsePrintFormat(string pf) +SrtStatsPrintFormat ParsePrintFormat(string pf, string& w_extras) { + size_t havecomma = pf.find(','); + if (havecomma != string::npos) + { + w_extras = pf.substr(havecomma+1); + pf = pf.substr(0, havecomma); + } + if (pf == "default") return SRTSTATS_PROFMAT_2COLS; diff --git a/apps/apputil.hpp b/apps/apputil.hpp index 1c8f4a0bb..044a68519 100644 --- a/apps/apputil.hpp +++ b/apps/apputil.hpp @@ -19,6 +19,7 @@ #include #include "netinet_any.h" +#include "utilities.h" #if _WIN32 @@ -314,7 +315,44 @@ enum SrtStatsPrintFormat SRTSTATS_PROFMAT_CSV }; -SrtStatsPrintFormat ParsePrintFormat(std::string pf); +SrtStatsPrintFormat ParsePrintFormat(std::string pf, std::string& w_extras); + +enum SrtStatCat +{ + SSC_GEN, //< General + SSC_WINDOW, // flow/congestion window + SSC_LINK, //< Link data + SSC_SEND, //< Sending + SSC_RECV //< Receiving +}; + +struct SrtStatData +{ + SrtStatCat category; + std::string name; + std::string longname; + + SrtStatData(SrtStatCat cat, std::string n, std::string l): category(cat), name(n), longname(l) {} + + virtual void PrintValue(std::ostream& str, const CBytePerfMon& mon) = 0; +}; + +template +struct SrtStatDataType: public SrtStatData +{ + typedef TYPE CBytePerfMon::*pfield_t; + pfield_t pfield; + + SrtStatDataType(SrtStatCat cat, const std::string& name, const std::string& longname, pfield_t field) + : SrtStatData (cat, name, longname), pfield(field) + { + } + + void PrintValue(std::ostream& str, const CBytePerfMon& mon) override + { + str << mon.*pfield; + } +}; class SrtStatsWriter { @@ -322,8 +360,30 @@ class SrtStatsWriter virtual std::string WriteStats(int sid, const CBytePerfMon& mon) = 0; virtual std::string WriteBandwidth(double mbpsBandwidth) = 0; virtual ~SrtStatsWriter() { }; + + void Option(const std::string& key, const std::string& val) + { + options[key] = val; + } + + bool Option(const std::string& key, std::string* rval = nullptr) + { + const std::string* out = map_getp(options, key); + if (!out) + return false; + + if (rval) + *rval = *out; + return true; + } + +protected: + std::map options; + }; +extern std::vector> g_SrtStatsTable; + std::shared_ptr SrtStatsWriterFactory(SrtStatsPrintFormat printformat); diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index b1ec637e9..4288f351e 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -335,7 +335,8 @@ int parse_args(LiveTransmitConfig &cfg, int argc, char** argv) cfg.stats_report = Option(params, o_statsrep); cfg.stats_out = Option(params, o_statsout); const string pf = Option(params, "default", o_statspf); - cfg.stats_pf = ParsePrintFormat(pf); + string pfext; + cfg.stats_pf = ParsePrintFormat(pf, (pfext)); if (cfg.stats_pf == SRTSTATS_PROFMAT_INVALID) { cfg.stats_pf = SRTSTATS_PROFMAT_2COLS; diff --git a/docs/srt-live-transmit.md b/docs/srt-live-transmit.md index ec29a2f1c..0a2dfb02d 100644 --- a/docs/srt-live-transmit.md +++ b/docs/srt-live-transmit.md @@ -366,7 +366,7 @@ shell (using **"** **"** quotes or backslash). - **-chunk, -c** - use given size of the buffer. The default size is 1456 bytes, which is the maximum payload size for a single SRT packet. - **-verbose, -v** - Display additional information on the standard output. Note that it's not allowed to be combined with output specified as **file://con**. - **-statsout** - SRT statistics output: filename. Without this option specified, the statistics will be printed to the standard output. -- **-pf**, **-statspf** - SRT statistics print format. Values: json, csv, default. +- **-pf**, **-statspf** - SRT statistics print format. Values: json, csv, default. After a comma, options can be specified (e.g. "json,pretty"). - **-s**, **-stats**, **-stats-report-frequency** - The frequency of SRT statistics collection, based on the number of packets. - **-loglevel** - lowest logging level for SRT, one of: *fatal, error, warning, note, debug* (default: *error*) - **-logfa, -lfa** - selected FAs in SRT to be logged (default: all are enabled). See the list of FAs running `-help:logging`. diff --git a/testing/srt-test-live.cpp b/testing/srt-test-live.cpp index daa3efc29..9c66dd30e 100644 --- a/testing/srt-test-live.cpp +++ b/testing/srt-test-live.cpp @@ -687,13 +687,26 @@ int main( int argc, char** argv ) #endif } - SrtStatsPrintFormat statspf = ParsePrintFormat(Option(params, "default", o_statspf)); + string pfextra; + SrtStatsPrintFormat statspf = ParsePrintFormat(Option(params, "default", o_statspf), (pfextra)); if (statspf == SRTSTATS_PROFMAT_INVALID) { cerr << "Invalid stats print format\n"; return 1; } transmit_stats_writer = SrtStatsWriterFactory(statspf); + if (pfextra != "") + { + vector options; + Split(pfextra, ',', back_inserter(options)); + for (auto& i: options) + { + vector klv; + Split(i, '=', back_inserter(klv)); + klv.resize(2); + transmit_stats_writer->Option(klv[0], klv[1]); + } + } // Options that require integer conversion size_t stoptime = Option(params, "0", o_stoptime);