Solved

Is there a way to set custom property values from your groovy script in your datasource?


Userlevel 2
Badge +2

Hey there.  I’m relatively new to logic monitor and I received a request from a client.  

 

The request is as follows.  

They wanted us to be able to provide not only blocked processes alerts for their SQL dbs but also for us to provide a breakdown of what is blocking what.

My solution to this was to edit the datasource that contains the processblocked datapoint.  I made some good progress.  I was able to create a custom property field for the instances that I’m monitoring.  I then edited the script for the collector attribute so that in theory that if particular datapoint (processessblocked) shows a value of greater than 0, it uses hostSets() to change that custom property value to the blocker session ids using some custom sql script within. 

When I test it using the test button pointed at my sql server, it works exactly as i expected.  It shows the default value of the property unless it hits that block process value of 1 or more.  then the property reads with the needed info.  

However after I save the datasource.  And put this in to practice, no matter what the custom property on those instances never update or change.  
I also tried applying similar logic to the active discovery script with the same results.  Seems to work fine through testing but then nothing in the actual instance changes.  

 

I can post any or all of the groovy script if need be.  Is it something simple like some sort of syntax I’m not getting or am I not even in the right area to do what I’m attempting to do?

 

Any help would be appreciated and like I said, I’ll gladly provide any more info or code if need be.  

 

Thanks

icon

Best answer by Stuart Weenig 17 May 2023, 17:57

View original

68 replies

Userlevel 7
Badge +17

I don’t think hostProps.get() does anything in production. Even if it did, you’d want instanceProps.set(), but AFAIK that doesn’t do anything either. There are two ways to write a property to an instance: 1) discovery and 2) API (or you could hack the collector).

Discovery:

If you want, your discovery script can set/update instance level properties every time it runs. The most frequent you can force this to run is every 15 minutes, so that may not work. However, if you want to go that route, you’d need to include something in the 5th term of each discovery script output line. Each output line has 5 terms, separated by double hashtags. The last is a url parameter style list of properties. So, if your discovery right now is outputting:

id##name##description

You’d need it to output:

id##name##description####prop1=value1&prop2=value2

Where prop1 and prop2 are the names of the properties you want to add and value1 and value2 are the values of those properties, respectively. More here.

API

If you need it more often, you would need a function in your collection script to call the API to patch the instance’s properties. The UI does this with a PUT on the instance, so you’d have to have the entire definition of the instance in json format so you could update it. There is a PATCH option, which might fit your use case better. All of this requires now that your DS have access to API credentials as properties. All of that is doable, but I haven’t done it before. In this case, you’re basically replicating what instanceProps.set() would do.

Put in another feedback ticket for instanceProps.set() (and hostProps.set()).
 

Also, this is exactly what LM Actions should do, if it ever becomes more than an idea.

Userlevel 2
Badge +2

Is there some extra syntax to calling props from an instance?  

 

I tried instanceProps.get(“nameofprop”) and it returns null

Do I need to do something like  instanceProps.get(“instance.name.nameofprop”)  ?

Userlevel 7
Badge +17

No, that should work if the property exists. You can’t call instanceProps.get() from discovery though, only collection. To verify the properties are present, you can look up the task details in the collector debug console. Here are the docs on the *Props.get() methods.

Userlevel 2
Badge +2

I was trying to run it in the collector attribute script as a test.  I assume it wouldn’t work there then you are saying.

Userlevel 7
Badge +17

Collector attribute script = collection script. *Props.get() methods should work there. Do the properties exist on your instances?

Userlevel 2
Badge +2

Perhaps I am setting what I think are the instance props in the wrong spot.

I’m trying to specifically get to this prop here.  

 

For reference I have the host prop “blockerdetails” set as middle level so i can see when im calling the whole server prop vs what i assumed was the instance.  

 

My apologies if this is some no brainer stuff I should have know in the first place.  I was kinda thrown into the deep end here at work, hahahaha

Userlevel 7
Badge +17

No, that’s the right place. instanceProps.get(“blockerdetails”) should do it. What’s your collection script look like (don’t forget to redact any keys)?

Userlevel 7
Badge +17

Wait, is this batchscript or script? Look above discovery at the “Collector” field.

Userlevel 2
Badge +2

It’s set to batchscript.  Should it be script?

 

Also it is greyed out and doesn’t give me the option to switch it if i wanted.

Userlevel 7
Badge +17

Ah that explains why it’s not working. Batchscript creates one task meant to collect all the data for all the instances. instanceProps.get() doesn’t know which instance to get the property from. Is this an OOTB DS or custom?

Userlevel 2
Badge +2

OOTB.

 

I was tweaking it but still using the OOTB

Userlevel 7
Badge +17

Also it is greyed out and doesn’t give me the option to switch it if i wanted.

Yeah, you can’t change it once the DS is created. You wouldn’t want to since the code is written for batchscript, not script. 

Restate your objective here? You want to write a property during every collection to the instance. instanceProps.get() isn’t going to do that. Do you have the collection script querying for the blocker, you just need something to do with it?

Userlevel 2
Badge +2

Also it is greyed out and doesn’t give me the option to switch it if i wanted.

. Do you have the collection script querying for the blocker, you just need something to do with it?

Yes exactly.  The collection script can pull the desired info.  I am just unclear what the best approach is to display that to the user through alerts and the triggered emails

Userlevel 7
Badge +17

Ok, well, it can’t be through any of the built in methods since they will only allow you to output numerical data. So, unless you can somehow map the value of “blocker” to a number, your best bet will be to write a block of code that takes the value of “blocker” and either writes it as a property or the instance description or something. We’re back to this. You’ll need to write a block of code to go in the collection script to PATCH each instance with the property/value of “blocker”. 

LM has pretty good Python and Go SDKs, but they don’t have an SDK for their native Groovy language, so you’ll have to write this all out on your own. Even the examples on their website mostly use Python instead of Groovy to access their own API. (Ug, why isn’t python natively supported in LM yet?) You might put in a ticket to support to see if they have examples in Groovy that they can point you to. I gave up trying to get Groovy to work and most of my custom datasources are written in python.

I realize this doesn’t help that much, but it’s the direction you’ll probably have to go.

Userlevel 2
Badge +2

No this helps a good bit.  Thanks.  Time to bust out some python

Userlevel 7
Badge +17

Yeah, except that the problem is that you already have this datasource collection script already gathering the data, you just have no easy way to push it back into LM. LM should provide Groovy examples of doing PATCHs. All the examples I have are for GETs, not PATCHs.

 

Userlevel 2
Badge +2

I did put a support ticket requesting just that. an example of a patch in groovy.  lets see what they say

Userlevel 2
Badge +2

Thanks again for pointing me at the API methods.  I’m messing around in postman first to make sure i can do all the things I want.  I’m still struggling a bit to find the correct datasource though.  

 

I’m at the right device using 

get device/devices/{id}/datasources

From there i have a list of all the datasources but i dont see the one that i need.

Microsoft_SQLServer_GlobalPerformance.  I assume I’m misinterpreting how this should work again, but unsure.  

 

**edit** did a little bit of of digging and was able to find the datasourceid even though it wasn’t in that list i got from the get method

 

**edit 2**   I succesfully patched the property i wanted to patch through postman.  now to use it in logic mon

 

Userlevel 7
Badge +17

If you’ve got postman working, you’re doing well. That’s where most people trip up.

So, you need to construct this URL to do your patch:

/device/devices/{deviceId}/devicedatasources/{hdsId}/instances/{id}

To do that, you need three variables: deviceId, hdsId, and id. Sounds like you already have deviceId. 

To get hdsId (hardware datasource id, don’t ask why it’s called that, it doesn’t matter), you’ll need to do a GET on :

/device/devices/{deviceId}/devicedatasources?filter=dataSourceName:"Microsoft_SQLServer_GlobalPerformance"&fields=id,dataSourceName

The response will give you, as you’ve seen, the list of datasources on that device, but with the filter specified above, you should only get a response if there’s been an instance discovered on the device whose device ID is {deviceId}. If there’s not one there, you have chosen the id of a device that doesn’t have an instance for that datasource.

From there, you can use the id from the response (which is different for every device, yay!) to get the list of instances that you will be attempting to patch. Do a GET on:

/device/devices/{deviceId}/devicedatasources/{hdsId}/instances

The id shown in the response for each instance is the id that should go in the PATCH url.

Also, when you go to do your patch, there’s a query parameter: opType that will affect how your patch works, especially when it comes to properties. The swagger docs don’t even tell you what the possible values are much less what they do, so here it is:

Define custom properties for this device. Each property needs to have a name and a value. To add or update just one or a few device properties in the customProperties object, but not all of them, you’ll need to additionally use the opType query parameter. The opType query parameter can be set to add, refresh or replace. opType=add indicates that the properties included in the payload will be added, but all existing properties will remain the same. opType=replace indicates that the properties included in the request payload will be added if they don’t already exist, or updated if they do already exist, but all other existing properties will remain the same. opType=refresh indicates that the properties will be replaced with those included in the request payload.

So, the possible values are add, replace, and refresh. 

Add - You specify the properties you want to add. Existing properties are untouched. New properties are added. If your payload has a property that already exists, that property isn’t modified.

Replace - You specify the properties you want to update. New properties are added. If your payload has a property that already exists, that property is updated to match your payload.

Refresh - This is the nuclear option which will delete all properties and replace them with the properties in your payload.

You’d probably want to do replace.

 

Userlevel 2
Badge +2

Thanks for the heads up about replace vs. refresh.  I was using refresh but I’ll switch it

Userlevel 2
Badge +2

So I began trying to port over what I have done with postman into my groovy script.  (I know you said you had much better success using python, but I still wanna give it a try first)

 

Interesting note.  I was able to successfully make a get request using this line.

 

def http_body = httpClient.get(endpoint_url, ['Authorization': ‘{{the authtoken copied from postman','Content-Type': 'application/json','Cookie':'JSESSIONID=17513E3CB1FC4E623F829A629A16B811'] );

 

now when i try to take everything in those square brackets for the header and put them into a variable and use them the same way

 

def http_body = httpClient.get(endpoint_url, headers);

 

i get an error.  interesting.  

Userlevel 7
Badge +17

Because your auth token from Postman expired. The pre-request script that you have in postman has to be built in groovy so that your signature is generated at runtime. AFAIK, authenticating using jsessionid will not work because those sessions also timeout.

No doubt, this would be easier in python, but you have to use groovy because you’re trying to expand the functionality of an existing groovy script. So, groovy is the right way to go.

Userlevel 2
Badge +2

I made sure it wasn’t expired.  Like i said when i just hard code the header i get a success.  but when i try to put the same info into a variable and use it I get 

 

The script failed, elapsed time: 0 seconds - No signature of method: com.santaba.agent.groovyapi.http.Client.get() is applicable for argument types:

I do understand that yea this method won’t work long term either way for that very reason.  I’m just taking this one small step at a time so I know what I’m doing is working every step of the way if that makes sense

 

when the auth does expire i get an auth error within my test.  not from the script itself

Userlevel 7
Badge +17

Ah, ok good. Show me how you’re defining the headers variable?

Userlevel 2
Badge +2

def headers = ‘["Authorization": "LMv1 PHf4xEdZHA5sz8U5ECM8:NzJjNzMzZDkyZjdmMDY0MWYwMWUwZmU3Nzc1ZDUzZjIwY2I3OWEyZGU3NGU5MmE1YzhiNWM3NTA5NDViZmFhZA==:1684428033047","Content-Type":"application/json"]’

 

 

Reply