File: plot.py

package info (click to toggle)
aspectc%2B%2B 1%3A2.3%2Bgit20221129-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 17,988 kB
  • sloc: cpp: 109,125; ansic: 7,644; sh: 2,262; makefile: 1,312; pascal: 634; python: 402; xml: 349
file content (428 lines) | stat: -rw-r--r-- 13,863 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# -*- coding: utf-8 -*-

# Argument: csv-file containing profiling data
# Output on stdout: names of all created pdf files

import sys
import re
import subprocess
import os

# Regular expression to match the lines of the csv-file
# group ; grouptime ; function ; number of executions ; total time (clock cycles)

regex_csv = re.compile('^((?P<group>\S+))?\s+;\s*(?P<grouptime>[^;]+);\s*(?P<func>[^;]+);\s*(?P<execs>[0-9]+)\s+;\s+(?P<time>[0-9]+)\s+$')

regex_args = re.compile('\(.*\)')
regex_scope = re.compile('\S*::')

def short_sig(sig):
    argsmatch = regex_args.search (sig)
    scopematch = regex_scope.search (sig)
    if scopematch:
        scope = scopematch.group()
    else:
        scope = ""

    # substitute any arguments and trailing tokens by an empty string
    s = sig[:argsmatch.start()]

    arguments = sig[argsmatch.start()+1:argsmatch.end()-1]
    if arguments == "":
        arg_list = []
    else:
        arg_list = arguments.split(",")
    s = s.split("::")[-1]
    return (s.split()[-1], scope, arg_list)



def create_unique_signature(short, siglist):
    new_signatures = {}

    if len(siglist) == 1:
        (longsig,scope,args) = siglist[0]
        if len(args) == 0:
            new_signatures[longsig] = short+"()"
        else:
            new_signatures[longsig] = short+"(...)"
        return new_signatures

    # init with short signature
    for siginfo in siglist:
        (longsig, scope, args) = siginfo
        new_signatures[longsig] = short

    diff_signatures = set(new_signatures.values())

    # are there different scopes?
    scopes = set()
    for siginfo in siglist:
        (longsig, scope, args) = siginfo
        scopes.add(scope)
    if len(scopes) > 1:
        # add scope
        for siginfo in siglist:
            (longsig, scope, args) = siginfo
            newsig = new_signatures[longsig]
            new_signatures[longsig] = scope+newsig
            diff_signatures = set(new_signatures.values())

    index = 0
    while len(diff_signatures) < len(new_signatures):
        # not yet unique, add arguments
        for siginfo in siglist:
            (longsig, scope, args) = siginfo
            newsig = new_signatures[longsig]
            if index == 0:
                newsig = newsig + "("
            if index < len(args):
                newsig = newsig + args[index]
            if index+1 < len(args):
                newsig = newsig + ","
            if index+1 == len(args):
                newsig = newsig +")"
            new_signatures[longsig] = newsig
        diff_signatures = set(new_signatures.values())
        index+= 1

    # add parentheses
    for longsig in new_signatures:
        new_sig = new_signatures[longsig]
        if str.find (new_sig,'(') == -1:
            new_sig += "("
        if str.find (new_sig,')') == -1:
            new_sig += "...)"
        new_signatures[longsig] = new_sig
    return new_signatures



class Function:
    func = ""
    short_func = ""
    execs = 0;
    time = 0
    def __init__(self, func, execs, time):
        self.short_name = short_sig(func)
        self.func = func
        self.execs = execs
        self.time = time
        return

class Group:
    def __init__(self, name, is_function, grptime):
        self.name = name
        self.is_function = is_function
        self.grptime = grptime
        self.maxtime = 0
        self.functions = []
        return

    def add_data(self,func, execs, time):
        f = Function(func,execs,time)
        self.functions.append(f)
        if self.maxtime < time:
            self.maxtime = time
        return
    

class ProfilingData:
    csv_file = ""
    datafile = ""
    commandfile = ""
    maxtime = 0
    groups = {}

    def __init__(self, filename):
        self.csv_file = filename
        # create names for temporary files containing the gnuplot commands and data
        (basename, extension) = os.path.splitext(self.csv_file)
        self.commandfile = basename + ".gp"
        self.datafile = basename + ".data"
        self.maxtime = 0
        self.groups = {}
        try:
            fd = open (self.csv_file, 'r')
            data = fd.readlines()
            fd.close()
        except:
            print "Error when reading '" + self.csv_file + "'"
            sys.exit (2)

        first = True
        for line in data:
            if first:
                first = False
                continue
            # Is it a valid line?
            match = re.match(regex_csv, line)
            if match:
                group = match.group('group')
                grouptime = match.group('grouptime')
                func = match.group('func').strip()
                execs = long(match.group('execs'))
                time = long(match.group('time'))
                if not group:
                    is_function = True
                    group = func
                    grouptime = time
                else:
                    is_function = False
                    grouptime = long(grouptime)
                if self.maxtime < grouptime:
                    self.maxtime = grouptime
                if group in self.groups:
                    groupInfo = self.groups[group]
                    groupInfo.add_data(func,execs,time)
                else:
                    groupInfo = Group(group,is_function,grouptime)
                    self.groups[group] = groupInfo
                    groupInfo.add_data(func,execs,time)
            else:
                print "nomatch", line
        return


    def print_data (self):
        print "Data read from ", self.csv_file
        for name in self.groups:
            groupInfo = self.groups[name]
            print groupInfo.name, groupInfo.grptime


    def assemble_commands (self, x, name, time):
        if x == 1:
            command = "plot "
            data = ""
        else:
            command = ", "
            data = ";"
        command += "newhistogram at "+str(x)+" '', "
        command += "'"+self.datafile+"' "
        command += "using "+str(2*x)+":xtic("+str(2*x-1)+"), "
        command += "'' using 0:"+str(2*x)+":"+str(2*x)+" with labels left "
        command += "offset first "+str(x)+",graph 0.05 notitle"
        data += name+";"+str(time)

        return (command,data)


    def print_gnuplot_commands(self,group,maxval):
        title = self.csv_file
        if group != None:
            title += ", "+ group
        
        commandhdr = ""
        commandhdr += "#gnuplot commands for profiling data of "
        commandhdr += title
        commandhdr += "\n\n"
        commandhdr += "set style fill solid 1.00 border -1\n"
        commandhdr += "set style histogram\n"
        commandhdr += "set style data histograms\n"
        commandhdr += "set key off\n"
        commandhdr += "set ylabel 'time in clock ticks'\n"
        commandhdr += "set xtics rotate by -45\n"
        commandhdr += "set datafile separator ';'\n"
        commandhdr += "set title 'Weaving Performance for "+title+"'\n"
        commandhdr += "set format y \"%.0f\"\n"
        command = ""
        data = ""
        xtics = 0;
        
        if group == None:
            # create and count short signatures
            short_signatures = {}
            for name in self.groups:
                groupInfo = self.groups[name]
                if groupInfo.is_function:
                    sig = groupInfo.name
                    (short, scope, args) = short_sig(sig)
                    if short in short_signatures:
                        sig_list = short_signatures[short]
                        sig_list.append ((sig,scope,args))
                    else:
                        short_signatures[short] = [(sig,scope,args)]

            # Create a histogram with infos about all groups and functions
            unique_signatures = {}
            xtics = len (self.groups)
            maxy = maxval.maxtime
            x = 0
            for name in self.groups:
                x += 1
                groupInfo = self.groups[name]
                if len(groupInfo.functions) == 1:
                    execStr = " ["+str(groupInfo.functions[0].execs)+"]"
                else:
                    execStr = ""

                sig = groupInfo.name
                if groupInfo.is_function:
                    if not unique_signatures.has_key(sig):
                        (short, scope, args) = short_sig(sig)
                        sig_list = short_signatures[short]
                        unique_signatures.update(create_unique_signature(short, sig_list))
                    sig = unique_signatures[sig]
                (com, dat) = self.assemble_commands (x, sig+execStr,
                                   groupInfo.grptime)
                command += com
                data += dat
                
        else:
            # create and count short signatures
            short_signatures = {}
            groupInfo = self.groups[group]
            for func in groupInfo.functions:
                sig = func.func
                (short, scope, args) = short_sig(sig)
                if short in short_signatures:
                    sig_info = short_signatures[short]
                    sig_info.append ((sig,scope,args))
                else:
                    short_signatures[short] = [(sig,scope,args)]

            # Create a histogram with infos about the given group
            maxy = maxval.grouptime[group]
            x = 0
            xtics = len(groupInfo.functions)
            if xtics == 1:
                return (None, None)

            unique_signatures = {}
            for func in groupInfo.functions:
                x += 1
                sig = func.func
                if unique_signatures.has_key(sig):
                    sig = unique_signatures[sig]
                else:
                    (short, scope, args) = short_sig(sig)
                    sig_info = short_signatures[short]
                    unique_signatures.update(create_unique_signature(short, sig_info))
                    sig = unique_signatures[sig]
                (com, dat) = self.assemble_commands (x, sig+" ["+str(func.execs)+"]",
                                        func.time)
                command += com
                data += dat

        commandhdr += "set yrange [0:"+str(maxy*1.1)+"]\n"
        commandhdr += "set terminal postscript eps noenhanced "
        commandhdr += "color solid rounded "
        if xtics < 30:
             commandhdr += "'Helvetica' 12\n"
        else:
            commandhdr += "'Helvetica' 8\n"

        return (commandhdr+command, data)
                            

    def create_pdf (self, command, data, name=None):
        if command == None:
            return

        # save the histograms as several eps and pdf files
        (basename, extension) = os.path.splitext(self.csv_file)
        if name != None:
            epsfile = basename + "-"+name+".eps"
        else:
            epsfile = basename + ".eps"

        try:
            dat = open(self.datafile, "w")
            sys.stdout = dat
            print data
        except:
            print "Cannot create temporary file '" + self.datafile + "'"
        finally:
            sys.stdout = sys.__stdout__
            dat.close()

        try:
            gp = open(self.commandfile, "w")
            sys.stdout = gp
            print command
        except:
            print "Cannot create temporary file '" + self.commandfile + "'"
        finally:
            sys.stdout = sys.__stdout__
            gp.close()

        # execute "gnuplot <datafile>"
        try:
            eps = open(epsfile, "w")
            gnuplot = subprocess.call(["gnuplot", self.commandfile], stdout=eps)
        except Exception, e:
            print "Error executing gnuplot: %s" % e
            sys.exit (2)
        finally:
            eps.close()

        # execute "epstopdf <epsfile>"
        try:
            epstopdf = os.system("epstopdf " + epsfile)
        except Exception, e:
            print "Error executing epstopdf: %s" % e
            sys.exit (2)

        # delete temporary gnuplot command and data files
        os.remove (self.commandfile)
        os.remove (self.datafile)

        # return the name of the pdf file
        (basename, extension) = os.path.splitext(epsfile)
        pdffile = basename+".pdf"
        return pdffile


    def create_histograms (self,maxval):
        # Create an overview histogram
        (command, data) = self.print_gnuplot_commands (None,maxval)
        pdffile = self.create_pdf (command, data)
        if pdffile != None:
            print pdffile,

        # create a histogram for each group
        for name in self.groups:
            (command, data) = self.print_gnuplot_commands (name,maxval)
            pdffile = self.create_pdf (command, data, name)
            if pdffile != None:
                print pdffile,

class MaxVal:
    maxtime = 0
    grouptime = {}

    def update (self, that):
        for group in that.groups:
            thatval = that.groups[group].maxtime
            if group in self.grouptime:
                if self.grouptime[group] < thatval:
                    self.grouptime[group] = thatval
            else:
                self.grouptime[group] = thatval
        if self.maxtime < that.maxtime:
            self.maxtime = that.maxtime
        return


def main(argv=None):
    if not argv: argv = sys.argv
    if len(argv) < 2:
        print "usage: python plot.py csv-files"
        sys.exit(1)

    # read the profiling data of all csv files
    profiling_data = []
    maxval = MaxVal()
    for csv_file in argv[1:]:
        data = ProfilingData(csv_file)
        profiling_data.append(data)
        maxval.update(data)

    # create histograms
    for data in profiling_data:
        data.create_histograms(maxval)

if __name__ == "__main__":
    sys.exit(main())