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