Powered by Squarespace

Popular articles:

What am I?

I'm a technology consultant, focused on enterprise collaborative applications. I'm based in North-east Scotland but work all over Europe, and sometimes I present at technology conferences using a mixture of deep-technology and humour to keep the audience awake.

I'm married with one grown-up daughter of whom I'm immensely proud, and have family members covering most significant time-zones. We took 'hide and seek' seriously.

Other interests include making excuses not to go to the gym, testing the tolerance of my peers with humour, and sometimes bringing my 20-year old ZZR 600 out of the garage to terrify myself with.

I've been blogging since it was called 'Talking cr@p on the internet' and at one stage hand-rolled my own blog. Fame and fortune for this minor technological greatness is still 'in the post'.

Enjoy my little outpost on the web and take cheer that it could be worse - I could be a Silverlight consultant...

How to get in touch?

I have a mobile phone number which has been unchanged in 15 years. Most folks have that. Or you can try eMailing me - look at the domain name and take a good guess which will work. Most things will. I'm on Linked-In for the business stuff, Facebook for the personal stuff, and Skype for the face to face stuff. 

I'm a freelance consultant, and I'm engaged by enterprise or government customers. Most of the work I do I cannot speak about, so excuse my somewhat clumsy evasions. 

« London gatwick airport | Main | I love me MiFi »
Tuesday
Feb022010

Universal Agent Profiling in Domino

[Note: I've had this blog entry in draft for ages - sorry to take so long to get this out. Yes, this is the one I mentioned I'd do from the Worst Practices stage. And yes, confusingly I published it without the code. D'oh!]

We've all been there. We've all had a server suddenly and unexpectedly get very slow, odd things happening and so forth. And we've all wanted to know - which agent is causing issues ? Which agents - out of all these databases - are working ? 

Scary. And its a question that can be easily answered.

Firstly, if you have money and want to buy product - go see TeamStudio Profiler. Its a fantastic tool that actually injects code into your lotusscript agents on the fly, and then uses these markers to measure exactly how your agents work. You can actually see how often any particular function is called, and for how long. 

Lets assume that you dont have this, as you have already solved this issue if you have Profiler.

Lets assume that your servers and apps are running Domino 6.x 7.x or above. In v6 v7, we have a fantastic new 'profile this agent' utility. And of course, like all good developers, you've never read the release notes. And like all good admins, you never read developer release notes.

Stick with me folks, this is good.

So - open up an agent, and open agent properties. Open the second tab - you've never done that, have you - and click on 'Profile this agent'. Easy, wasnt it ?

Or if your a DDE chappie, then: 

 

What does this do? Well, this tells agent manager that every time this agent runs, create a profile document in this database specific to this agent and then list all the calls that this agent made to the notes infrastructure. So you can see how long your agent took, and how long all the calls to the Notes infrastructure took. For instance (and this is a particularly complex agent)

Go off and play with this *right now*. Seriously. 

Back again? Now. You can see how cool this is, right. Hows about being able to switch this on for *all* agents ? Well, thats easy. Its just a flag. That is, a simple notes field on the agent design document itself. You can just open up all the agents in Lotuscscript, and set the flag, right ? In particular, you pick up the field $FlagsExt and add or remove a capital-F to the text string (if it exists).

And some code that might switch this on and off looks like this:

Sub Initialize
 
 Dim sSession As New NotesSession
 Dim ui As New NotesUIWorkspace
 
 Dim dbthis As notesDatabase
 Set dbThis = sSession.currentDatabase
 
 ' prompt the user for a target database
 Dim dbPath As Variant
 dbPath = ui.Prompt(13, "Target Database", "Select a target Database")
 
 Dim dbTarget As notesDatabase
 Set dbTarget = sSession.getDatabase(dbPath(0), dbPath(1))
 If (dbTarget Is Nothing) Then Exit Sub
 If Not dbTarget.IsOpen Then Exit Sub
 
 Dim ret As Integer
 ret = ui.prompt(2, "Continue", "Do you want to enable Agent Profiling on all agents in database: " + dbTarget.Title)
 
 If (ret <> 1) Then
  Print "Cancelled"
  Exit Sub
 End If
 
 Dim nn As notesNoteCollection
 
 Set nn = dbTarget.CreateNoteCollection(False)
 nn.SelectAgents = True
 Call nn.BuildCollection()
 
 Dim noteiD As String
 noteID = nn.getFirstNoteID
 
 While noteID <> "" 
  
  Dim docAgent As NotesDocument
  Set docAgent = dbTarget.GetDocumentByID(noteID)
  If Not docAgent Is Nothing Then
   
   Dim itm As NotesItem, dirty As Boolean
   dirty = False
   
   Set itm =docAgent.GetFirstItem("$FlagsExt")
   If (itm Is Nothing) Then
    dirty = True
    Set itm = docAgent.replaceItemValue("$FlagsExt", "F")
   End If
   
   If (Instr(itm.Text, "F") < 1) Then
    dirty = True
    Set itm = docAgent.replaceItemValue("$FlagsExt", itm.text + "F") 
   End If
   
   If (dirty) Then
    itm.IsSigned = True
    Print "Saving agent: " + docAgent.getitemvalue("$Title")(0)
    Call docAgent.Sign()
    Call docAgent.Save(False, False)
   End If
  End If
  
  noteID = nn.GetNextNoteId(noteID)
 Wend
 
 
End Sub

Okay. If this was a stretch for you, seriously consider not using this stuff.  Secondly, remember, that this will open, update and sign all your agents using the current user ID. This may not be a good thing in your environment. Again, if you don't understand what I'm blathering on about, DONT DO IT. You are on your own.

We now have one or more agents enabled for profiling. All is well. And we can see the status of the last run of the agent itself. And get a nice profile report. So far, so very-out-of-the-box. Hows about collecting ALL the agent runs in another database so you can see how it works over time ? 

Simple. A rather smart chap called Damien Katz developed something called Trigger Happy a while ago. Damien, as you recall, was the genius who managed to completely refactor the @formula engine in v6. This chap doesn't stick his tounge out when he types, if you know what I mean. 

Trigger Happy is an extension manager. That is, it hooks in part of a DLL (on windows servers) so that anytime anything exciting happens on a domino server, Agent Trigger can run. And you can tell it to look for particular events. In this case, I'm asking it to run anytime ANY document is saved on the server. This is very dangerous - my code could easily cause the server to go bananas. Tread carefully here. We're literally standing on a office chair, watering the plants on our 40th storey balcony.

This code would trap every single document save on the server, and run this lotusscript agent, passing it the open document. Can you see how dangerous this is ?

What would this code look like?

Sub Initialize
 Dim session As New NotesSession
 On Error Goto errorhandler
 
 If (session.DocumentContext.Form(0) <> "$BEProfileR7") Then Exit Sub
 
 Dim d As NotesDocument, dbSrc As NotesDatabase
 Set d = session.documentContext
 
 If (d Is Nothing) Then Exit Sub
 
 Dim db As NotesDatabase
 Set db = session.CurrentDatabase
 Dim doc As NotesDocument
 Set doc = db.CreateDocument
 
 If (doc Is Nothing) Then Exit Sub
 
 Call session.DocumentContext.CopyAllItems(doc, True)
 Call doc.ReplaceItemValue("Form", "Agent Performance Profile")
 
 Dim rt As notesRichTextItem
 Set rt = doc.GetFirstItem("Body")
 
 Dim T As Variant
 T = Split(rt.GetFormattedText(True, 132), Chr(10))
 
 If (Ubound(T) >0) Then Call doc.replaceitemValue("Time.Elapsed",    Clng(Strleft(Trim(Strrightback(T(1), ":")), " ")))
 If (Ubound(T) >1) Then Call doc.replaceitemValue("Methods",     Clng(Strrightback(T(2), ":")))
 If (Ubound(T) >2) Then Call doc.replaceitemValue("Time.Measured",   Clng(Strleft(Trim(Strrightback(T(3), ":")), " ")))
 Call doc.replaceitemValue("Text",  T)
 
 Dim ts As New TriggerSession
 Call doc.ReplaceItemValue("UserName", ts.username)
 
 Set dbSrc = d.ParentDatabase
 If Not (dbSrc Is Nothing) Then 
  If (dbSrc.IsOpen) Then 
   Call doc.replaceitemValue("db.Server",  dbSrc.server)
   Call doc.replaceitemValue("db.Title",  dbSrc.Title)
   Call doc.replaceitemValue("db.Path",  dbSrc.FilePath)
  End If
 End If
 
 Call doc.save(False, False)
 
 Print |Saving Agent Performance Profile for user: | & ts.UserName & |, agent: | & d.subject(0)
 
exitFunction:
 Exit Sub
 
errorHandler:
 Print  "Copy Agent Performance Logs: Run-time error: "+ Error$ + " at line: "+ Cstr(Erl)
 Resume exitFunction
 
End Sub

Note that we're hinging this on the fact that the document profile document that is being saved, is being saved with a form-name of "$BEProfileR7". If we find a document (and remember, it could be ANY document in ANY database on this server), we're saving a copy in this current database (where this agent is lodged).

Now for the bad news. I'm not going to tell you how to set this up in a step-by-step fashion. You have enough clues. Its important that you understand both the risks and rewards for all the steps. If you don't understand this, or if you cant get it working - I cant help you. And honestly, this is running-with-scissors++. I'd rather you cursed me for not spoon feeding, than curse me for killing your servers.

So there you have it. Less than 40 lines of code, and all of a sudden you have a very important, free, somewhat dangerous,  testing and measurement tool. 

Many thanks to Damien for doing the difficult stuff. 

Reader Comments (6)

[Sticks hand up at back of class]
errrrr... Mr Buchan .. if Damien's code is triggered on doc.save() what happens when your script does a doc.save() does it reconise that it shouldnt trigger itself trigger itself trigger itself trigger itself trigger itself trigger itself itself trigger itself trigger itself trigger itself trigger itself trigger itself itself trigger itself trigger itself trigger itself trigger itself trigger itself itself trigger itself trigger itself trigger itself trigger itself trigger itself itself trigger itself trigger itself trigger itself trigger itself trigger itself ... OUT OF CHEESE ERROR!!!!!!!!

February 2, 2010 | Unregistered CommenterSteve McDonagh

It is great this all knits together to be a free tool, but if you describe it as "somewhat dangerous" are you actually starting to find examples of disaster for Worst Practices LS11?

February 3, 2010 | Unregistered CommenterGrant Norman

Mr McD - yes and no. The database that Agent Trigger executes the agent from is excluded, so no, it doesnt recurse.

We've had this running on one of our bigger development servers for over 8 weeks now - no issues.

Somewhat dangerous ? Yes. I've seen some really dumb code (some written by me) and yes - handing out razor blades in the candyfloss like this will encourage more Worst Practices.. ;-)

---* Bill

February 3, 2010 | Unregistered CommenterBill

Trying to use this in Domino Designer 6.5.4 left me scratching my head for a while...
Going back to an article by The Source of all knowledge about agents, Julie Kadashevich, revealed this actually surfaced in Domino Release 7...

So. Bill, please update your otherwise fine article to reflect the version info..

February 3, 2010 | Unregistered CommenterLars Berntrop-Bos

Ah - damn! Its been so long since I've used 6.5 (and why on earth are you still using it ?) that I forgot it was v7.

Sorry.

---* Bill

February 3, 2010 | Registered CommenterBill Buchan

Och aye, tis just that there a lot of shops distributed over 5 countries all have a local Notes 6.5 client installed, and I want to be sure not to upset them with stuff from a new designer...

February 3, 2010 | Unregistered CommenterLars Berntrop-Bos

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>