Update BWE_TEST_LOGGING_PLOT output format, and fix plot_dynamics.py script.

The new format for plot lines is PLOT <plot_no> <var_name>:<ssrc>@<alg_name> <time> <value>. The var_name is no longer prefixed by the context/tag (which most of the time was just the same as the test name.)

Update plot_dynamics.py script (which didn't work) to visualize the new BWE_TEST_LOGGING_PLOT lines.

BUG=webrtc:6621

Review-Url: https://codereview.webrtc.org/2456373002
Cr-Commit-Position: refs/heads/master@{#14983}
This commit is contained in:
terelius 2016-11-08 09:51:58 -08:00 committed by Commit bot
parent 579729dd9e
commit f94ba462bc
8 changed files with 211 additions and 280 deletions

View File

@ -217,42 +217,47 @@ uint32_t PacketProcessor::bits_per_second() const {
RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
int flow_id,
const char* name,
const std::string& plot_name)
const std::string& algorithm_name)
: PacketProcessor(listener, flow_id, kRegular),
packets_per_second_stats_(),
kbps_stats_(),
start_plotting_time_ms_(0),
plot_name_(plot_name) {
std::stringstream ss;
ss << name << "_" << flow_id;
name_ = ss.str();
}
flow_id_(flow_id),
name_(name),
algorithm_name_(algorithm_name) {
// Only used when compiling with BWE test logging enabled.
RTC_UNUSED(flow_id_);
}
RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
const std::string& plot_name)
const std::string& algorithm_name)
: PacketProcessor(listener, flow_ids, kRegular),
packets_per_second_stats_(),
kbps_stats_(),
start_plotting_time_ms_(0),
plot_name_(plot_name) {
flow_id_(0),
name_(name),
algorithm_name_(algorithm_name) {
// TODO(terelius): Appending the flow IDs to the algorithm name is a hack to
// keep the current plot functionality without having to print the full
// context for each PLOT line. It is unclear whether multiple flow IDs are
// needed at all in the long term.
std::stringstream ss;
ss << name;
char delimiter = '_';
ss << algorithm_name_;
for (int flow_id : flow_ids) {
ss << delimiter << flow_id;
delimiter = ',';
ss << ',' << flow_id;
}
name_ = ss.str();
algorithm_name_ = ss.str();
}
RateCounterFilter::RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
int64_t start_plotting_time_ms,
const std::string& plot_name)
: RateCounterFilter(listener, flow_ids, name, plot_name) {
const std::string& algorithm_name)
: RateCounterFilter(listener, flow_ids, name, algorithm_name) {
start_plotting_time_ms_ = start_plotting_time_ms;
}
@ -277,11 +282,13 @@ void RateCounterFilter::Plot(int64_t timestamp_ms) {
plot_kbps = rate_counter_.bits_per_second() / 1000.0;
}
BWE_TEST_LOGGING_CONTEXT(name_.c_str());
if (plot_name_.empty()) {
BWE_TEST_LOGGING_PLOT(0, "Throughput_kbps#1", timestamp_ms, plot_kbps);
if (algorithm_name_.empty()) {
BWE_TEST_LOGGING_PLOT_WITH_SSRC(0, "Throughput_kbps#1", timestamp_ms,
plot_kbps, flow_id_);
} else {
BWE_TEST_LOGGING_PLOT_WITH_NAME(0, "Throughput_kbps#1", timestamp_ms,
plot_kbps, plot_name_);
BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(0, "Throughput_kbps#1",
timestamp_ms, plot_kbps, flow_id_,
algorithm_name_);
}
RTC_UNUSED(plot_kbps);

View File

@ -230,16 +230,16 @@ class RateCounterFilter : public PacketProcessor {
RateCounterFilter(PacketProcessorListener* listener,
int flow_id,
const char* name,
const std::string& plot_name);
const std::string& algorithm_name);
RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
const std::string& plot_name);
const std::string& algorithm_name);
RateCounterFilter(PacketProcessorListener* listener,
const FlowIds& flow_ids,
const char* name,
int64_t start_plotting_time_ms,
const std::string& plot_name);
const std::string& algorithm_name);
virtual ~RateCounterFilter();
void LogStats();
@ -250,10 +250,11 @@ class RateCounterFilter : public PacketProcessor {
private:
Stats<double> packets_per_second_stats_;
Stats<double> kbps_stats_;
std::string name_;
int64_t start_plotting_time_ms_;
int flow_id_;
std::string name_;
// Algorithm name if single flow, Total link utilization if all flows.
std::string plot_name_;
std::string algorithm_name_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RateCounterFilter);
};

View File

@ -18,6 +18,7 @@
#include <algorithm>
#include <sstream>
#include "webrtc/base/format_macros.h"
#include "webrtc/base/platform_thread.h"
#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
@ -90,19 +91,26 @@ void Logging::Log(const char format[], ...) {
}
}
void Logging::Plot(int figure, double value) {
Plot(figure, value, 0, "-");
}
void Logging::Plot(int figure, double value, uint32_t ssrc) {
Plot(figure, value, ssrc, "-");
}
void Logging::Plot(int figure, double value, const std::string& alg_name) {
Plot(figure, value, 0, alg_name);
void Logging::Plot(int figure, const std::string& name, double value) {
Plot(figure, name, value, 0, "-");
}
void Logging::Plot(int figure,
const std::string& name,
double value,
uint32_t ssrc) {
Plot(figure, name, value, ssrc, "-");
}
void Logging::Plot(int figure,
const std::string& name,
double value,
const std::string& alg_name) {
Plot(figure, name, value, 0, alg_name);
}
void Logging::Plot(int figure,
const std::string& name,
double value,
uint32_t ssrc,
const std::string& alg_name) {
@ -110,20 +118,9 @@ void Logging::Plot(int figure,
ThreadMap::iterator it = thread_map_.find(rtc::CurrentThreadId());
assert(it != thread_map_.end());
const State& state = it->second.stack.top();
std::stringstream ss;
ss << ssrc;
std::string label = state.tag + ':' + ss.str() + '@' + alg_name;
std::string prefix("Available");
if (alg_name.compare(0, prefix.length(), prefix) == 0) {
std::string receiver("Receiver");
size_t start_pos = label.find(receiver);
if (start_pos != std::string::npos) {
label.replace(start_pos, receiver.length(), "Sender");
}
}
if (state.enabled) {
printf("PLOT\t%d\t%s\t%f\t%f\n", figure, label.c_str(),
state.timestamp_ms * 0.001, value);
printf("PLOT\t%d\t%s:%" PRIu32 "@%s\t%f\t%f\n", figure, name.c_str(), ssrc,
alg_name.c_str(), state.timestamp_ms * 0.001, value);
}
}

View File

@ -183,26 +183,27 @@
_4, _5); \
} while (0)
#define BWE_TEST_LOGGING_PLOT(figure, name, time, value) \
do { \
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
static_cast<int64_t>(time), true); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value); \
#define BWE_TEST_LOGGING_PLOT(figure, name, time, value) \
do { \
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
static_cast<int64_t>(time), true); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value); \
} while (0)
#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name) \
do { \
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
static_cast<int64_t>(time), true); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value, \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
alg_name); \
} while (0)
#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc) \
do { \
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
static_cast<int64_t>(time), true); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value, ssrc); \
#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc) \
do { \
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
static_cast<int64_t>(time), true); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
ssrc); \
} while (0)
#define BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(figure, name, time, value, \
@ -210,8 +211,8 @@
do { \
__BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name, \
static_cast<int64_t>(time), true); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, value, ssrc, \
alg_name); \
webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
ssrc, alg_name); \
} while (0)
#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id) \
@ -279,10 +280,14 @@ class Logging {
void SetGlobalEnable(bool enabled);
void Log(const char format[], ...);
void Plot(int figure, double value);
void Plot(int figure, double value, const std::string& alg_name);
void Plot(int figure, double value, uint32_t ssrc);
void Plot(int figure, const std::string& name, double value);
void Plot(int figure,
const std::string& name,
double value,
const std::string& alg_name);
void Plot(int figure, const std::string& name, double value, uint32_t ssrc);
void Plot(int figure,
const std::string& name,
double value,
uint32_t ssrc,
const std::string& alg_name);

View File

@ -124,13 +124,13 @@ void MetricRecorder::PlotAllDynamics() {
void MetricRecorder::PlotDynamics(int metric) {
if (metric == kTotalAvailable) {
BWE_TEST_LOGGING_PLOT_WITH_NAME(
BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(
0, plot_information_[kTotalAvailable].prefix, now_ms_,
GetTotalAvailableKbps(), "Available");
GetTotalAvailableKbps(), flow_id_, "Available");
} else if (metric == kAvailablePerFlow) {
BWE_TEST_LOGGING_PLOT_WITH_NAME(
BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(
0, plot_information_[kAvailablePerFlow].prefix, now_ms_,
GetAvailablePerFlowKbps(), "Available_per_flow");
GetAvailablePerFlowKbps(), flow_id_, "Available_per_flow");
} else {
PlotLine(metric, plot_information_[metric].prefix,
plot_information_[metric].time_ms,
@ -144,8 +144,9 @@ void MetricRecorder::PlotLine(int windows_id,
const std::string& prefix,
int64_t time_ms,
T y) {
BWE_TEST_LOGGING_PLOT_WITH_NAME(windows_id, prefix, time_ms,
static_cast<double>(y), algorithm_name_);
BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(windows_id, prefix, time_ms,
static_cast<double>(y), flow_id_,
algorithm_name_);
}
void MetricRecorder::UpdateTimeMs(int64_t time_ms) {
@ -355,6 +356,8 @@ void MetricRecorder::PlotZero() {
for (int i = kThroughput; i <= kLoss; ++i) {
if (plot_information_[i].plot) {
std::stringstream prefix;
// TODO(terelius): Since this does not use the BWE_TEST_LOGGING macros,
// it hasn't been kept up to date with the plot format. Remove or fix?
prefix << "Receiver_" << flow_id_ << "_" + plot_information_[i].prefix;
PlotLine(i, prefix.str(), now_ms_, 0);
plot_information_[i].last_plot_ms = now_ms_;

View File

@ -44,15 +44,15 @@ PacketReceiver::PacketReceiver(PacketProcessorListener* listener,
// Metric recorder plots them in separated figures,
// alignment will take place with the #1 left axis.
prefixes.push_back("Throughput_kbps#1");
prefixes.push_back("MetricRecorderThroughput_kbps#1");
prefixes.push_back("Sending_Estimate_kbps#1");
prefixes.push_back("Delay_ms_#1");
prefixes.push_back("Packet_Loss_#1");
prefixes.push_back("Objective_function_#1");
// Plot Total/PerFlow Available capacity together with throughputs.
prefixes.push_back("Throughput_kbps#1"); // Total Available.
prefixes.push_back("Throughput_kbps#1"); // Available per flow.
prefixes.push_back("Capacity_kbps#1"); // Total Available.
prefixes.push_back("PerFlowCapacity_kbps#1"); // Available per flow.
bool plot_loss = plot_delay; // Plot loss if delay is plotted.
metric_recorder_->SetPlotInformation(prefixes, plot_delay, plot_loss);

View File

@ -7,11 +7,15 @@
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
# This script is used to plot simulation dynamics.
# Able to plot each flow separately. Other plot boxes can be added,
# currently one for Throughput, one for Latency and one for Packet Loss.
# This script is used to plot simulation dynamics. The expected format is
# PLOT <plot_number> <var_name>:<ssrc>@<alg_name> <time> <value>
# <var_name> may optionally be followed by #<axis_alignment> but it is
# deprecated. <plot_number> is also deprecated.
# Each combination <var_name>:<ssrc>@<alg_name> is stored in it's own time
# series. The main function defines which time series should be displayed and
# whether they should should be displayed in the same or separate windows.
import matplotlib
import matplotlib.pyplot as plt
import numpy
import re
@ -20,144 +24,145 @@ import sys
# Change this to True to save the figure to a file. Look below for details.
save_figure = False
class Variable(object):
def __init__(self, variable):
self._ID = variable[0]
self._xlabel = variable[1]
self._ylabel = variable[2]
self._subplot = variable[3]
self._y_max = variable[4]
class ParsePlotLineException(Exception):
def __init__(self, reason, line):
super(ParsePlotLineException, self).__init__()
self.reason = reason
self.line = line
def parse_plot_line(line):
split_line = line.split()
if len(split_line) != 5:
raise ParsePlotLineException("Expected 5 arguments on line", line)
(plot, _, annotated_var, time, value) = split_line
if plot != "PLOT":
raise ParsePlotLineException("Line does not begin with \"PLOT\\t\"", line)
# The variable name can contain any non-whitespace character except "#:@"
match = re.match(r'([^\s#:@]+)(?:#\d)?:(\d+)@(\S+)', annotated_var)
if match == None:
raise ParsePlotLineException("Could not parse variable name, ssrc and \
algorithm name", annotated_var)
var_name = match.group(1)
ssrc = match.group(2)
alg_name = match.group(3).replace('_', ' ')
return (var_name, ssrc, alg_name, time, value)
def generate_label(var_name, ssrc, ssrc_count, alg_name):
label = var_name
if ssrc_count > 1 or ssrc != "0":
label = label + " flow " + ssrc
if alg_name != "-":
label = label + " " + alg_name
return label
class Figure(object):
def __init__(self, name):
self.name = name
self.subplots = []
def addSubplot(self, var_names, xlabel, ylabel):
self.subplots.append(Subplot(var_names, xlabel, ylabel))
def addSample(self, var_name, ssrc, alg_name, time, value):
for s in self.subplots:
s.addSample(var_name, ssrc, alg_name, time, value)
def plotFigure(self, fig):
n = len(self.subplots)
for i in range(n):
ax = fig.add_subplot(n, 1, i+1)
self.subplots[i].plotSubplot(ax)
class Subplot(object):
def __init__(self, var_names, xlabel, ylabel):
self.xlabel = xlabel
self.ylabel = ylabel
self.var_names = var_names
self.samples = dict()
def getID(self):
return self._ID
def getXLabel(self):
return self._xlabel
def getYLabel(self):
return self._ylabel
def getSubplot(self):
return self._subplot
def getYMax(self):
return self._y_max
def getNumberOfFlows(self):
return len(self.samples)
def addSample(self, line):
groups = re.search(r'_(((\d)+((,(\d)+)*))_(\D+))#\d:(\d)@(\S+)', line)
# Each variable will be plotted in a separated box.
var_name = groups.group(1)
alg_name = groups.group(9)
alg_name = alg_name.replace('_', ' ')
def addSample(self, var_name, ssrc, alg_name, time, value):
if var_name not in self.var_names:
return
if alg_name not in self.samples.keys():
self.samples[alg_name] = {}
if ssrc not in self.samples[alg_name].keys():
self.samples[alg_name][ssrc] = {}
if var_name not in self.samples[alg_name][ssrc].keys():
self.samples[alg_name][ssrc][var_name] = []
if var_name not in self.samples[alg_name].keys():
self.samples[alg_name][var_name] = []
self.samples[alg_name][ssrc][var_name].append((time, value))
sample = re.search(r'(\d+\.\d+)\t([-]?\d+\.\d+)', line)
def plotSubplot(self, ax):
ax.set_xlabel(self.xlabel)
ax.set_ylabel(self.ylabel)
s = (sample.group(1), sample.group(2))
self.samples[alg_name][var_name].append(s)
count = 0
for alg_name in self.samples.keys():
for ssrc in self.samples[alg_name].keys():
for var_name in self.samples[alg_name][ssrc].keys():
x = [sample[0] for sample in self.samples[alg_name][ssrc][var_name]]
y = [sample[1] for sample in self.samples[alg_name][ssrc][var_name]]
x = numpy.array(x)
y = numpy.array(y)
def plotVar(v, ax, show_legend, show_x_label):
if show_x_label:
ax.set_xlabel(v.getXLabel(), fontsize='large')
ax.set_ylabel(v.getYLabel(), fontsize='large')
ssrc_count = len(self.samples[alg_name].keys())
l = generate_label(var_name, ssrc, ssrc_count, alg_name)
plt.plot(x, y, label=l, linewidth=2.0)
count += 1
for alg in v.samples.keys():
plt.grid(True)
if count > 1:
plt.legend(loc='best')
for series in v.samples[alg].keys():
x = [sample[0] for sample in v.samples[alg][series]]
y = [sample[1] for sample in v.samples[alg][series]]
x = numpy.array(x)
y = numpy.array(y)
line = plt.plot(x, y, label=alg, linewidth=4.0)
colormap = {'Available0':'#AAAAAA',
'Available1':'#AAAAAA',
'GCC0':'#80D000',
'GCC1':'#008000',
'GCC2':'#00F000',
'GCC3':'#00B000',
'GCC4':'#70B020',
'NADA0':'#0000AA',
'NADA1':'#A0A0FF',
'NADA2':'#0000FF',
'NADA3':'#C0A0FF',
'NADA4':'#9060B0',}
flow_id = re.search(r'(\d+(,\d+)*)', series) # One or multiple ids.
key = alg + flow_id.group(1)
if key in colormap:
plt.setp(line, color=colormap[key])
elif alg == 'TCP':
plt.setp(line, color='#AAAAAA')
else:
plt.setp(line, color='#654321')
if alg.startswith('Available'):
plt.setp(line, linestyle='--')
plt.grid(True)
# x1, x2, y1, y2
_, x2, _, y2 = plt.axis()
if v.getYMax() >= 0:
y2 = v.getYMax()
plt.axis((0, x2, 0, y2))
if show_legend:
plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.40),
shadow=True, fontsize='large', ncol=len(v.samples))
def main():
variables = [
('Throughput_kbps', "Time (s)", "Throughput (kbps)", 1, 4000),
('Delay_ms', "Time (s)", "One-way Delay (ms)", 2, 500),
('Packet_Loss', "Time (s)", "Packet Loss Ratio", 3, 1.0),
]
receiver = Figure("PacketReceiver")
receiver.addSubplot(['Throughput_kbps', 'MaxThroughput_', 'Capacity_kbps',
'PerFlowCapacity_kbps', 'MetricRecorderThroughput_kbps'],
"Time (s)", "Throughput (kbps)")
receiver.addSubplot(['Delay_ms_', 'Delay_ms'], "Time (s)",
"One-way delay (ms)")
receiver.addSubplot(['Packet_Loss_'], "Time (s)", "Packet Loss Ratio")
var = []
kalman_state = Figure("KalmanState")
kalman_state.addSubplot(['kc', 'km'], "Time (s)", "Kalman gain")
kalman_state.addSubplot(['slope_1/bps'], "Time (s)", "Slope")
kalman_state.addSubplot(['var_noise'], "Time (s)", "Var noise")
# Create objects.
for variable in variables:
var.append(Variable(variable))
detector_state = Figure("DetectorState")
detector_state.addSubplot(['offset_ms'], "Time (s)", "Offset")
detector_state.addSubplot(['gamma_ms'], "Time (s)", "Gamma")
# Add samples to the objects.
# Select which figures to plot here.
figures = [receiver, detector_state]
# Add samples to the figures.
for line in sys.stdin:
if line.startswith("[ RUN ]"):
test_name = re.search(r'\.(\w+)', line).group(1)
if line.startswith("PLOT"):
for v in var:
if v.getID() in line:
v.addSample(line)
try:
(var_name, ssrc, alg_name, time, value) = parse_plot_line(line)
for f in figures:
# The sample will be ignored bv the figures that don't need it.
f.addSample(var_name, ssrc, alg_name, time, value)
except ParsePlotLineException as e:
print e.reason
print e.line
matplotlib.rcParams.update({'font.size': 48/len(variables)})
# Plot variables.
fig = plt.figure()
# Offest and threshold on the same plot.
n = var[-1].getSubplot()
i = 0
for v in var:
ax = fig.add_subplot(n, 1, v.getSubplot())
plotVar(v, ax, i == 0, i == n - 1)
i += 1
if save_figure:
fig.savefig(test_name + ".png")
# Plot figures.
for f in figures:
fig = plt.figure(f.name)
f.plotFigure(fig)
if save_figure:
fig.savefig(test_name + f.name + ".png")
plt.show()
if __name__ == '__main__':

View File

@ -1,87 +0,0 @@
#!/bin/bash
# Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
# To set up in e.g. Eclipse, run a separate shell and pipe the output from the
# test into this script.
#
# In Eclipse, that amounts to creating a Run Configuration which starts
# "/bin/bash" with the arguments "-c [trunk_path]/out/Debug/modules_unittests
# --gtest_filter=*BweTest* | [trunk_path]/webrtc/modules/
# remote_bitrate_estimator/test/plot_dynamics.sh
# This script supports multiple figures (windows), the figure is specified as an
# identifier at the first argument after the PLOT command. Each figure has a
# single y axis and a dual y axis mode. If any line specifies an axis by ending
# with "#<axis number (1 or 2)>" two y axis will be used, the first will be
# assumed to represent bitrate (in kbps) and the second will be assumed to
# represent time deltas (in ms).
log=$(</dev/stdin)
# Plot dynamics.
function gen_gnuplot_input {
colors=(a7001f 0a60c2 b2582b 21a66c d6604d 4393c3 f4a582 92c5de edcbb7 b1c5d0)
plots=$(echo "$log" | grep "^PLOT" | grep "#")
# Each figure corresponds to a separate plot window.
figures=($(echo "$plots" | cut -f 2 | sort | uniq))
for figure in "${figures[@]}" ; do
# Each data set corresponds to a plot line.
data_sets=$(echo "$plots" | grep "^PLOT.$figure" | cut -f 3 | sort | uniq)
# Lines can be scaled on the left (1) or right (2) axis.
linetypes=($(echo "$data_sets" | grep "#" | cut -d '#' -f 2 | \
cut -d '@' -f 1 | uniq))
# Set plot configurations.
echo "reset; "
echo "set terminal wxt $figure size 1440,900 font \"Arial,9\"; "
echo "set xlabel \"Seconds\"; "
if (( "${#linetypes[@]}" > "1" )); then
echo "set ylabel 'Bitrate (kbps)';" # Left side.
echo "set ytics nomirror;"
echo "set y2label 'Time delta (ms)';" # Right side.
echo "set y2tics nomirror;"
else
# Single axis (left side), set its label according to data.
y_label=$(echo "$data_sets" | grep "#" | cut -d '#' -f 1 | \
cut -d ' ' -f 1 | cut -d '/' -f 3 | sed 's/[0-9]/#/g' | \
cut -d '#' -f 3 | head -n 1 | sed 's/_/ /g')
echo "set ylabel \"$y_label\";"
fi
i=0
echo -n "plot "
for set in $data_sets ; do
(( i++ )) && echo -n ","
echo -n "'-' with "
echo -n "linespoints "
echo -n "ps 0.5 "
echo -n "lc rgbcolor \"#${colors[$(($i % 10))]}\" "
if (( "${#linetypes[@]}" > "1" )); then
# Multiple sets can have a same line plot.
linetype=$(echo "$set" | grep "#" | cut -d '#' -f 2 | cut -d '@' -f 1)
if (( "${#linetype}" > "0")); then
echo -n "axes x1y$linetype "
else
# If no line type is specified, but line types are used, we will
# default to scale on the left axis.
echo -n "axes x1y1 "
fi
fi
echo -n "title \"$set\" "
done
echo
for set in $data_sets ; do
echo "$log" | grep "^PLOT.$figure.$set" | cut -f 4,5
echo "e"
done
done
}
gen_gnuplot_input | gnuplot -persist