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