Branch libreoffice-5-0-4
[LibreOffice.git] / bin / benchmark-document-loading
blob6d06580929c8210f88837a867910ab9a0ddc27e0
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 datetime
36 import getopt
37 import os
38 import subprocess
39 import sys
40 import time
41 import urllib
42 import uuid
44 import signal
45 import threading
46 try:
47 from urllib.parse import quote
48 except ImportError:
49 from urllib import quote
51 try:
52 import pyuno
53 import uno
54 import unohelper
55 except ImportError:
56 print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables")
57 print("PYTHONPATH=/installation/opt/program")
58 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
59 raise
61 try:
62 from com.sun.star.beans import PropertyValue
63 from com.sun.star.document import XDocumentEventListener
64 from com.sun.star.io import IOException, XOutputStream
65 except ImportError:
66 print("UNO API class not found: try to set URE_BOOTSTRAP variable")
67 print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc")
68 raise
70 validCalcFileExtensions = [ ".xlsx", ".xls", ".ods", ".fods" ]
71 validWriterFileExtensions = [ ".docx" , ".rtf", ".odt", ".fodt", ".doc" ]
72 validImpressFileExtensions = [ ".ppt", ".pptx", ".odp", ".fodp" ]
73 validDrawFileExtensions = [ ".odg", ".fodg" ]
74 validRevereseFileExtensions = [ ".vsd", ".vdx", ".cdr", ".pub", ".wpd" ]
75 validFileExtensions = dict([("calc", validCalcFileExtensions), ("writer", validWriterFileExtensions), ("impress", validImpressFileExtensions), ("draw", validDrawFileExtensions), ("reverse", validRevereseFileExtensions) ])
76 flatODFTypes = dict([("calc", (".fods", "OpenDocument Spreadsheet Flat XML")),
77 ("writer", (".fodt", "OpenDocument Text Flat XML")),
78 ("impress", (".fodp", "OpenDocument Presentation Flat XML")),
79 ("draw", (".fodg", "OpenDocument Drawing Flat XML"))])
81 outdir = ""
83 def partition(list, pred):
84 left = []
85 right = []
86 for e in list:
87 if pred(e):
88 left.append(e)
89 else:
90 right.append(e)
91 return (left, right)
93 def filelist(dir, suffix):
94 if len(dir) == 0:
95 raise Exception("filelist: empty directory")
96 if not(dir[-1] == "/"):
97 dir += "/"
98 files = [dir + f for f in os.listdir(dir)]
99 # print(files)
100 return [f for f in files
101 if os.path.isfile(f) and os.path.splitext(f)[1] == suffix]
103 def getFiles(dirs, suffix):
104 # print( dirs )
105 files = []
106 for dir in dirs:
107 files += filelist(dir, suffix)
108 return files
110 ### UNO utilities ###
112 class OutputStream( unohelper.Base, XOutputStream ):
113 def __init__( self ):
114 self.closed = 0
116 def closeOutput(self):
117 self.closed = 1
119 def writeBytes( self, seq ):
120 sys.stdout.write( seq.value )
122 def flush( self ):
123 pass
125 class OfficeConnection:
126 def __init__(self, args):
127 self.args = args
128 self.soffice = None
129 self.socket = None
130 self.xContext = None
131 self.pro = None
132 def setUp(self):
133 (method, sep, rest) = self.args["--soffice"].partition(":")
134 if sep != ":":
135 raise Exception("soffice parameter does not specify method")
136 if method == "path":
137 socket = "pipe,name=pytest" + str(uuid.uuid1())
138 try:
139 userdir = self.args["--userdir"]
140 except KeyError:
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 "--valgrind" in self.args:
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 killFile = open("killFile.log", "a")
205 killFile.write(command + "\n")
206 killFile.close()
207 # print("kill")
208 # print(command)
209 os.system(command)
211 class PersistentConnection:
212 def __init__(self, args):
213 self.args = args
214 self.connection = None
215 def getContext(self):
216 return self.connection.xContext
217 def setUp(self):
218 assert(not self.connection)
219 conn = OfficeConnection(self.args)
220 conn.setUp()
221 self.connection = conn
222 def preTest(self):
223 assert(self.connection)
224 def postTest(self):
225 assert(self.connection)
226 def tearDown(self):
227 if self.connection:
228 try:
229 self.connection.tearDown()
230 finally:
231 self.connection = None
232 def kill(self):
233 if self.connection:
234 self.connection.kill()
236 def simpleInvoke(connection, test):
237 try:
238 connection.preTest()
239 test.run(connection.getContext(), connection)
240 finally:
241 connection.postTest()
243 def runConnectionTests(connection, invoker, tests):
244 try:
245 connection.setUp()
246 for test in tests:
247 invoker(connection, test)
248 finally:
249 pass
250 #connection.tearDown()
252 class EventListener(XDocumentEventListener,unohelper.Base):
253 def __init__(self):
254 self.layoutFinished = False
255 def documentEventOccured(self, event):
256 # print(str(event.EventName))
257 if event.EventName == "OnLayoutFinished":
258 self.layoutFinished = True
259 def disposing(event):
260 pass
262 def mkPropertyValue(name, value):
263 return uno.createUnoStruct("com.sun.star.beans.PropertyValue",
264 name, 0, value, 0)
266 ### tests ###
268 def logTimeSpent(url, startTime):
269 print(os.path.basename(urllib.parse.urlparse(url).path) + "\t" + str(time.time()-startTime))
271 def loadFromURL(xContext, url, t, component):
272 xDesktop = xContext.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", xContext)
273 props = [("Hidden", True), ("ReadOnly", True)] # FilterName?
274 loadProps = tuple([mkPropertyValue(name, value) for (name, value) in props])
275 xListener = None
276 if component == "writer":
277 xListener = EventListener()
278 xGEB = xContext.getValueByName(
279 "/singletons/com.sun.star.frame.theGlobalEventBroadcaster")
280 xGEB.addDocumentEventListener(xListener)
281 try:
282 xDoc = None
283 startTime = time.time()
284 xDoc = xDesktop.loadComponentFromURL(url, "_blank", 0, loadProps)
285 if component == "calc":
286 try:
287 if xDoc:
288 xDoc.calculateAll()
289 except AttributeError:
290 pass
291 t.cancel()
292 logTimeSpent(url, startTime)
293 return xDoc
294 elif component == "writer":
295 time_ = 0
296 t.cancel()
297 while time_ < 30:
298 if xListener.layoutFinished:
299 logTimeSpent(url, startTime)
300 return xDoc
301 # print("delaying...")
302 time_ += 1
303 time.sleep(1)
304 else:
305 t.cancel()
306 logTimeSpent(uri, startTime)
307 return xDoc
308 file = open("file.log", "a")
309 file.write("layout did not finish\n")
310 file.close()
311 return xDoc
312 except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"):
313 xListener = None
314 raise # means crashed, handle it later
315 except pyuno.getClass("com.sun.star.lang.DisposedException"):
316 xListener = None
317 raise # means crashed, handle it later
318 except pyuno.getClass("com.sun.star.lang.IllegalArgumentException"):
319 pass # means could not open the file, ignore it
320 except:
321 if xDoc:
322 # print("CLOSING")
323 xDoc.close(True)
324 raise
325 finally:
326 if xListener:
327 xGEB.removeDocumentEventListener(xListener)
329 def exportToODF(xContext, xDoc, baseName, t, component):
330 exportFileName = outdir + "/" + os.path.splitext(baseName)[0] + flatODFTypes[component][0]
331 print("exportToODF " + baseName + " => " + exportFileName)
332 props = [("FilterName", flatODFTypes[component][1]),
333 ("Overwrite", True)]
334 storeProps = tuple([mkPropertyValue(name, value) for (name, value) in props])
335 xDoc.storeToURL(exportFileName, tuple(storeProps))
337 def handleCrash(file, disposed):
338 # print("File: " + file + " crashed")
339 crashLog = open("crashlog.txt", "a")
340 crashLog.write('Crash:' + file + ' ')
341 if disposed == 1:
342 crashLog.write('through disposed\n')
343 crashLog.close()
344 # crashed_files.append(file)
345 # add here the remaining handling code for crashed files
347 def alarm_handler(args):
348 args.kill()
350 class HandleFileTest:
351 def __init__(self, file, state, component):
352 self.file = file
353 self.state = state
354 self.component = component
355 def run(self, xContext, connection):
356 # print("Loading document: " + self.file)
357 t = None
358 args = None
359 try:
360 url = "file://" + quote(self.file)
361 file = open("file.log", "a")
362 file.write(url + "\n")
363 file.close()
364 xDoc = None
365 args = [connection]
366 t = threading.Timer(60, alarm_handler, args)
367 t.start()
368 xDoc = loadFromURL(xContext, url, t, self.component)
369 self.state.goodFiles.append(self.file)
370 exportToODF(xContext, xDoc, os.path.basename(urllib.parse.urlparse(url).path), t, self.component)
371 except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"):
372 # print("caught UnknownPropertyException " + self.file)
373 if not t.is_alive():
374 # print("TIMEOUT!")
375 self.state.timeoutFiles.append(self.file)
376 else:
377 t.cancel()
378 handleCrash(self.file, 0)
379 self.state.badPropertyFiles.append(self.file)
380 connection.tearDown()
381 connection.setUp()
382 except pyuno.getClass("com.sun.star.lang.DisposedException"):
383 # print("caught DisposedException " + self.file)
384 if not t.is_alive():
385 # print("TIMEOUT!")
386 self.state.timeoutFiles.append(self.file)
387 else:
388 t.cancel()
389 handleCrash(self.file, 1)
390 self.state.badDisposedFiles.append(self.file)
391 connection.tearDown()
392 connection.setUp()
393 finally:
394 if t.is_alive():
395 t.cancel()
396 try:
397 if xDoc:
398 t = threading.Timer(10, alarm_handler, args)
399 t.start()
400 xDoc.close(True)
401 t.cancel()
402 except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"):
403 print("caught UnknownPropertyException while closing")
404 self.state.badPropertyFiles.append(self.file)
405 connection.tearDown()
406 connection.setUp()
407 except pyuno.getClass("com.sun.star.lang.DisposedException"):
408 print("caught DisposedException while closing")
409 if t.is_alive():
410 t.cancel()
411 else:
412 self.state.badDisposedFiles.append(self.file)
413 connection.tearDown()
414 connection.setUp()
415 # print("...done with: " + self.file)
417 class State:
418 def __init__(self):
419 self.goodFiles = []
420 self.badDisposedFiles = []
421 self.badPropertyFiles = []
422 self.timeoutFiles = []
425 def writeReport(state, startTime):
426 goodFiles = open("goodFiles.log", "w")
427 goodFiles.write("Files which loaded perfectly:\n")
428 goodFiles.write("Starttime: " + startTime.isoformat() +"\n")
429 for file in state.goodFiles:
430 goodFiles.write(file)
431 goodFiles.write("\n")
432 goodFiles.close()
433 badDisposedFiles = open("badDisposedFiles.log", "w")
434 badDisposedFiles.write("Files which crashed with DisposedException:\n")
435 badDisposedFiles.write("Starttime: " + startTime.isoformat() + "\n")
436 for file in state.badDisposedFiles:
437 badDisposedFiles.write(file)
438 badDisposedFiles.write("\n")
439 badDisposedFiles.close()
440 badPropertyFiles = open("badPropertyFiles.log", "w")
441 badPropertyFiles.write("Files which crashed with UnknownPropertyException:\n")
442 badPropertyFiles.write("Starttime: " + startTime.isoformat() + "\n")
443 for file in state.badPropertyFiles:
444 badPropertyFiles.write(file)
445 badPropertyFiles.write("\n")
446 badPropertyFiles.close()
447 timeoutFiles = open("timeoutFiles.log", "w")
448 timeoutFiles.write("Files which timed out:\n")
449 timeoutFiles.write("Starttime: " + startTime.isoformat() + "\n")
450 for file in state.timeoutFiles:
451 timeoutFiles.write(file)
452 timeoutFiles.write("\n")
453 timeoutFiles.close()
455 def runHandleFileTests(opts, dirs):
456 startTime = datetime.datetime.now()
457 connection = PersistentConnection(opts)
458 global outdir
459 outdir = opts["--outdir"] + "/" + startTime.strftime('%Y%m%d.%H%M%S')
460 try:
461 tests = []
462 state = State()
463 # print("before map")
464 for component, validExtension in validFileExtensions.items():
465 files = []
466 for suffix in validExtension:
467 files.extend(getFiles(dirs, suffix))
468 files.sort()
469 tests.extend( (HandleFileTest(file, state, component) for file in files) )
470 runConnectionTests(connection, simpleInvoke, tests)
471 finally:
472 connection.kill()
473 writeReport(state, startTime)
475 def parseArgs(argv):
476 (optlist,args) = getopt.getopt(argv[1:], "hr",
477 ["help", "soffice=", "userdir=", "outdir=", "valgrind"])
478 # print(optlist)
479 return (dict(optlist), args)
481 def usage():
482 message = """usage: {program} [option]... [directory]..."
483 -h | --help: print usage information
484 --soffice=method:location
485 specify soffice instance to connect to
486 supported methods: 'path', 'connect'
487 --outdir=URL specify the output directory for flat ODF exports
488 --userdir=URL specify user installation directory for 'path' method
489 --valgrind pass --valgrind to soffice for 'path' method
491 'location' is a pathname, not a URL. 'outdir' and 'userdir' are URLs.
492 The 'directory' parameters should be full absolute pathnames, not URLs."""
493 print(message.format(program = os.path.basename(sys.argv[0])))
496 if __name__ == "__main__":
497 (opts,args) = parseArgs(sys.argv)
498 if len(args) == 0:
499 usage()
500 sys.exit(1)
501 if "-h" in opts or "--help" in opts:
502 usage()
503 sys.exit()
504 elif "--soffice" in opts and "--outdir" in opts:
505 runHandleFileTests(opts, args)
506 else:
507 usage()
508 sys.exit(1)
510 # vim:set shiftwidth=4 softtabstop=4 expandtab: