-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrace2dot.py
executable file
·302 lines (243 loc) · 9.4 KB
/
trace2dot.py
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
#!/usr/bin/env python
"""
simple script to visualize the trace output of smv / NuSMV
via Graphviz's dot-format. first, the trace is parsed and
then coded as dot-graph with states as nodes and input
(transitions) as arcs between them. even if the counterexample's
loop start- and end-state are the same, they are represented by
two different nodes as there can be differences in the completeness
of the state variables' representation.
this is only a simple hack to get quick and dirty trace graphs ;-)
"""
import os,sys,getopt
from collections import OrderedDict
digraph = ""
try:
import pydot
except:
print ("this module depends on pydot\nplease visit http://dkbza.org/ to obtain these bindings")
sys.exit(2)
# CHANGE HERE:
VIEW_CMD="gv -antialias" #used as: VIEW_CMD [file]
DOT_CMD="dot -Tps -o" #used as: DOT_CMD [outfile] [infile]
TEMPDIR="/tmp" #store dot-file and rendering if viewmode without output-file
# for internal purposes, change only, if you know, what you do
DEBUG=False
PROFILE=False
PSYCO=True
if PSYCO:
try:
import psyco
except (ImportError, ):
pass
else:
psyco.full()
if DEBUG: print ("psyco enabled")
def trace2dotlist(traces):
"""this function takes the trace output of (nu)smv as a string;
then, after cleaning the string of warnings and compiler messages,
decomposes the output into separate single traces which are translated
to dot-graphs by _singletrace2dot. as a traceoutput can combine several
traces, this method returns a list of the dot-graphs"""
# beautify ;-)
lines = [line for line in traces if not (line.startswith("***") or
line.startswith("WARNING") or line == "\n")]
map(lambda x: x.lstrip(" "), lines)
# cut list at each "-- specification"
index=0
trace_list=[]
# trace_list = traces for multiple properties.
# each trace consists of sequence of states.
# each state consists of a list of variables and their values
for line in lines:
if line.startswith("-- specification"):
# TODO: need to commemnt out the following line
# formulae = item.rstrip("is false\n").lstrip("-- specification")
last = index
index = lines.index(line)
trace_list.append(lines[last: index])
trace_list.append(lines[index: len(lines)])
#sort out postive results. And filter out the empty trace.
trace_list = [trace for trace in trace_list if len(trace)>1 and not str(trace[0]).endswith("true")]
# Draw graph for each trace
graph=[]
for trace in trace_list:
graph.append(_singletrace2dot(trace,True))
return graph
def _singletrace2dot(trace,is_beautified=False):
"""translate a single trace into a corresponding dot-graph;
wheras the parsing assumes a correct trace given as
trace ::= state ( input state )*
"""
# if not is_beautified:
# lines = [line for line in trace if not (line.startswith("***") or
# line.startswith("WARNING") or line == "\n"
# or line.startswith("-- specification") or line.startswith("-- as demonstrated")
# or line.startswith("Trace Description: ") or line.startswith("Trace Type: "))]
# map(lambda x: x.lstrip(" "), lines)
# else:
# lines = trace
# strip the headers of each trace.
global digraph
lines = []
for line in trace:
#print(line)
if( not (line.startswith("***") or
line.startswith("WARNING") or line == "\n"
or line.startswith("-- specification") or line.startswith("-- as demonstrated")
or line.startswith("Trace Description: ") or line.startswith("Trace Type: "))):
lines.append(line.lstrip(" "))
#print (lines)
#slice list at "->"
index=0
states=[]
for item in lines:
if item.startswith("->"):
last=index
index=lines.index(item)
states.append(lines[last:index]) # the first state is empty
states.append(lines[index:len(lines)])
print (states)
lines=False #free space!
graph = pydot.Graph()
loop=False #flag to finally add an additional dotted edge for loop
assert states[1][0].startswith("-> State:") #starting with state!
digraph = 'Digraph G{\n'
digraph += 'rankdir=LR\n'
stateVariablesDict = OrderedDict()
counter = 0
for item in states[1:]: #first item is header
name= item[0].lstrip("-> ").rstrip(" <-\n")
if (name.startswith("State")):
state=name.lstrip("State: ")
node=pydot.Node(state)
props=name+'\\n' #to reach pydotfile: need double '\'
digraph = digraph + 'S' + str(counter) + '[shape=box,label=\"' + name + '\\n'
counter = counter + 1
#print (name)
for i in (item[1:]):
#props+=i.rstrip('\n')
#props+="\\n"
isNewValue = False
s = str(i).rstrip('\n')
variable = s[:s.rfind('=')].strip()
value = s[s.rfind('=')+1:].strip()
if(variable not in stateVariablesDict):
isNewValue = False
else:
(val, newValInd) = stateVariablesDict[variable]
if(str(val) != str(value)):
isNewValue = True
stateVariablesDict[variable] = (value, isNewValue)
#stateVariablesList = [[k, v] for k, v in stateVariablesDict.items()]
for var, (val, newValInd) in stateVariablesDict.items():
if(newValInd == True):
props += '*' + str(var) + ' = ' + str(val) + '\\n'
digraph = digraph + '*' + str(var) + ' = ' + str(val) + '\\n'
else:
props += str(var) + ' = ' + str(val) + '\\n'
digraph = digraph + str(var) + ' = ' + str(val) + '\\n'
node.set_label('"'+props+'"')
digraph = digraph + '\"]\n'
graph.add_node(node)
for var, (val, newValInd) in stateVariablesDict.items():
stateVariablesDict[var] = (val, False)
elif name.startswith("Input"):
assert state #already visited state
trans=name.lstrip("Input: ")
edge=pydot.Edge(state,trans)
hasLoop = [it for it in item[1:] if it.startswith("-- Loop starts here")]
#TODO: check trace-syntax, if this can happen only in the last line of a transition
# then list-compreh. can be avoided
if hasLoop:
loop=state #remember state at which loop starts
item.remove(hasLoop[0])
props=""
for i in (item[1:]):
props+=i.rstrip('\n')
props+="\\n"
edge.set_label(props)
graph.add_edge(edge)
else:
assert False #only states and transitions!
if loop:
edge=pydot.Edge(state,loop)
edge.set_style("dotted,bold")
edge.set_label(" LOOP")
graph.add_edge(edge)
for i in range(1, counter):
digraph = digraph + 'S' + str(i-1) + ' -> ' + 'S' + str(i) + '\n'
digraph = digraph + '\n}\n'
return graph
def usage():
print ("usage:")
print (str(os.path.basename(sys.argv[0]))+" [-h|--help] [-o|--output=<filename>] filename")
print ()
print (" -o : output to file (else to std.output)")
print (" --view : generate preview & open viewer")
def main():
global digraph
try:
opts, args = getopt.getopt(sys.argv[1:], "hvo:", ["view","help","output="])
except getopt.GetoptError:
# print help information and exit:
usage()
sys.exit(2)
outputfilename = None
verbose = False
view=False
tempdir=None
for o, a in opts:
if o in ("-h", "--help"):
usage()
sys.exit()
if o in ("-o", "--output"):
outputfilename = a
if o == "--view":
view=True
if args.__len__():
filename=args[0]
trace = open(filename,'r').readlines()
#trace.close()
else:
trace=sys.stdin.readlines()
graph= trace2dotlist(trace)
if outputfilename:
outputfile=open(outputfilename,'w')
elif view:
import tempfile
tempdir=tempfile.mkdtemp(dir=TEMPDIR)
outputfilename=os.path.join(tempdir,"trace.dot")
outputfile=open(outputfilename,'w')
else:
outputfile=sys.stdout
for g in graph:
outputfile.write(g.to_string())
outputfile.close()
# Draw Digraph:
#print (digraph)
outputfilename = str(outputfilename) + '_digraph.dot'
outputfile = open(outputfilename, 'w')
outputfile.write(digraph)
outputfile.close()
if view:
if not tempdir: #for view & output
import tempfile
tempdir=tempfile.mkdtemp(dir=TEMPDIR)
visualgraphfile=os.path.join(tempdir,"trace.ps")
os.system("%s %s %s"%(DOT_CMD,visualgraphfile,outputfilename))
os.system("%s %s"%(VIEW_CMD,visualgraphfile))
if __name__=="__main__":
if DEBUG:
# for post-mortem debugging
import pydb,sys
sys.excepthook = pydb.exception_hook
elif PROFILE:
if PSYCO:
raise (Exception, "cannot profile whilst using psyco!!!")
import hotshot
prof = hotshot.Profile("_hotshot",lineevents=1)
prof.runcall(main)
prof.close()
else:
main()