Have any questions?
+44 1234 567 890
Chapter 4. Monitoring, Multithreading, Scheduling and other Advanced Techniques
4. Monitoring, Multithreading, Scheduling and other Advanced Techniques
In this chapter we are covering several advanced scripting techniques such as how to monitor script executions (and possibly stop the execution of a script), how to work with multiple threads, how to use the registry to temporarily or persistently store values and how to schedule scripts. Let us start by looking at how ReportServer executes scripts via the terminal.
4.1. Execution of Scripts
Whenever you execute a script via the terminal command exec the script execution is wrapped into a new thread. This allows to monitor the execution and if necessary stop the script. You can display a list of the currently running scripts via the terminal command ps.
The following script doesn't do much, but it needs almost one minute for it:
import java.lang.Thread
Thread.sleep(60000)
"awake again"
If you run this script, the Terminal window will remain inactive during the time of execution. You can either open a second terminal window (CTRL+ALT+T), or wait for the script to return and then run it using the silent flag:
exec -s sleep.groovy
The silent flag tells ReportServer to ignore the output of the script and run it in the background.
Now, by using the command "ps" you can view executions which are presently active.
reportserver$ ps
ID Date User Command Thread Interrupted
1 03.05.13 16:23 3 exec -s sleep.groovy false
By entering the kill command, you can cancel scripts. Here, first an interrupt will be sent to the script which in our case leads to sending the thread an interrupt. In our case we can interrupt the script as follows.
reportserver$ kill 1
When you call "ps" again you will see that the script was interrupted indeed. The following script, however, will not be terminated that easily. It catches any exception and thus also exceptions thrown on interrupt.
import java.lang.Thread
def slept = false;
while(! slept){
try{
Thread.sleep(60000)
slept = true;
} catch(all) {
}
}
"done"
If you run this script and try to terminate it using "kill ID", you will see that the script is still listed by the "ps" command. Note that the flag "interrupted" is now set.
reportserver$ ps
ID Date User Command Thread Interrupted
7 03.05.13 16:23 3 exec -s sleep.groovy true
To hard-terminate the execution you can enter kill -f ID. This will terminate the thread by Thread.stop(). Please keep in mind that this may have undesirable side effects. You will find a description of the Thread.stop() method and related issues under http://docs.oracle.com/javase/7/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html.
Note that scripts which are executed not via the terminal but, for example, during a script report execution or via the scheduler will not run in an extra thread. They will, thus, also not be listed by the ps command.
4.1.1. Starting scripts in the server thread
In some situations it might be helpful to avoid starting a script in an own thread, but to start it in the server thread. This means, however, that this script cannot be monitored and interrupted by "ps/kill". To start a script without an own thread use the -n flag.
4.2. Storing Values Between Scripts
When a script terminates its scope is cleared and any variables are lost. Sometimes it is helpful to store values which can then be retrieved at a later point, for example, by a different script. ReportServer provides script developers with two mechanisms to easily store and retrieve values at a later point. The script registry is kept in memory by ReportServer at all times and, thus, allows for very fast access. However, when ReportServer is restarted the registry is cleared. To store values persistently, ReportServer offers the so called PropertiesService. Values stored via this service will be pushed to the database.
The registry is available via the GLOBALS object. Consider the following script registryCnt.groovy.
def registry = GLOBALS.services['registry']
def cnt = registry.containsKey("myCnt") ? registry.get("myCnt") : 0
cnt++;
registry.put("myCnt", cnt)
"The count is at: " + cnt
Executing this script multiple times will yield the following output
reportserver$ exec registryCnt.groovy
The count is at: 1
reportserver$ exec registryCnt.groovy
The count is at: 2
reportserver$ exec registryCnt.groovy
The count is at: 3
The registry implements java.util.Map<String,Object>. You can find detailed documentation on the various methods at http://docs.oracle.com/javase/7/docs/api/java/util/Map.html.
- Note you can list, add and modify all entries in the registry with the registry terminal command. You can find more information on the Admin Guide: https://reportserver.net/en/guides/admin/main/.
To store values persistently you need to use the PropertiesService (net.datenwerke.gf.service.properties.PropertiesService: https://reportserver.net/api/latest/javadoc/net/datenwerke/gf/service/properties/PropertiesService.html) which is a regular ReportServer service. Let us implement our same counter script as persistentCnt.groovy.
import net.datenwerke.gf.service.properties.PropertiesService
def registry = GLOBALS.getInstance(PropertiesService)
def cnt = registry.containsKey('myCnt') ? registry.get('myCnt') as int : 0
cnt++
registry.setProperty('myCnt', cnt as String)
"The count is at: $cnt"
Note the as int and as String. The PropertiesService stores all its properties in form of character strings. If we execute the script, we get the following output.
reportserver$ exec persistentCnt.groovy
The count is at: 1
reportserver$ exec persistentCnt.groovy
The count is at: 1
reportserver$ exec persistentCnt.groovy
The count is at: 1
This is not quite as expected. If we have a look at the corresponding database table RS_PROPERTY you will see that the table is still empty. Alternatively, you can use the following terminal command
desc Property "hql:from Property"
which lists all entities of type Property. The reason is simple. We did not run the exec command in commit mode and, thus, the property change was not persisted. If we run the command again, this time with the -c flag, we get the following output.
reportserver$ exec -c persistentCnt.groovy
The count is at: 1
reportserver$ exec -c persistentCnt.groovy
The count is at: 2
reportserver$ exec -c persistentCnt.groovy
The count is at: 3
We can now also inspect the property using the desc command from before, which now yields:
d key version value
1 myCnt 2 3
Note you can list, add and modify all entries in the properties mapping with the properties terminal command. You can find more information on the Admin Guide: https://reportserver.net/en/guides/admin/main/.
4.3. Multithreading in Scripts
Sometimes it can be convenient to have work done in parallel. The common way to do this in Groovy or Java is to conjure up multiple threads that do the actual work and run them in parallel. Usually you would want to have the threads controlled by a thread pool. For this ReportServer provides an easy mechanism. The following script is a simple example of how to create thread pools using ReportServer's DwAsyncService (net.datenwerke.async.DwAsyncService):
import net.datenwerke.async.DwAsyncService
import net.datenwerke.async.configurations.*
def aService = GLOBALS.getInstance(DwAsyncService)
def poolName = 'myPool'
// get pool
def pool = aService.initPool(poolName, new SingleThreadPoolConfig())
def runA = {
(1..10).each{
tout.println "A says: $it"
Thread.sleep(200)
}
} as Runnable
def runB = {
(1..10).each{
tout.println "B says: $it"
Thread.sleep(200)
}
} as Runnable
def futureA = pool.submit(runA)
def futureB = pool.submit(runB)
// wait for tasks
futureA.get()
futureB.get()
aService.shutdownPool(poolName)
The initPool method takes a name and a configuration and creates a new pool for the given configuration. If a pool with the same name already exists, it will first shutdown the old pool. If we run this script we get the following result:
reportserver$ exec threadExample.groovy
A says: 1
A says: 2
A says: 3
A says: 4
A says: 5
A says: 6
A says: 7
A says: 8
A says: 9
A says: 10
B says: 1
B says: 2
B says: 3
B says: 4
B says: 5
B says: 6
B says: 7
B says: 8
B says: 9
B says: 10
There are two things to note. First, the printlns are not immediately pushed to the client. Currently ReportServer waits until the script terminates before it sends the result (which includes printlns) to the client. Secondly, the script executed the two loops not in parallel, but consecutively. This is, because of our choice of thread pool. We used a SingleThreadPoolConfig, which constructs a thread pool consisting of a single thread. Besides the SingleThreadPoolConfig you can use a FixedThreadPoolConfig which generates a pool with a fixed size. Thus, if we exchange the SingleThreadPoolConfig by
new FixedThreadPoolConfig(2)
and rerun our program, we get the following (expected) output:
reportserver$ exec threadExample.groovy
A says: 1
B says: 1
B says: 2
A says: 2
A says: 3
B says: 3
B says: 4
A says: 4
B says: 5
A says: 5
B says: 6
A says: 6
A says: 7
B says: 7
B says: 8
A says: 8
B says: 9
A says: 9
B says: 10
A says: 10
4.4. Scheduling Scripts
The ReportServer scheduler is not only used to schedule reports, but it can also be used to schedule the execution of scripts. To schedule a script use the terminal command scheduleScript. It comes with two commands:
list | Lists all scheduled scripts |
execute | Schedules a new script |
To schedule a script you can use natural language expressions. Examples are
scheduleScript execute myScript.groovy " " today at 15:23
scheduleScript execute myScript.groovy " " every day at 15:23
scheduleScript execute myScript.groovy " " at 23.08.2012 15:23
scheduleScript execute myScript.groovy " " every workday at 15:23 starting on 15.03.2011 for 10 times
scheduleScript execute myScript.groovy " " every hour at 23 for 10 times
scheduleScript execute myScript.groovy " " today between 16:00 and 23:00 every 10 minutes
scheduleScript execute myScript.groovy " " every week on monday and wednesday at 23:12 starting on 27.09.2011 until 28.11.2012
scheduleScript execute myScript.groovy " " every month on day 2 at 12:12 starting on 27.09.2011 11:25 for 2 times
The " " (quotation marks) after the script name are the scripts arguments. If we schedule our previous persistentCnt.groovy script we get the following output
scheduleScript execute persistentCnt.groovy " " every hour at 23 for 10 times
Script persistentCnt.groovy scheduled. First execution: 13.12.2013 19:23:00
Via the scheduleScript list command you get an overview of all currently scheduled scripts:
reportserver$ scheduleScript list
id scriptId name next firetime
1 573 persistentCnt.groovy 13.12.2013 19:23:00
To get an overview of the next fire times you can use the scheduler command.
reportserver$ scheduler listFireTimes 1
13.12.2013 19:23:00
13.12.2013 20:23:00
13.12.2013 21:23:00
13.12.2013 22:23:00
13.12.2013 23:23:00
14.12.2013 19:23:00
14.12.2013 20:23:00
14.12.2013 21:23:00
14.12.2013 22:23:00
14.12.2013 23:23:00
Here 1 denotes the schedule entry's id. The scheduler command can also be used for scheduled reports.
To remove an entry you can use the "scheduler remove" command, for example to remove the above entry you need to run
reportserver$ scheduler remove 1