don't throw away command output when packaging installsets
[LibreOffice.git] / bin / benchmark-document-loading
blob9295b95d5783c7d4c92ea21ce2f658a15b80e2f6
1 #!/usr/bin/env python # -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
3 # Version: MPL 1.1 / GPLv3+ / LGPLv3+
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License or as specified alternatively below. You may obtain a copy of
8 # the License at http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
13 # License.
15 # Major Contributor(s):
16 # Copyright (C) 2012 Red Hat, Inc., Michael Stahl <mstahl@redhat.com>
17 # (initial developer)
19 # All Rights Reserved.
21 # For minor contributions see the git repository.
23 # Alternatively, the contents of this file may be used under the terms of
24 # either the GNU General Public License Version 3 or later (the "GPLv3+"), or
25 # the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"),
26 # in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable
27 # instead of those above.
29 # Simple script to load a bunch of documents and export them as Flat ODF
31 # Personally I run it like this:
32 # ~/lo/master-suse/instdir/program/python ~/lo/master-suse/bin/benchmark-document-loading --soffice=path:/home/tml/lo/master-suse/instdir/program/soffice --outdir=file://$PWD/out --userdir=file:///tmp/test $PWD/docs
35 import argparse
36 import datetime
37 import os
38 import subprocess
39 import sys
40 import threading
41 import time
42 import urllib
43 try:
44 from urllib.parse import quote
45 except ImportError:
46 from urllib import quote
47 import uuid
49 try:
50 import pyuno
51 import uno
52 import unohelper
53 except ImportError:
54 print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables")
55 print("PYTHONPATH=/installation/opt/program")
56 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
57 raise
59 try:
60 from com.sun.star.document import XDocumentEventListener
61 from com.sun.star.io import XOutputStream
62 except ImportError:
63 print("UNO API class not found: try to set URE_BOOTSTRAP variable")
64 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
65 raise
67 validCalcFileExtensions = [ ".xlsx", ".xls", ".ods", ".fods" ]
68 validWriterFileExtensions = [ ".docx" , ".rtf", ".odt", ".fodt", ".doc" ]
69 validImpressFileExtensions = [ ".ppt", ".pptx", ".odp", ".fodp" ]
70 validDrawFileExtensions = [ ".odg", ".fodg" ]
71 validReverseFileExtensions = [ ".vsd", ".vdx", ".cdr", ".pub", ".wpd" ]
72 validFileExtensions = {"calc": validCalcFileExtensions,
73 "writer": validWriterFileExtensions,
74 "impress": validImpressFileExtensions,
75 "draw": validDrawFileExtensions,
76 "reverse": validReverseFileExtensions}
77 flatODFTypes = {"calc": (".fods", "OpenDocument Spreadsheet Flat XML"),
78 "writer": (".fodt", "OpenDocument Text Flat XML"),
79 "impress": (".fodp", "OpenDocument Presentation Flat XML"),
80 "draw": (".fodg", "OpenDocument Drawing Flat XML")}
82 outdir = ""
84 def partition(list, pred):
85 left = []
86 right = []
87 for e in list:
88 if pred(e):
89 left.append(e)
90 else:
91 right.append(e)
92 return (left, right)
94 def filelist(directory, suffix):
95 if not directory:
96 raise Exception("filelist: empty directory")
97 if directory[-1] != "/":
98 directory += "/"
99 files = [directory + f for f in os.listdir(directory)]
100 # print(files)
101 return [f for f in files
102 if os.path.isfile(f) and os.path.splitext(f)[1] == suffix]
104 def getFiles(dirs, suffix):
105 # print( dirs )
106 files = []
107 for d in dirs:
108 files += filelist(d, suffix)
109 return files
111 ### UNO utilities ###
113 class OutputStream( unohelper.Base, XOutputStream ):
114 def __init__( self ):
115 self.closed = 0
117 def closeOutput(self):
118 self.closed = 1
120 def writeBytes( self, seq ):
121 sys.stdout.write( seq.value )
123 def flush( self ):
124 pass
126 class OfficeConnection:
127 def __init__(self, args):
128 self.args = args
129 self.soffice = None
130 self.socket = None
131 self.xContext = None
132 self.pro = None
133 def setUp(self):
134 (method, sep, rest) = self.args.soffice.partition(":")
135 if sep != ":":
136 raise Exception("soffice parameter does not specify method")
137 if method == "path":
138 socket = "pipe,name=pytest" + str(uuid.uuid1())
139 userdir = self.args.userdir
140 if not userdir:
141 raise Exception("'path' method requires --userdir")
142 if not userdir.startswith("file://"):
143 raise Exception("--userdir must be file URL")
144 self.soffice = self.bootstrap(rest, userdir, socket)
145 elif method == "connect":
146 socket = rest
147 else:
148 raise Exception("unsupported connection method: " + method)
149 self.xContext = self.connect(socket)
151 def bootstrap(self, soffice, userdir, socket):
152 argv = [ soffice, "--accept=" + socket + ";urp",
153 "-env:UserInstallation=" + userdir,
154 "--quickstart=no",
155 "--norestore", "--nologo", "--headless" ]
156 if self.args.valgrind:
157 argv.append("--valgrind")
158 os.putenv("SAL_LOG", "-INFO-WARN")
159 os.putenv("LIBO_ONEWAY_STABLE_ODF_EXPORT", "YES")
160 self.pro = subprocess.Popen(argv)
161 # print(self.pro.pid)
163 def connect(self, socket):
164 xLocalContext = uno.getComponentContext()
165 xUnoResolver = xLocalContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", xLocalContext)
166 url = "uno:" + socket + ";urp;StarOffice.ComponentContext"
167 # print("OfficeConnection: connecting to: " + url)
168 while True:
169 try:
170 xContext = xUnoResolver.resolve(url)
171 return xContext
172 # except com.sun.star.connection.NoConnectException
173 except pyuno.getClass("com.sun.star.connection.NoConnectException"):
174 # print("NoConnectException: sleeping...")
175 time.sleep(1)
177 def tearDown(self):
178 if self.soffice:
179 if self.xContext:
180 try:
181 # print("tearDown: calling terminate()...")
182 xMgr = self.xContext.ServiceManager
183 xDesktop = xMgr.createInstanceWithContext("com.sun.star.frame.Desktop", self.xContext)
184 xDesktop.terminate()
185 # print("...done")
186 # except com.sun.star.lang.DisposedException:
187 except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"):
188 # print("caught UnknownPropertyException while TearDown")
189 pass # ignore, also means disposed
190 except pyuno.getClass("com.sun.star.lang.DisposedException"):
191 # print("caught DisposedException while TearDown")
192 pass # ignore
193 else:
194 self.soffice.terminate()
195 ret = self.soffice.wait()
196 self.xContext = None
197 self.socket = None
198 self.soffice = None
199 if ret != 0:
200 raise Exception("Exit status indicates failure: " + str(ret))
201 # return ret
202 def kill(self):
203 command = "kill " + str(self.pro.pid)
204 with open("killFile.log", "a") as killFile:
205 killFile.write(command + "\n")
206 # print("kill")
207 # print(command)
208 os.system(command)
210 class PersistentConnection:
211 def __init__(self, args):
212 self.args = args
213 self.connection = None
214 def getContext(self):
215 return self.connection.xContext
216 def setUp(self):
217 assert(not self.connection)
218 conn = OfficeConnection(self.args)
219 conn.setUp()
220 self.connection = conn
221 def preTest(self):
222 assert(self.connection)
223 def postTest(self):
224 assert(self.connection)
225 def tearDown(self):
226 if self.connection:
227 try:
228 self.connection.tearDown()
229 finally:
230 self.connection = None
231 def kill(self):
232 if self.connection:
233 self.connection.kill()
235 def simpleInvoke(connection, test):
236 try:
237 connection.preTest()
238 test.run(connection.getContext(), connection)
239 finally:
240 connection.postTest()
242 def runConnectionTests(connection, invoker, tests):
243 try:
244 connection.setUp()
245 for test in tests:
246 invoker(connection, test)
247 finally:
248 pass
249 #connection.tearDown()
251 class EventListener(XDocumentEventListener,unohelper.Base):
252 def __init__(self):
253 self.layoutFinished = False
254 def documentEventOccured(self, event):
255 # print(str(event.EventName))
256 if event.EventName == "OnLayoutFinished":
257 self.layoutFinished = True
258 def disposing(event):
259 pass
261 def mkPropertyValue(name, value):
262 return uno.createUnoStruct("com.sun.star.beans.PropertyValue",
263 name, 0, value, 0)
265 ### tests ###
267 def logTimeSpent(url, startTime):
268 print(os.path.basename(urllib.parse.urlparse(url).path) + "\t" + str(time.time()-startTime))
270 def loadFromURL(xContext, url, t, component):
271 xDesktop = xContext.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", xContext)
272 props = [("Hidden", True), ("ReadOnly", True)] # FilterName?
273 loadProps = tuple([mkPropertyValue(name, value) for (name, value) in props])
274 xListener = None
275 if component == "writer":
276 xListener = EventListener()
277 xGEB = xContext.getValueByName(
278 "/singletons/com.sun.star.frame.theGlobalEventBroadcaster")
279 xGEB.addDocumentEventListener(xListener)
280 try:
281 xDoc = None
282 startTime = time.time()
283 xDoc = xDesktop.loadComponentFromURL(url, "_blank", 0, loadProps)
284 if component == "calc":
285 try:
286 if xDoc:
287 xDoc.calculateAll()
288 except AttributeError:
289 pass
290 t.cancel()
291 logTimeSpent(url, startTime)
292 return xDoc
293 elif component == "writer":
294 time_ = 0
295 t.cancel()
296 while time_ < 30:
297 if xListener.layoutFinished:
298 logTimeSpent(url, startTime)
299 return xDoc
300 # print("delaying...")
301 time_ += 1
302 time.sleep(1)
303 else:
304 t.cancel()
305 logTimeSpent(url, startTime)
306 return xDoc
307 with open("file.log", "a") as fh:
308 fh.write("layout did not finish\n")
309 return xDoc
310 except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"):
311 xListener = None
312 raise # means crashed, handle it later
313 except pyuno.getClass("com.sun.star.lang.DisposedException"):
314 xListener = None
315 raise # means crashed, handle it later
316 except pyuno.getClass("com.sun.star.lang.IllegalArgumentException"):
317 pass # means could not open the file, ignore it
318 except:
319 if xDoc:
320 # print("CLOSING")
321 xDoc.close(True)
322 raise
323 finally:
324 if xListener:
325 xGEB.removeDocumentEventListener(xListener)
327 def exportToODF(xContext, xDoc, baseName, t, component):
328 exportFileName = outdir + "/" + os.path.splitext(baseName)[0] + flatODFTypes[component][0]
329 print("exportToODF " + baseName + " => " + exportFileName)
330 props = [("FilterName", flatODFTypes[component][1]),
331 ("Overwrite", True)]
332 storeProps = tuple([mkPropertyValue(name, value) for (name, value) in props])
333 xDoc.storeToURL(exportFileName, tuple(storeProps))
335 def handleCrash(file, disposed):
336 # print("File: " + file + " crashed")
337 with open("crashlog.txt", "a") as crashLog:
338 crashLog.write('Crash:' + file + ' ')
339 if disposed == 1:
340 crashLog.write('through disposed\n')
341 # crashed_files.append(file)
342 # add here the remaining handling code for crashed files
344 def alarm_handler(args):
345 args.kill()
347 class HandleFileTest:
348 def __init__(self, file, state, component):
349 self.file = file
350 self.state = state
351 self.component = component
352 def run(self, xContext, connection):
353 # print("Loading document: " + self.file)
354 t = None
355 args = None
356 try:
357 url = "file://" + quote(self.file)
358 with open("file.log", "a") as fh:
359 fh.write(url + "\n")
360 xDoc = None
361 args = [connection]
362 t = threading.Timer(60, alarm_handler, args)
363 t.start()
364 xDoc = loadFromURL(xContext, url, t, self.component)
365 self.state.goodFiles.append(self.file)
366 exportToODF(xContext, xDoc, os.path.basename(urllib.parse.urlparse(url).path), t, self.component)
367 except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"):
368 # print("caught UnknownPropertyException " + self.file)
369 if not t.is_alive():
370 # print("TIMEOUT!")
371 self.state.timeoutFiles.append(self.file)
372 else:
373 t.cancel()
374 handleCrash(self.file, 0)
375 self.state.badPropertyFiles.append(self.file)
376 connection.tearDown()
377 connection.setUp()
378 except pyuno.getClass("com.sun.star.lang.DisposedException"):
379 # print("caught DisposedException " + self.file)
380 if not t.is_alive():
381 # print("TIMEOUT!")
382 self.state.timeoutFiles.append(self.file)
383 else:
384 t.cancel()
385 handleCrash(self.file, 1)
386 self.state.badDisposedFiles.append(self.file)
387 connection.tearDown()
388 connection.setUp()
389 finally:
390 if t.is_alive():
391 t.cancel()
392 try:
393 if xDoc:
394 t = threading.Timer(10, alarm_handler, args)
395 t.start()
396 xDoc.close(True)
397 t.cancel()
398 except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"):
399 print("caught UnknownPropertyException while closing")
400 self.state.badPropertyFiles.append(self.file)
401 connection.tearDown()
402 connection.setUp()
403 except pyuno.getClass("com.sun.star.lang.DisposedException"):
404 print("caught DisposedException while closing")
405 if t.is_alive():
406 t.cancel()
407 else:
408 self.state.badDisposedFiles.append(self.file)
409 connection.tearDown()
410 connection.setUp()
411 # print("...done with: " + self.file)
413 class State:
414 def __init__(self):
415 self.goodFiles = []
416 self.badDisposedFiles = []
417 self.badPropertyFiles = []
418 self.timeoutFiles = []
421 def write_state_report(files_list, start_time, report_filename, description):
422 with open(report_filename, "w") as fh:
423 fh.write("%s:\n" % description)
424 fh.write("Starttime: %s\n" % start_time.isoformat())
425 for f in files_list:
426 fh.write("%s\n" % f)
429 def writeReport(state, startTime):
430 write_state_report(state.goodFiles, startTime, "goodFiles.log",
431 "Files which loaded perfectly")
432 write_state_report(state.badDisposedFiles, startTime, "badDisposedFiles.log",
433 "Files which crashed with DisposedException")
434 write_state_report(state.badPropertyFiles, startTime, "badPropertyFiles.log",
435 "Files which crashed with UnknownPropertyException")
436 write_state_report(state.timeoutFiles, startTime, "timeoutFiles.log",
437 "Files which timed out")
439 def runHandleFileTests(opts):
440 startTime = datetime.datetime.now()
441 connection = PersistentConnection(opts)
442 global outdir
443 outdir = os.path.join(opts.outdir, startTime.strftime('%Y%m%d.%H%M%S'))
444 try:
445 tests = []
446 state = State()
447 # print("before map")
448 for component, validExtension in validFileExtensions.items():
449 files = []
450 for suffix in validExtension:
451 files.extend(getFiles(opts.dirs, suffix))
452 files.sort()
453 tests.extend( (HandleFileTest(file, state, component) for file in files) )
454 runConnectionTests(connection, simpleInvoke, tests)
455 finally:
456 connection.kill()
457 writeReport(state, startTime)
459 def parseArgs(argv):
460 epilog = "'location' is a pathname, not a URL. 'outdir' and 'userdir' are URLs.\n" \
461 "The 'directory' parameters should be full absolute pathnames, not URLs."
463 parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
464 epilog=epilog)
465 parser.add_argument('--soffice', metavar='method:location', required=True,
466 help="specify soffice instance to connect to\n"
467 "supported methods: 'path', 'connect'")
468 parser.add_argument('--outdir', metavar='URL', required=True,
469 help="specify the output directory for flat ODF exports")
470 parser.add_argument('--userdir', metavar='URL',
471 help="specify user installation directory for 'path' method")
472 parser.add_argument('--valgrind', action='store_true',
473 help="pass --valgrind to soffice for 'path' method")
474 parser.add_argument('dirs', metavar='directory', nargs='+')
476 args = parser.parse_args(argv[1:])
478 return args
481 if __name__ == "__main__":
482 opts = parseArgs(sys.argv)
483 runHandleFileTests(opts)
485 # vim:set shiftwidth=4 softtabstop=4 expandtab: