1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 package complex.memCheck; 23 24 import com.sun.star.beans.PropertyValue; 25 import com.sun.star.frame.XStorable; 26 import com.sun.star.lang.XComponent; 27 import com.sun.star.lang.XMultiServiceFactory; 28 import com.sun.star.uno.UnoRuntime; 29 import com.sun.star.util.XCloseable; 30 import helper.OSHelper; 31 import helper.ProcessHandler; 32 import java.io.BufferedReader; 33 import java.io.File; 34 import java.io.FileInputStream; 35 // import java.io.FilePermission; 36 import java.io.FileWriter; 37 import java.io.FilenameFilter; 38 import java.io.InputStreamReader; 39 import java.io.PrintWriter; 40 import java.util.Enumeration; 41 import java.util.Properties; 42 import java.util.StringTokenizer; 43 import java.util.Vector; 44 import util.DesktopTools; 45 // import util.WriterTools; 46 47 import org.junit.After; 48 import org.junit.AfterClass; 49 import org.junit.Before; 50 import org.junit.BeforeClass; 51 import org.junit.Test; 52 import org.openoffice.test.Argument; 53 import org.openoffice.test.OfficeConnection; 54 import static org.junit.Assert.*; 55 56 /** 57 * Documents are opened and exported with OpenOffice. The memory usage of 58 * OpenOffice is monitored and if the usage exceeds the allowed kilobytes, 59 * the test is failed. Used for monitoring the OpenOffice process is the 60 * command line tool 'pmap', available on Solaris or Linux. This test will not 61 * run on Windows.<br>Test procedure: every given document type is searched in 62 * the source directory 63 * Needed parameters: 64 * <ul> 65 * <li>"AllowMemoryIncrease" (optional) - the allowed memory increase measured in kByte per exported document. The default is 10 kByte.</li> 66 * <li>"ExportDocCount" (optional) - the amount of exports for each document that is loaded. Is defaulted to 25. 67 * <li>"FileExportFilter" (optional) - a relation between loaded document type and used export filter. Is defaulted to 68 * writer, calc and impress. This parameter can be set with a number to give more than one relation. Example:<br> 69 * "FileExportFilter1=sxw,writer_pdf_Export"<br> 70 * "FileExportFilter2=sxc,calc_pdf_Export"<br> 71 * "FileExportFilter3=sxi,impress_pdf_Export"<br></li> 72 * All parameters are used for iteration over the test document path. 73 * </ul> 74 */ 75 class TempDir 76 { 77 78 private String m_sTempDir; 79 TempDir(String _sTempDir)80 public TempDir(String _sTempDir) 81 { 82 m_sTempDir = _sTempDir; 83 } 84 getOfficeTempDir()85 public String getOfficeTempDir() 86 { 87 return m_sTempDir; 88 } 89 getTempDir()90 public String getTempDir() 91 { 92 final String sTempDir = FileHelper.getJavaCompatibleFilename(m_sTempDir); 93 return sTempDir; 94 } 95 } 96 97 public class CheckMemoryUsage 98 { 99 100 private final String sWriterDoc = "sxw,writer_pdf_Export"; 101 private final String sCalcDoc = "sxc,calc_pdf_Export"; 102 private final String sImpressDoc = "sxi,impress_pdf_Export"; 103 // private String sProcessIdCommand = null; 104 TempDir m_aTempDir; 105 // private String sFS = null; 106 // private String sMemoryMap1 = null; 107 // private String sMemoryMap2 = null; 108 // private String sDocumentPath = ""; 109 private String[][] sDocTypeExportFilter; 110 private String[][] sDocuments; 111 private int iAllowMemoryIncrease = 10; 112 private int iExportDocCount = 25; 113 114 /** 115 * Collect all documents to load and all filters used for export. 116 */ 117 @Before before()118 public void before() throws Exception 119 { 120 121 final XMultiServiceFactory xMsf = getMSF(); 122 123 // test does definitely not run on Windows. 124 if (OSHelper.isWindows()) 125 { 126 System.out.println("Test can only reasonably be executed with a tool that " 127 + "displays the memory usage of StarOffice."); 128 System.out.println("Test does not run on Windows, only on Solaris or Linux."); 129 // in an automatic environment it is better to say, there is no error here. 130 // it is a limitation, but no error. 131 System.exit(0); 132 } 133 134 Properties properties = new Properties(); 135 try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(Argument.get("properties")), "UTF-8"))) { 136 properties.load(reader); 137 } 138 139 // how many times is every document exported. 140 int count = Integer.parseInt(properties.getProperty("ExportDocCount", "0")); 141 if (count != 0) 142 { 143 iExportDocCount = count; 144 } 145 146 // get the temp dir for creating the command scripts. 147 // sTempDir = System.getProperty("java.io.tmpdir"); 148 m_aTempDir = new TempDir(util.utils.getOfficeTemp/*Dir*/(xMsf)); 149 150 // get the file extension, export filter connection 151 Enumeration keys = properties.keys(); 152 Vector<String> v = new Vector<String>(); 153 while (keys.hasMoreElements()) 154 { 155 String key = (String) keys.nextElement(); 156 if (key.startsWith("FileExportFilter")) 157 { 158 v.add((String) properties.get(key)); 159 } 160 } 161 // if no param given, set defaults. 162 if (v.size() == 0) 163 { 164 v.add(sWriterDoc); 165 v.add(sCalcDoc); 166 v.add(sImpressDoc); 167 } 168 // store a file extension 169 sDocTypeExportFilter = new String[v.size()][2]; 170 for (int i = 0; i < v.size(); i++) 171 { 172 // 2do: error routine for wrong given params 173 final String sVContent = v.get(i); 174 StringTokenizer t = new StringTokenizer(sVContent, ","); 175 final String sExt = t.nextToken(); 176 final String sName = t.nextToken(); 177 sDocTypeExportFilter[i][0] = sExt; 178 sDocTypeExportFilter[i][1] = sName; 179 } 180 181 // get files to load and export 182 String sDocumentPath = Argument.get("tdoc"); 183 File f = new File(FileHelper.getJavaCompatibleFilename(sDocumentPath)); 184 sDocuments = new String[sDocTypeExportFilter.length][]; 185 for (int j = 0; j < sDocTypeExportFilter.length; j++) 186 { 187 FileFilter filter = new FileFilter(sDocTypeExportFilter[j][0]); 188 String[] doc = f.list(filter); 189 sDocuments[j] = new String[doc.length]; 190 for (int i = 0; i < doc.length; i++) 191 { 192 // final String sDocument = FileHelper.appendPath(sDocumentPath, doc[i]); 193 // sDocuments[j][i] = utils.getFullURL(sDocuments[j][i]); 194 sDocuments[j][i] = TestDocument.getUrl(sDocumentPath, doc[i]); 195 } 196 } 197 } 198 199 /** 200 * delete all created files on disk 201 */ 202 @After after()203 public void after() 204 { 205 // delete the constructed files. 206 // we don't need to delete anything, all is stored in $USER_TREE 207 // for (int i = 0; i < iExportDocCount; i++) 208 // { 209 // final String sDocumentName = "DocExport" + i + ".pdf"; 210 // final String sFilename = FileHelper.appendPath(m_sTempDir, sDocumentName); 211 // File f = new File(FileHelper.getJavaCompatibleFilename(sFilename)); 212 // f.delete(); 213 // } 214 // File f = new File(sProcessIdCommand); 215 // f.delete(); 216 // f = new File(sOfficeMemoryCommand); 217 // f.delete(); 218 } 219 220 /** 221 * The test function: load documents and save them using the given filters 222 * for each given document type. 223 */ 224 @Test loadAndSaveDocuments()225 public void loadAndSaveDocuments() 226 { 227 int nOk = 0; 228 int nRunThrough = 0; 229 230 // At first: 231 // we load the document, there will be some post work in office like late initialization 232 // we store exact one time the document 233 // so the memory footprint should be right 234 235 // iterate over all document types 236 for (int k = 0; k < sDocTypeExportFilter.length; k++) 237 { 238 // iterate over all documents of this type 239 for (int i = 0; i < sDocuments[k].length; i++) 240 { 241 242 final String sDocument = sDocuments[k][i]; 243 final String sExtension = sDocTypeExportFilter[k][1]; 244 245 // OfficeMemchecker aChecker = new OfficeMemchecker(); 246 // aChecker.setDocumentName(FileHelper.getBasename(sDocument)); 247 // aChecker.setExtension(sExtension); 248 // aChecker.start(); 249 250 loadAndSaveNTimesDocument(sDocument, 1, sExtension); 251 252 // nOk += checkMemory(aChecker); 253 // nRunThrough ++; 254 } 255 System.out.println(); 256 System.out.println(); 257 } 258 259 shortWait(10000); 260 261 // Now the real test, load document and store 25 times 262 263 // iterate over all document types 264 for (int k = 0; k < sDocTypeExportFilter.length; k++) 265 { 266 // iterate over all documents of this type 267 for (int i = 0; i < sDocuments[k].length; i++) 268 { 269 270 final String sDocument = sDocuments[k][i]; 271 final String sExtension = sDocTypeExportFilter[k][1]; 272 273 OfficeMemchecker aChecker = new OfficeMemchecker(); 274 aChecker.setDocumentName(FileHelper.getBasename(sDocument)); 275 aChecker.setExtension(sExtension); 276 aChecker.start(); 277 278 loadAndSaveNTimesDocument(sDocument, iExportDocCount, sExtension); 279 280 aChecker.stop(); 281 final int nConsumMore = aChecker.getConsumMore(); 282 283 nOk += checkMemory(nConsumMore); 284 nRunThrough++; 285 } 286 System.out.println(); 287 System.out.println(); 288 } 289 System.out.println("Find the output of used 'pmap' here: " + m_aTempDir.getTempDir() + " if test failed."); 290 assertTrue("Office consumes too many memory.", nOk == nRunThrough); 291 } 292 293 /** 294 * Checks how much memory should consume 295 * @param storageBefore 296 * @return 1 if consume is ok, else 0 297 */ checkMemory(int nConsumMore)298 private int checkMemory(int nConsumMore) 299 { 300 int nAllowed = iAllowMemoryIncrease * iExportDocCount; 301 System.out.println("The Office consumes now " + nConsumMore 302 + "K more memory than at the start of the test; allowed were " 303 + nAllowed + "K."); 304 if (nConsumMore > nAllowed) 305 { 306 System.out.println("ERROR: This is not allowed."); 307 return 0; 308 } 309 System.out.println("OK."); 310 return 1; 311 } 312 313 /** 314 * load and save exact one document 315 */ loadAndSaveNTimesDocument(String _sDocument, int _nCount, String _sStoreExtension)316 private void loadAndSaveNTimesDocument(String _sDocument, int _nCount, String _sStoreExtension) 317 { 318 System.out.println("Document: " + _sDocument); 319 XComponent xComponent = DesktopTools.loadDoc(getMSF(), _sDocument, null); 320 XStorable xStorable = UnoRuntime.queryInterface(XStorable.class, xComponent); 321 if (xStorable != null) 322 { 323 // export each document iExportDocCount times 324 for (int j = 0; j < _nCount; j++) 325 { 326 final String sDocumentName = FileHelper.getBasename(_sDocument) + "_" + j + ".pdf"; 327 final String sFilename = FileHelper.appendPath(m_aTempDir.getOfficeTempDir(), sDocumentName); 328 // String url = utils.getFullURL(sFilename); 329 String url = sFilename; // graphical.FileHelper.getFileURLFromSystemPath(sFilename); 330 try 331 { 332 PropertyValue[] props = new PropertyValue[1]; 333 props[0] = new PropertyValue(); 334 props[0].Name = "FilterName"; 335 // use export filter for this doc type 336 props[0].Value = _sStoreExtension; 337 xStorable.storeToURL(url, props); 338 } 339 catch (com.sun.star.io.IOException e) 340 { 341 fail("Could not store to '" + url + "'"); 342 } 343 } 344 // close the doc 345 XCloseable xCloseable = UnoRuntime.queryInterface(XCloseable.class, xStorable); 346 try 347 { 348 xCloseable.close(true); 349 } 350 catch (com.sun.star.util.CloseVetoException e) 351 { 352 e.printStackTrace(); 353 fail("Cannot close document: test is futile, Office will surely use more space."); 354 } 355 } 356 else 357 { 358 System.out.println("Cannot query for XStorable interface on document '" + _sDocument + "'"); 359 System.out.println(" -> Skipping storage."); 360 } 361 362 } 363 364 // ----------------------------------------------------------------------------- 365 private class OfficeMemchecker 366 { 367 368 /** 369 * After called start() it contains the memory need at startup 370 */ 371 private int m_nMemoryStart; 372 /** 373 * After called stop() it contains the memory usage 374 */ 375 private int m_nMemoryUsage; 376 private String m_sDocumentName; 377 private String m_sExtension; 378 OfficeMemchecker()379 public OfficeMemchecker() 380 { 381 m_nMemoryStart = 0; 382 } 383 setDocumentName(String _sDocName)384 public void setDocumentName(String _sDocName) 385 { 386 m_sDocumentName = _sDocName; 387 } 388 setExtension(String _sExt)389 public void setExtension(String _sExt) 390 { 391 m_sExtension = _sExt; 392 } 393 start()394 public void start() 395 { 396 m_nMemoryStart = getOfficeMemoryUsage(createModeName("start", 0)); 397 } 398 createModeName(String _sSub, int _nCount)399 private String createModeName(String _sSub, int _nCount) 400 { 401 StringBuffer aBuf = new StringBuffer(); 402 aBuf.append(_sSub); 403 aBuf.append('_').append(m_sDocumentName).append('_').append(m_sExtension); 404 aBuf.append('_').append(_nCount); 405 return aBuf.toString(); 406 } 407 stop()408 public void stop() 409 { 410 // short wait for the office to 'calm down' and free some memory 411 shortWait(20000); 412 // wait until memory is not freed anymore. 413 int storageAfter = getOfficeMemoryUsage(createModeName("stop", 0)); 414 int mem = 0; 415 int count = 0; 416 while (storageAfter != mem && count < 10) 417 { 418 count++; 419 mem = storageAfter; 420 storageAfter = getOfficeMemoryUsage(createModeName("stop", count)); 421 shortWait(1000); 422 } 423 m_nMemoryUsage = (storageAfter - m_nMemoryStart); 424 } 425 getConsumMore()426 public int getConsumMore() 427 { 428 return m_nMemoryUsage; 429 } 430 431 /** 432 * Get the process ID from the Office 433 * @return the Id as String 434 */ getOfficeProcessID()435 private String getOfficeProcessID() 436 { 437 String sProcessIdCommand = FileHelper.appendPath(m_aTempDir.getTempDir(), "getPS"); 438 final String sofficeArg = org.openoffice.test.Argument.get("soffice"); 439 final String sPSGrep = "ps -ef | grep $USER | grep <soffice>.bin | grep -v grep"; 440 final String sProcessId = sPSGrep.replaceAll("<soffice>", FileHelper.getJavaCompatibleFilename(sofficeArg)); 441 442 createExecutableFile(sProcessIdCommand, sProcessId); 443 ProcessHandler processID = new ProcessHandler(sProcessIdCommand); 444 processID.noOutput(); 445 processID.executeSynchronously(); 446 String text = processID.getOutputText(); 447 if (text == null || text.equals("") || text.indexOf(' ') == -1) 448 { 449 fail("Could not determine Office process ID. Check " + sProcessIdCommand); 450 } 451 StringTokenizer aToken = new StringTokenizer(text); 452 // this is not nice, but ps gives the same output on every machine 453 aToken.nextToken(); 454 String id = aToken.nextToken(); 455 return id; 456 } 457 458 /** 459 * Get the memory usage of the Office in KByte. 460 * @return The memory used by the Office. 461 */ getOfficeMemoryUsage(String _sMode)462 private int getOfficeMemoryUsage(String _sMode) 463 { 464 final String sMemoryMonitor = "pmap <processID> |tee <pmapoutputfile> | grep total"; 465 String sOfficeMemoryCommand = null; 466 sOfficeMemoryCommand = FileHelper.appendPath(m_aTempDir.getTempDir(), "getPmap"); 467 // sOfficeMemoryCommand = FileHelper.getJavaCompatibleFilename(sOfficeMemoryCommand); 468 String command = sMemoryMonitor.replaceAll("<processID>", getOfficeProcessID()); 469 String sPmapOutputFile = FileHelper.appendPath(m_aTempDir.getTempDir(), "pmap_" + _sMode + ".txt"); 470 command = command.replaceAll("<pmapoutputfile>", sPmapOutputFile); 471 createExecutableFile(sOfficeMemoryCommand, command); 472 473 ProcessHandler processID = new ProcessHandler(sOfficeMemoryCommand); 474 processID.noOutput(); 475 processID.executeSynchronously(); 476 int nError = processID.getExitCode(); 477 assertTrue("Execute of " + sOfficeMemoryCommand + " failed", nError == 0); 478 String text = processID.getOutputText(); 479 if (text == null || text.equals("") || text.indexOf(' ') == -1) 480 { 481 fail("Could not determine Office memory usage. Check " + sOfficeMemoryCommand); 482 } 483 StringTokenizer aToken = new StringTokenizer(text); 484 // this works, because the output of pmap is quite standardized. 485 aToken.nextToken(); 486 String mem = aToken.nextToken(); 487 mem = mem.substring(0, mem.indexOf('K')); 488 Integer memory = new Integer(mem); 489 return memory.intValue(); 490 } 491 492 /** 493 * Write a script file and set its rights to rwxrwxrwx. 494 * @param fileName The name of the created file 495 * @param line The commandline that has to be written inside of the file. 496 */ createExecutableFile(String fileName, String line)497 private void createExecutableFile(String fileName, String line) 498 { 499 final String sChmod = "chmod a+x "; 500 final String bash = "#!/bin/bash"; 501 502 try 503 { 504 String sFilename = FileHelper.getJavaCompatibleFilename(fileName); 505 PrintWriter fWriter = new PrintWriter(new FileWriter(sFilename)); 506 fWriter.println(bash); 507 fWriter.println(line); 508 fWriter.close(); 509 // change rights to rwxrwxrwx 510 ProcessHandler processID = new ProcessHandler(sChmod + sFilename); 511 processID.noOutput(); 512 processID.executeSynchronously(); 513 int nError = processID.getExitCode(); 514 assertTrue("chmod failed. ", nError == 0); 515 } 516 catch (java.io.IOException e) 517 { 518 } 519 } 520 } 521 522 /** 523 * Let this thread sleep for some time 524 * @param milliSeconds time to wait in milliseconds. 525 */ shortWait(int milliSeconds)526 public static void shortWait(int milliSeconds) 527 { 528 System.out.println("Wait for: " + milliSeconds + "ms"); 529 try 530 { 531 Thread.sleep(milliSeconds); 532 } 533 catch (java.lang.InterruptedException e) 534 { // ignore 535 } 536 } 537 538 /** 539 * Own file filter, will just return ok for all files that end with a given 540 * suffix 541 */ 542 private class FileFilter implements FilenameFilter 543 { 544 545 private String suffix = null; 546 547 /** 548 * C'tor. 549 * @param suffix The suffix each filename should end with. 550 */ FileFilter(String suffix)551 public FileFilter(String suffix) 552 { 553 this.suffix = suffix; 554 } 555 556 /** 557 * Returns true, if the name of the file has the suffix given to the 558 * c'tor. 559 * @param name The filename that is tested. 560 * @param file Not used. 561 * @return True, if name ends with suffix. 562 */ accept(File file, String name)563 public boolean accept(File file, String name) 564 { 565 return name.endsWith(suffix); 566 } 567 } 568 getMSF()569 private XMultiServiceFactory getMSF() 570 { 571 final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager()); 572 return xMSF1; 573 } 574 575 // setup and close connections 576 @BeforeClass setUpConnection()577 public static void setUpConnection() throws Exception 578 { 579 System.out.println("setUpConnection()"); 580 connection.setUp(); 581 } 582 583 @AfterClass tearDownConnection()584 public static void tearDownConnection() 585 throws InterruptedException, com.sun.star.uno.Exception 586 { 587 System.out.println("tearDownConnection()"); 588 connection.tearDown(); 589 } 590 private static final OfficeConnection connection = new OfficeConnection(); 591 } 592