cnl_file_plot.py 12.1 KB
Newer Older
1 2 3
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

Mario Hock's avatar
Mario Hock committed
4
# Copyright (c) 2015,
5 6 7 8 9 10 11 12 13 14
# Karlsruhe Institute of Technology, Institute of Telematics
#
# This code is provided under the BSD 2-Clause License.
# Please refer to the LICENSE.txt file for further information.
#
# Author: Mario Hock

import matplotlib
import matplotlib.pyplot as plt
import os
Mario Hock's avatar
Mario Hock committed
15
import copy
16

17
from cnl_library import CNLParser, calc_ema, merge_lists, pretty_json, get_common_base_time, get_base_times
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
import cnl_plot
import plot_ticks

## Workaround: "pdf-presenter-console" needs this, otherwise no text is displayed at all.
matplotlib.rc('pdf', fonttype=42)



## TODO the following functions should be shared among cnl_plot and this file

def prepare_x_values(cnl_file, plateau=True):
    if ( plateau ):
        cnl_file.x_values = merge_lists( cnl_file.cols["begin"], cnl_file.cols["end"] )
    else:
        cnl_file.x_values = cnl_file.cols["end"]


def net_fields_to_plot(args):

    # send/ receive
    if ( args.send_only ):
        nic_fields = ["send"]
    elif ( args.receive_only ):
        nic_fields = ["receive"]
    else:
        nic_fields = ["send", "receive"]


    # select nics (and optionally name them properly)
    if ( args.nics ):
        if ( args.nic_labels ):
            nics = dict(zip(args.nics, args.nic_labels))
        else:
            nics = dict(zip(args.nics, args.nics))
    else:
        nics = None

    return nics, nic_fields


def set_tick_labels( ax, x_minutes=True, adapt_net_yticks=True ):
    if ( x_minutes ):
        ax.xaxis.set_major_locator( plot_ticks.TimeLocator() )
        ax.xaxis.set_major_formatter( matplotlib.ticker.FuncFormatter(plot_ticks.format_xticks_minutes) )
    if ( adapt_net_yticks ):
        ax.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(plot_ticks.format_yticks))



#######



## NOTE: based on corresponding function in cnl_plot
#
def plot_net(ax, cnl_file, args):
    # parameters
Mario Hock's avatar
Mario Hock committed
75
    legend_outside = False   # TODO make no legend and legend outside possible
76 77 78 79 80
    alpha = args.opacity if args.transparent_net else 1.0
    smooth = args.smooth_net

    # axes
    ax.set_ylim(top=args.net_scale)
Mario Hock's avatar
Mario Hock committed
81
    #ax.set_ylabel('Throughput (Bit/s)', fontsize=layout.fontsize.axis_labels)  ## TODO make fontsize choosable
82 83 84
    ax.set_ylabel('Throughput (Bit/s)')
    ax.set_xlabel('Time (s)')

Mario Hock's avatar
--sum  
Mario Hock committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
    # * plot summarized *
    if ( args.sum or args.sum_only ):
        # summarize
        sum = None
        for col_name in cnl_file.net_col_names:
            print( col_name )

            if not sum:
                sum = cnl_file.cols[col_name]
            else:
                sum = [ x+y for x,y in zip(sum, cnl_file.cols[col_name]) ]

        # (just to be compatible with cnl_plot.plot)
        aux_col_dict = dict()
        aux_col_dict["sum"] = sum

        # * plot *
        cnl_plot.plot(ax, cnl_file.x_values, aux_col_dict, ["sum"], ["Total"], alpha,
Mario Hock's avatar
Mario Hock committed
103 104 105 106 107 108 109 110
            color=args.sum_color, ema_only=True if smooth else False, smooth=smooth)


    ## * plot regular *
    if ( not args.sum_only ):
        # * plot regular *
        cnl_plot.plot(ax, cnl_file.x_values, cnl_file.cols, cnl_file.net_col_names, cnl_file.net_col_labels, alpha,
                  color=args.color, ema_only=True if smooth else False, smooth=smooth)
Mario Hock's avatar
--sum  
Mario Hock committed
111 112 113



114
    # Legend
Mario Hock's avatar
Mario Hock committed
115 116 117 118 119 120 121 122 123 124 125
    if ( args.legend_pos != None ):
        if ( legend_outside ):
            offset = matplotlib.transforms.ScaledTranslation(0, -20, matplotlib.transforms.IdentityTransform())
            trans = ax.transAxes + offset

            l = ax.legend( loc='upper left', bbox_to_anchor=(0, 0), ncol=int(len(cnl_file.net_col_names)/2),
                        bbox_transform = trans,
                        fancybox=False, shadow=False#,
                        #fontsize=layout.fontsize.legend
                        )
        else:
Mario Hock's avatar
Mario Hock committed
126
            l = ax.legend(loc=args.legend_pos, ncol=args.legend_col_number)
127 128 129 130 131 132 133


    ax.set_xlim(xmin=args.x_min)
    ax.set_xlim(xmax=args.x_max)



Mario Hock's avatar
Mario Hock committed
134 135


136 137 138 139 140 141
def plot(subplot_args, base_times=0):
    """
    [base_times] can either be a single nummer,
        this is treated as common base time for all subplots
        or a list
        with the individual base times (same order as args.files)
142

143 144 145 146 147
    """

    fig, ax = plt.subplots()
    i=0
    current_base_time = base_times   # makes sense if a common base_time is used
Mario Hock's avatar
Mario Hock committed
148 149 150 151 152 153 154 155 156 157

    for args in subplot_args:

        nics, nic_fields = net_fields_to_plot(args)

        ## Plot all files (with a common base time)
        #
        for filename in args.files:
            cnl_file = cnl_plot.parse_cnl_file(filename, nic_fields, nics)

158 159 160 161 162
            # if individual base_times are used, get the next one
            if isinstance(base_times, list):
                current_base_time = base_times[i]
                i += 1

Mario Hock's avatar
Mario Hock committed
163 164 165 166 167 168
            ## show some output
            print( filename )
            #print( pretty_json(cnl_file.get_general_header()) )
            #print()

            prepare_x_values(cnl_file)
169
            cnl_file.x_values = [ x - current_base_time for x in cnl_file.x_values ]
Mario Hock's avatar
Mario Hock committed
170 171 172 173 174


            ## * Plot *
            plot_net(ax, cnl_file, args)

175 176 177 178 179 180


    ## Format tick labels
    set_tick_labels(ax, False, True)


Mario Hock's avatar
Mario Hock committed
181 182 183 184 185 186


    ## output ##

    args = subplot_args[0]

187 188 189 190 191
    # Show / hardcopy plot
    if ( args.output == "live" ):
        #plt.title(filename)
        plt.show()
    else:
Mario Hock's avatar
Mario Hock committed
192 193 194 195 196 197 198 199
        ## output-filename is given explicitly
        if ( args.output_filename ):
            filename = args.output_filename
        ## otherwise, the original filename is used (with adapted filename extension)
        else:
            filename = args.files[0]

        ## if output_dir is given, strip the original path from the filename (if there is any)
200 201 202 203 204 205
        if ( args.output_dir ):
            os.makedirs(args.output_dir, exist_ok=True)
            out_filename = args.output_dir + "/" + os.path.basename(filename) #+ "." + args.output
        else:
            out_filename = filename #+ "." + args.output

Mario Hock's avatar
Mario Hock committed
206
        ## replace filename extension to the output format
207 208
        out_filename = os.path.splitext(out_filename)[0] + "." + args.output

Mario Hock's avatar
Mario Hock committed
209
        ## * save file *
210 211 212 213 214 215 216 217
        plt.savefig(out_filename, format=args.output)
        print(out_filename)






Mario Hock's avatar
Mario Hock committed
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241

def merge_args(sub_args, args):
    """
      merges [sub_args] over [args] into [merged_args]:

        For every element that exist in both, the one from [sub_args] is used
    """
    merged_args = copy.copy(args)
    merged_args.subplots = None

    merged_args_dict = merged_args.__dict__
    sub_args_dict = sub_args.__dict__


    for key in sub_args_dict:
        #print( key, sub_args_dict[key] )

        if ( sub_args_dict[key] != None ):
            merged_args_dict[key] = sub_args_dict[key]

    return merged_args



242 243 244
## MAIN ##
if __name__ == "__main__":

Mario Hock's avatar
Mario Hock committed
245 246
    ### Command line arguments

247
    import argparse
Mario Hock's avatar
Mario Hock committed
248
    import shlex
249 250 251 252 253

    ## NOTE: most args are copied from cnl_plot

    DEFAULT_OPACITY = 0.7
    DEFAULT_ALPHA = 0.1             # alpha for ema, the smaller the smoother
Mario Hock's avatar
--sum  
Mario Hock committed
254
    DEFAULT_Y_RANGE = 10  # Gbit/s
255 256 257 258 259
    DEFAULT_X_MIN = -5.0
    DEFAULT_X_MAX = None



Mario Hock's avatar
Mario Hock committed
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
    ## These arguments can be given within a "--subplots"-argument as well as when "--subplots" isn't used at all
    def add_subplot_args(parser):
        ## NOTE: The merging algorithm could get confused, if arguments with default-values are put in here!
        #    (Maybe "nargs='?'" can be used in this case...)

        parser.add_argument("files", nargs='*')
        #parser.add_argument("files")

        ## make it pretty
        parser.add_argument("-s", "--send-only", action="store_true")
        parser.add_argument("-r", "--receive-only", action="store_true")
        parser.add_argument("--nics", nargs='*')
        parser.add_argument("-nl", "--nic-labels", nargs='*')

        parser.add_argument("-c", "--color", type=str, nargs='*',
                            help="see: http://matplotlib.org/api/colors_api.html")

Mario Hock's avatar
Mario Hock committed
277 278 279 280
        parser.add_argument("-sc", "--sum-color", type=str, default="#d400ae",
                            help="Color for summarized line, use together with --sum")


Mario Hock's avatar
Mario Hock committed
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298

    ## These arguments can't be given within "--subplots"
    def add_unique_args(parser):
        parser.add_argument("-ref", "--reference-files", nargs='+', default=list(),
                            help="These files will be used to find a common base time, but will not be plotted.")

        parser.add_argument("-nsc", "--net-scale", type=float, default=DEFAULT_Y_RANGE,
                            help="[Gbit/s]; Default: 10")
        parser.add_argument("--opacity", type=float, default=DEFAULT_OPACITY,
                            help="Default: 0.7")
        parser.add_argument("-tn", "--transparent-net", action="store_true")
        ## TODO smoothing could be interesting for individual subplots, too..
        parser.add_argument("-sn", "--smooth-net", nargs='?', const=DEFAULT_ALPHA, type=float,
                            metavar="ALPHA",
                            help = "Smooth transmission rates with exponential moving average. (Disabled by default. When specified without parameter: ALPHA=0.1)" )

        parser.add_argument("-o", "--output", default = "live",
                            help="Set output type. Choices: live, pdf, Default: live")
299

Mario Hock's avatar
Mario Hock committed
300 301
        parser.add_argument("-of", "--output-filename",
                    help="Sets the filename of output explicitly (helpful when subplots are used).")
302

Mario Hock's avatar
Mario Hock committed
303
        parser.add_argument("-d", "--output-dir")
304

305 306 307
        parser.add_argument("--rel-base-time", action="store_true",
                            help="Do NOT use a common base time, but begin every line at 0. (Do not in conjunction with --reference-files)")

308

Mario Hock's avatar
--sum  
Mario Hock committed
309 310 311 312 313 314
        parser.add_argument("--sum", action="store_true", default=False,
                            help="Summarizes the values of the given streams (show total throughput as one line). Please make sure to select the connect NICs and send/receive direction.")

        parser.add_argument("--sum-only", action="store_true", default=False,
                            help="Hide individual lines (use together with --sum.")

315

Mario Hock's avatar
Mario Hock committed
316 317 318
        ## make it pretty
        parser.add_argument("--x-min", type=float, default=DEFAULT_X_MIN)
        parser.add_argument("--x-max", type=float, default=DEFAULT_X_MAX)
319

Mario Hock's avatar
Mario Hock committed
320 321
        parser.add_argument("-l", "--legend-pos", type=int,
                            help="see: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.legend")
322

Mario Hock's avatar
Mario Hock committed
323 324 325
        parser.add_argument("-ncols", "--legend-col-number", type=int, default=1,
                            help="see: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.legend")

326

Mario Hock's avatar
Mario Hock committed
327 328 329
        ## subplots
        parser.add_argument("-sub", "--subplots", type=str, nargs='*',
                            help="Use this if you want to draw multiple plots into the same image. (Arguments must not start with '-'.)")
330 331


Mario Hock's avatar
Mario Hock committed
332 333 334 335
    # Create general parser
    parser = argparse.ArgumentParser()
    add_subplot_args(parser)
    add_unique_args(parser)
336 337 338

    args = parser.parse_args()

Mario Hock's avatar
Mario Hock committed
339
    ## adjust arguments
340
    args.net_scale *= 10**9  # --> multiply by 10**9 to get Gbit/s
Mario Hock's avatar
Mario Hock committed
341
    args.sum_color = [args.sum_color]
342 343


Mario Hock's avatar
Mario Hock committed
344 345 346
    ## Subplots: Read the arguments given as argument to "--subplots" and merge the result
    subplot_args = list()
    subplot_args.append(args)
347 348


Mario Hock's avatar
Mario Hock committed
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
    if args.subplots:
        # Create subplot parser
        subplot_parser = argparse.ArgumentParser()
        add_subplot_args(subplot_parser)

        for subcmd in args.subplots:
            sub_argv = shlex.split(subcmd)
            sub_args = subplot_parser.parse_args(sub_argv)

            merged_args = merge_args(sub_args, args)

            subplot_args.append(merged_args)
            # XXX debug
            #print( sub_args )
            #print()
            #print( merged_args )
365 366 367 368




Mario Hock's avatar
Mario Hock committed
369
    ## Find common base time
370 371 372 373 374
    if ( not args.rel_base_time ):
        reference = list()
        reference.extend(args.reference_files)
        for args in subplot_args:
            reference.extend(args.files)
Mario Hock's avatar
Mario Hock committed
375

376
        base_time = get_common_base_time(reference)
Mario Hock's avatar
Mario Hock committed
377

378 379 380 381 382 383
    ## Rel base time, find a base-time for each plot
    else:
        reference = list()
#        reference.extend(args.reference_files)
        for args in subplot_args:
            reference.extend(args.files)
Mario Hock's avatar
Mario Hock committed
384

385
        base_time = get_base_times(reference)
386 387 388



Mario Hock's avatar
Mario Hock committed
389
    ### Plotting
390

391
    plot(subplot_args, base_time)
392 393