blob: 71c0123adc92be8a1b22ebbc995bd6fb0d9baa7b [file] [log] [blame]
Jose Fonseca731ccce2016-01-19 12:36:27 +00001#!/usr/bin/env python
2##########################################################################
3#
4# Copyright 2014-2016 VMware, Inc.
5# All Rights Reserved.
6#
7# Permission is hereby granted, free of charge, to any person obtaining a copy
8# of this software and associated documentation files (the "Software"), to deal
9# in the Software without restriction, including without limitation the rights
10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# copies of the Software, and to permit persons to whom the Software is
12# furnished to do so, subject to the following conditions:
13#
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# THE SOFTWARE.
24#
25##########################################################################/
26
27
28import subprocess
29import sys
30import os.path
31import optparse
32
33import unpickle
34
35
36class ContextState:
37
38 def __init__(self):
39 self.textures = {}
40
41
42class LeakDetector(unpickle.Unpickler):
43
44 def __init__(self, apitrace, trace):
45
46 cmd = [apitrace, 'pickle', '--symbolic', trace]
47 p = subprocess.Popen(args = cmd, stdout = subprocess.PIPE)
48
49 unpickle.Unpickler.__init__(self, p.stdout)
50
51 self.context = ContextState()
52
53 def parse(self):
54 unpickle.Unpickler.parse(self)
55
56 # Reached the end of the trace -- dump any live objects
57 self.dumpLeaks("<EOF>", self.context)
58
59 def handleCall(self, call):
60 # Ignore calls without side effects
61 if call.flags & unpickle.CALL_FLAG_NO_SIDE_EFFECTS:
62 return
63
64 # Dump call for debugging:
65 if False:
66 sys.stderr.write(str(call))
67
68 # FIXME: keep track of current context on each thread (*MakeCurrent)
69 context = self.context
70
71 if call.functionName == 'glGenTextures':
72 n, textures = call.argValues()
73 for i in range(n):
74 texture = textures[i]
75 context.textures[texture] = call.no
76
77 if call.functionName == 'glDeleteTextures':
78 n, textures = call.argValues()
79 for i in range(n):
80 texture = textures[i]
81 try:
82 del context.textures[texture]
83 except KeyError:
84 # Ignore if texture name was never generated
85 pass
86
87 if call.functionName in [
88 'glXDestroyContext',
89 'eglDestroyContext',
90 'wglDeleteContext',
91 ]:
92 self.dumpLeaks(call.no, context)
93
94 def dumpLeaks(self, callNo, context):
95 for textureName, textureCallNo in context.textures.iteritems():
96 sys.stderr.write('error: call %s: texture %u created in call %u was never destroyed\n' % (callNo, textureName, textureCallNo))
97 context.textures.clear()
98
99
100def main():
101 '''Main program.
102 '''
103
104 # Parse command line options
105 optparser = optparse.OptionParser(
106 usage='\n\t%prog [options] TRACE',
107 version='%%prog')
108 optparser.add_option(
109 '-a', '--apitrace', metavar='PROGRAM',
110 type='string', dest='apitrace', default='apitrace',
111 help='apitrace command [default: %default]')
112
113 options, args = optparser.parse_args(sys.argv[1:])
114 if len(args) != 1:
115 optparser.error("incorrect number of arguments")
116
117 inTrace = args[0]
118 if not os.path.isfile(inTrace):
119 sys.stderr.write("error: `%s` does not exist\n" % inTrace)
120 sys.exit(1)
121
122 detector = LeakDetector(options.apitrace, inTrace)
123 detector.parse()
124
125
126if __name__ == '__main__':
127 main()