1#!/usr/bin/env python3

2# Copyright 2013 Canonical Ltd.

3# Written by:

4# Sylvain Pineau <sylvain.pineau@canonical.com>

5#

6# This program is free software: you can redistribute it and/or modify

7# it under the terms of the GNU General Public License version 3,

8# as published by the Free Software Foundation.

9#

10# This program is distributed in the hope that it will be useful,

11# but WITHOUT ANY WARRANTY; without even the implied warranty of

12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

13# GNU General Public License for more details.

14#

15# You should have received a copy of the GNU General Public License

16# along with this program. If not, see <http://www.gnu.org/licenses/>.

17

18"""

19 Script checking gpu lockups.

20

21 Several threads are started to exercise the GPU in ways that can cause gpu

22 lockups.

23 Inspired by the workload directory of the xdiagnose package.

24"""

25

26 import gi

27 import os

28 import re

29 import subprocess

30 import sys

31 import time

32

33 gi.require_version("Gio", "2.0")

34 from gi.repository import Gio # noqa: E402

35 from math import cos, sin # noqa: E402

36 from threading import Thread # noqa: E402

37

38

39 class GlxThread(Thread):

40 """

41 Start a thread running glxgears

42 """

43

44 def run(self):

45

46 try:

47 self.process = subprocess.Popen(

48 ["glxgears", "-geometry", "400x400"],

49 stdout=subprocess.PIPE,

50 stderr=subprocess.STDOUT,

51 )

52 self.process.communicate()

53 except (subprocess.CalledProcessError, FileNotFoundError) as er:

54 print("WARNING: Unable to start glxgears (%s)" % er)

55

56 def terminate(self):

57 if not hasattr(self, "id"):

58 print("WARNING: Attempted to terminate non-existing window.")

59 if hasattr(self, "process"):

60 self.process.terminate()

61

62

63 class RotateGlxThread(Thread):

64 """

65 Start a thread performing glxgears windows rotations

66 """

67

68 def __init__(self, id, offset):

69 Thread.__init__(self)

70 self.id = id

71 self.offset = offset

72 self.cancel = False

73

74 def run(self):

75 while True:

76 for j in range(60):

77 x = int(200 * self.offset + 100 * sin(j * 0.2))

78 y = int(200 * self.offset + 100 * cos(j * 0.2))

79 coords = "%s,%s" % (x, y)

80 subprocess.call(

81 "wmctrl -i -r %s -e 0,%s,-1,-1" % (self.id, coords),

82 shell=True,

83 )

84 time.sleep(0.002 * self.offset)

85 if self.cancel:

86 return

87

88

89 class ChangeWorkspace(Thread):

90 """

91 Start a thread performing fast workspace switches

92 """

93

94 def __init__(self, hsize, vsize, xsize, ysize):

95 Thread.__init__(self)

96 self.hsize = hsize

97 self.vsize = vsize

98 self.xsize = xsize

99 self.ysize = ysize

100 self.cancel = False

101

102 def run(self):

103 while True:

104 for i in range(self.hsize):

105 for j in range(self.vsize):

106 subprocess.call(

107 "wmctrl -o %s,%s" % (self.xsize * j, self.ysize * i),

108 shell=True,

109 )

110 time.sleep(0.5)

111 if self.cancel:

112 # Switch back to workspace #1

113 subprocess.call("wmctrl -o 0,0", shell=True)

114 return

115

116

117 class Html5VideoThread(Thread):

118 """

119 Start a thread performing playback of an HTML5 video in firefox

120 """

121

122 @property

123 def html5_path(self):

124 if os.getenv("PLAINBOX_PROVIDER_DATA"):

125 return os.path.join(

126 os.getenv("PLAINBOX_PROVIDER_DATA"),

127 "websites/html5_video.html",

128 )

129

130 def run(self):

131 if self.html5_path and os.path.isfile(self.html5_path):

132 subprocess.call(

133 "firefox %s" % self.html5_path,

134 stdout=open(os.devnull, "w"),

135 stderr=subprocess.STDOUT,

136 shell=True,

137 )

138 else:

139 print("WARNING: unable to start html5 video playback.")

140 print("WARNING: test results may be invalid.")

141

142 def terminate(self):

143 if self.html5_path and os.path.isfile(self.html5_path):

144 subprocess.call("pkill firefox", shell=True)

145

146

147 def check_gpu(log=None):

148 if not log:

149 log = subprocess.check_output(["dmesg"], universal_newlines=True)

150 if re.findall(r"gpu\s+hung", log, flags=re.I):

151 print("GPU hung Detected")

152 return 1

153

154

155 def main():

156 if check_gpu():

157 return 1

158 GlxWindows = []

159 GlxRotate = []

160 subprocess.call("pkill 'glxgears|firefox'", shell=True)

161

162 Html5Video = Html5VideoThread()

163 Html5Video.start()

164

165 source = Gio.SettingsSchemaSource.get_default()

166

167 for i in range(2):

168 GlxWindows.append(GlxThread())

169 GlxWindows[i].start()

170 time.sleep(5)

171 try:

172 windows = subprocess.check_output(

173 "wmctrl -l | grep glxgears", shell=True

174 )

175 except subprocess.CalledProcessError as er:

176 print("WARNING: Got an exception %s" % er)

177 windows = ""

178 for app in sorted(windows.splitlines(), reverse=True):

179 if b"glxgears" not in app:

180 continue

181 GlxWindows[i].id = str(

182 re.match(b"^(0x\w+)", app).group(0), "utf-8" # noqa: W605

183 )

184 break

185 if hasattr(GlxWindows[i], "id"):

186 rotator = RotateGlxThread(GlxWindows[i].id, i + 1)

187 GlxRotate.append(rotator)

188 rotator.start()

189 else:

190 print("WARNING: Window {} not found, not rotating it.".format(i))

191

192 hsize = vsize = 2

193 hsize_ori = vsize_ori = None

194 if source.lookup("org.compiz.core", True):

195 settings = Gio.Settings(

196 "org.compiz.core", "/org/compiz/profiles/unity/plugins/core/"

197 )

198 hsize_ori = settings.get_int("hsize")

199 vsize_ori = settings.get_int("vsize")

200 settings.set_int("hsize", hsize)

201 settings.set_int("vsize", vsize)

202 time.sleep(5)

203 else:

204 hsize = int(

205 subprocess.check_output(

206 "gconftool --get /apps/compiz-1/general/screen0/options/hsize",

207 shell=True,

208 )

209 )

210 vsize = int(

211 subprocess.check_output(

212 "gconftool --get /apps/compiz-1/general/screen0/options/vsize",

213 shell=True,

214 )

215 )

216 (x_res, y_res) = re.search(

217 b"DG:\s+(\d+)x(\d+)", # noqa: W605

218 subprocess.check_output("wmctrl -d", shell=True),

219 ).groups()

220 DesktopSwitch = ChangeWorkspace(

221 hsize, vsize, int(x_res) // hsize, int(y_res) // vsize

222 )

223 DesktopSwitch.start()

224

225 time.sleep(35)

226

227 for i in range(len(GlxRotate)):

228 GlxRotate[i].cancel = True

229 for i in range(len(GlxWindows)):

230 GlxWindows[i].terminate()

231 DesktopSwitch.cancel = True

232 time.sleep(10)

233 Html5Video.terminate()

234 if check_gpu() or not Html5Video.html5_path:

235 return 1

236

237 if source.lookup("org.compiz.core", True):

238 settings = Gio.Settings(

239 "org.compiz.core", "/org/compiz/profiles/unity/plugins/core/"

240 )

241 settings.set_int("hsize", hsize_ori)

242 settings.set_int("vsize", vsize_ori)

243 Gio.Settings.sync()

244

245

246 if __name__ == "__main__":

247 sys.exit(main())