1#!/usr/bin/env python
2
3"""
4Extract iperf data from json blob and format for gnuplot.
5"""
6
7import json
8import os
9import sys
10
11from optparse import OptionParser
12
13import pprint
14# for debugging, so output to stderr to keep verbose
15# output out of any redirected stdout.
16pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
17
18
19def generate_output(iperf, options):
20    """Do the actual formatting."""
21    for i in iperf.get('intervals'):
22        for ii in i.get('streams'):
23            if options.verbose:
24                pp.pprint(ii)
25            row = '{0} {1} {2} {3} {4}\n'.format(
26                round(float(ii.get('start')), 4),
27                ii.get('bytes'),
28                # to Gbits/sec
29                round(float(ii.get('bits_per_second')) / (1000*1000*1000), 3),
30                ii.get('retransmits'),
31                round(float(ii.get('snd_cwnd')) / (1000*1000), 2)
32            )
33            yield row
34
35
36def summed_output(iperf, options):
37    """Format summed output."""
38
39    for i in iperf.get('intervals'):
40
41        row_header = None
42
43        byte = list()
44        bits_per_second = list()
45        retransmits = list()
46        snd_cwnd = list()
47
48        for ii in i.get('streams'):
49            if options.verbose:
50                pp.pprint(i)
51            # grab the first start value
52            if row_header is None:
53                row_header = round(float(ii.get('start')), 2)
54            # aggregate the rest of the values
55            byte.append(ii.get('bytes'))
56            bits_per_second.append(float(ii.get('bits_per_second')) / (1000*1000*1000))
57            retransmits.append(ii.get('retransmits'))
58            snd_cwnd.append(float(ii.get('snd_cwnd')) / (1000*1000))
59
60        row = '{h} {b} {bps} {r} {s}\n'.format(
61            h=row_header,
62            b=sum(byte),
63            bps=round(sum(bits_per_second), 3),
64            r=sum(retransmits),
65            s=round(sum(snd_cwnd) / len(snd_cwnd), 2)
66        )
67
68        yield row
69
70
71def main():
72    """Execute the read and formatting."""
73    usage = '%prog [ -f FILE | -o OUT | -v ]'
74    parser = OptionParser(usage=usage)
75    parser.add_option('-f', '--file', metavar='FILE',
76                      type='string', dest='filename',
77                      help='Input filename.')
78    parser.add_option('-o', '--output', metavar='OUT',
79                      type='string', dest='output',
80                      help='Optional file to append output to.')
81    parser.add_option('-s', '--sum',
82                      dest='summed', action='store_true', default=False,
83                      help='Summed version of the output.')
84    parser.add_option('-v', '--verbose',
85                      dest='verbose', action='store_true', default=False,
86                      help='Verbose debug output to stderr.')
87    options, _ = parser.parse_args()
88
89    if not options.filename:
90        parser.error('Filename is required.')
91
92    file_path = os.path.normpath(options.filename)
93
94    if not os.path.exists(file_path):
95        parser.error('{f} does not exist'.format(f=file_path))
96
97    with open(file_path, 'r') as fh:
98        data = fh.read()
99
100    try:
101        iperf = json.loads(data)
102    except Exception as ex:  # pylint: disable=broad-except
103        parser.error('Could not parse JSON from file (ex): {0}'.format(str(ex)))
104
105    if options.output:
106        absp = os.path.abspath(options.output)
107        output_dir, _ = os.path.split(absp)
108        if not os.path.exists(output_dir):
109            parser.error('Output file directory path {0} does not exist'.format(output_dir))
110        fh = open(absp, 'a')
111    else:
112        fh = sys.stdout
113
114    if options.summed:
115        fmt = summed_output
116    else:
117        fmt = generate_output
118
119    for i in fmt(iperf, options):
120        fh.write(i)
121
122
123if __name__ == '__main__':
124    main()
125