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