Monday, April 23, 2007

Run scheduled agent every 60 seconds

The shortest time between executions of a scheduled agent is 5 minutes. But very often you want to run some important function with only 1 minute interval. Your function takes maybe only 2 seconds to run, but you still are required to wait 5 minutes before it can be started again at the next agent invocation.

The agent I created makes it possible to simulate running agent every 60 seconds, or even every 2 seconds if you wish so. Some restrictions apply :)

It uses the fact that Notes counts 5 minutes from the START of the previous instance, not from the END. So if your agent takes 4 minutes 50 seconds to run, the next execution of the agent (assuming perfect conditions) will be just 10 seconds after the previous execution is finished. Delays caused by server load and other unforeseen delays can cause the agent to wait from 30 seconds to 2 minutes until the next execution. During many tests, I havent's observed execution delay longer than 2 minutes.


Even with the worst case 2 minutes delay, it's much better than standard 5 minutes delay. Note that this delay is only for the time between agent executions, within the started agent you can call the function every 2 seconds if you wish.

What if the function takes more than 5 minutes to run? Well, the next instance of the agent will wait until the current instance is finished and then start after a certain delay. In my tests the delay was almost always 2 minutes. The delay if the agent ends 15 seconds BEFORE the 5-minutes period (4m 45s) was according to my tests always 1 minute 5 seconds. These numbers will most certainly be somewhat different on your server.


The main disadvantage of this method is that one of the Agent Manager's threads will be constantly busy, so you would need to increase the number of agent threads to make it possible for other scheduled agents to run too.

To enable your agent to run every 60 seconds, do following:
1) In your existing agent, move the code from Initialize method to MainAction method.
2) Paste the code from the agent below into the Initialize method of your agent.
3) Change variable values as needed.

Agent is configured to execute the MainAction() function every 60 secondss (runinterval), max 100 times (runmaxtimes), during the 5 minute period (agentschedinterval).

Agent execution flow. Click to enlarge.


"Gone in 60 seconds" agent:



Sub Initialize
'Created by Andrei Kouvchinnikov, www.botstation.com

Print "******************* Agent started *******************"

Dim session As New NotesSession
Dim db As NotesDatabase
Dim expectedruntime As Integer 'time which take to run the function. required only for deciding about ending the loop.
Dim runinterval As Integer 'interval (sec) between runs
Dim runmaxtimes As Integer ' max number of times the function will run
Dim runcounter As Integer 'counter of number of times the function run
Dim runstart As Long, runend As Long, rundiff As Long
Dim notimeleft ' loop must be closed now, no time left to run another round
Dim agentstart As Long 'initial time when agent started. required only for deciding about ending the loop.
Dim agentschedule As Integer 'nr of minutes this agent is scheduled to run. required only for deciding about ending the loop.
Dim dynadjust 'instead of hardcoded runtime value, use last run period for calculation of next period
Dim uselongestperiod 'instead of the last run period, use the longest period the function took to run

expectedruntime=10 ' function is initially expected to take max 10 seconds
runinterval=60 ' function is called with interval of X seconds
runmaxtimes=100 'if function's run time variates, you can limit max number of times function runs
agentschedinterval=5 'agent is scheduled to run every X minutes. From the agent properties.
margininterval=5 'nr of seconds to add to the final round. used for situations when function's execution time is 0.
runcounter=0
dynadjust=True
uselongestperiod=True

agentstart=Timer

While runcounter<runmaxtimes And notimeleft=False
runstart=Timer

Call MainAction()

runend=Timer
If dynadjust=True Then expectedruntime=Int(runend-runstart) 'dynamically adjust expected time to actual time it take to run the function
If uselongestperiod Then
If expectedruntime<Int(runend-runstart) Then expectedruntime=Int(runend-runstart)
End If
timeleft=runinterval-expectedruntime

If Int((agentschedinterval*60)-Int(Timer-agentstart))<timeleft+(expectedruntime+Int(expectedruntime/100*30)+margininterval) Then 'assumes that function can take 30% longer time to run than the last time
notimeleft=True
Print "Exit. Function will not manage to finish one more run. Computed time: "+Cstr(Now)+" + "+Cstr((expectedruntime+Int(expectedruntime/100*30)))
End If

If timeleft>0 And notimeleft=False Then
Sleep timeleft 'finished before the expected time. sleep until next execution.
Else
Sleep Int((agentschedinterval*60)-Int(Timer-agentstart))-margininterval 'sleep X seconds-margin
End If
runcounter=runcounter+1
Wend
Print "******************* Agent finished *******************"
End Sub


Sub MainAction()
Print "Triggered "+Cstr(Now)

%REM
Here goes your code
Delete demo code below.
%END REM


Randomize
sleeprand=Int(Rnd()*10)
Sleep sleeprand 'Simulates time taken by the function's operations by sleeping X seconds

End Sub





This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.


Output from the agent. Note the 1m 5s delay between instances.



Technorati:

10 comments:

Anonymous said...

We can set the schedule option as "More than once a day" and ru agent every one minute ie. 60sec

Anonymous said...

You can type "1" in the minutes interval field in agent's scheduling properties, but it will be changed to "5" automatically by client/server.
Even if client/server does not change "1" to "5" (by using voodoo API mad l33t hax0r skillz), the agent will not run with 1 minute interval anyway, in the best case it will run with 5 minutes interval.
So unfortunately the type of solution described in the post is the only pure Domino solution.
You can also trigger agent every 1 minute using external Java/Windows program or calls to agent's URL, but that's another topic.

Anonymous said...

Your idea to schedule agent is very nice, but you wrote yourself, that the main disadvantage of this method is that one of the Agent Manager's threads will be constantly busy.

I have another idea to schedule an agent and you can set the delaytime e.g. to 10 seconds.

The idea:
1. Create an mail-in Database Document (e.g. mail-in name=Agentmail and the filename=yourdatabase.nsf

2. Create an BEFORE_MAIL_DELIVERY Agent "ControlAgent". To start the control-loop you only have to send a mail to "Agentmail".

The ControlAgent itself send a mail to "Agentmail" and in the subject field there are all the parameters, e.g. the last run from your productivity agent. When the current time is greater than the time you store in the subject field the ControlAgent starts your productivity agent with sendconsoleCommand "tell amgr run ..."

3. In my solution I have the parameter:
- counter (current)
- cycles (max. / 0=permanent)
- delayBetweenJobs
- mailaddress

Of course, the incoming mail will not be stored.

If you need detail information, please give me an answer here.

Best regards
Didi

Andrei Kouvchinnikov said...

Hi Didi. Let's say that you want to run the agent every 150 seconds. So you receive an email with "150 seconds" (or time value) as parameter. The last time agent run was 5 seconds ago. What is your code doing then? Sleeping 150 seconds and then sending console command and next mail to itself, or is it going into email loop and sends like 1 mail per second to itself until the specified time matches?

/Andrei

Anonymous said...

Hi Andrei,

the "lastJobTime" is a parameter in the email and the SendConsoleCommand is triggered by the difference between the "lastJobTime" and "now".


If difference>=delayBetweenJobs Then
lastJobTime=Cstr(Now)
Call session.SendConsoleCommand(<< ....... tell amgr run ...... >>)

else

'all parameters in field "subject"
'delimiter is "!@!"

maildoc.SendTo = mailaddressWatchDoc
maildoc.Subject = lastJobTime & "!@!" & Cstr(counter) & "!@!" & Cstr(cycles) & "!@!" & Cstr(delayBetweenJobs) & "!@!" & Cstr(delaySleep) & "!@!" & mailaddressWatchDoc

Call maildoc.Send( False )

end if

Didi

Anonymous said...

I like the idea. On going through the article, I thought it may be possible to implement this simpler: Say we estimate the agent we want to run to take 10 seconds per run. Have an agent that calls the agent we want to run repeatedly (with some sleep time), until it is about 4.30 minutes since the start of the calling agent.

A crude solution since it is not exactly a uniform interval, but easier to implement I thought. Any problems in this, except of course the need for an almost dedicated Agent Manager Thread?

Anonymous said...

Beautifull code, just what we needed !

Best World of Warcraft Gold Guide said...

great post man :)

Anonymous said...

Let's choose a glasses frames
do you want a pair of prescription eyeglasses.
Buy progressive glasses.

nurmanelis said...

hai.. ohhh it was incredible tool ... I was frankly very surprised and awesome .. taste in my country there? ... I wanted to follow up on the job ... ((once again good and I'm proud of such a tool maker) OCTOBER FALL Ι LOMBOK &BALI ISLAND