High CPU usage by perspective threads

Hi everyone,
I'm currently working with ignition perspective on a mobile app for my company.

i've noticed an high CPU usage while i'm working on this specific project and this happens only with the mobile app project. if i try to open any other project i have a CPU usage of 6-10% plus some spikes due to a gateway script used to store data from other projects to our database.

As you can see the gateway shows that the main issue is with perspective threads. what can be causing this abnormal usage?

i have also downloaded the threads dump file if necessary.

these are some of the most intensive threads:

Thread [perspective-worker-34582] id=482615, (TIMED_WAITING for java.util.concurrent.SynchronousQueue$TransferStack@c041808)
java.base@17.0.9/jdk.internal.misc.Unsafe.park(Native Method)
java.base@17.0.9/java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
java.base@17.0.9/java.util.concurrent.SynchronousQueue$TransferStack.transfer(Unknown Source)
java.base@17.0.9/java.util.concurrent.SynchronousQueue.poll(Unknown Source)
java.base@17.0.9/java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
java.base@17.0.9/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
java.base@17.0.9/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
java.base@17.0.9/java.lang.Thread.run(Unknown Source)

Thread [perspective-worker-34589] id=483111, (TIMED_WAITING for java.util.concurrent.SynchronousQueue$TransferStack@c041808)
java.base@17.0.9/jdk.internal.misc.Unsafe.park(Native Method)
java.base@17.0.9/java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
java.base@17.0.9/java.util.concurrent.SynchronousQueue$TransferStack.transfer(Unknown Source)
java.base@17.0.9/java.util.concurrent.SynchronousQueue.poll(Unknown Source)
java.base@17.0.9/java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
java.base@17.0.9/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
java.base@17.0.9/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
java.base@17.0.9/java.lang.Thread.run(Unknown Source)

thanks for any kind of help

The threads with stack traces that show TIMED_WAITING are idle. Bindings and transforms tend to be brief workloads that are rarely captured by the "snapshot" nature of a thread dump.

Consider opening a support ticket to obtain guidance on the loggers that would be most useful at DEBUG and/or TRACE level to identify your culprits.

You can also do your own logging in transform scripts. If you suspect any particular script, adding logging would be my first step.

thanks i will surely do that!

i suspect some circular bindings, but i'm kind of new of the platform so i'm not that skilled to find them out. could a transform on a expression binding like this be a problem?

I don't see any circularity in that sample, because it has no side effects. (Please post code using this forum's "preformatted text" style instead of screenshots.) That is, there aren't any assignments to other Perspective properties within that transform. Those are often the source of circular evaluations that the designer cannot identify automatically.

Side note: You should not use multiple separate tag read operations in your scripts. Always assemble all necessary tag paths for reads into a list, and use a single system.tag.readBlocking() to get them all at once.

If the transform runs often, it would be even better to use separate custom properties with indirect tag bindings to track those tag values, and reference just the custom property in the script.

I think all those could be done with a lookup expression.

In this sample, they can not do that, as the second read path is dependent on the result of the first.

Looking at it, I would say there is potential of using tags to store values that do not need to be globally persistent values. (e.g. designParameters).

Ah, indeed. That dataset of user-specific parameters should be indirect bound to a session or view custom dataset. With expression bindings to extract the CoreID and DeviceID into view custom properties. Then a final indirect binding on a view custom property to subscribe to the power tag.

Then this entire binding and transform becomes a simple expression binding. No script at all.

1 Like

Back to the original point:

If the application has many inefficient scripts like this transform, then judicious use of non-script solutions will dramatically improve performance.

thanks for this huge advice.
I will try to implement all of this right now and post the results

Ok so i've changed those scripts in bindings like pturmel suggested, but i didn't get any major change on CPU usage.

i believe maybe this script on startup of one of my pages is the issue

def runAction(self):
	user = self.session.props.auth.user.userName
	FlexRepeaterInstances=[]
	Elenco_Core = system.tag.browse('[MQTT Engine]ctrlXboost/'+ user +'/MQTT', filter = {'tagType':'Folder'})
	for core in Elenco_Core.getResults():
		coreName= str(core['fullPath']).split("/")[3]
		sourceTagPath = "[MQTT Engine]ctrlXboost/"+ user +"/MQTT/" + coreName
		if core ['hasChildren'] == True:
			Elenco_Devices = system.tag.browse(sourceTagPath, filter = {'tagType':'Folder'})
			for device in Elenco_Devices.getResults():
				dataTagPath= str(device ['fullPath']) + '/sts'
				dataDefaultPath= '[default]ctrlXboost/' + dataTagPath.replace('[MQTT Engine]ctrlXboost/', '')
				FlexRepeaterInstances=[]
				ElencoTagMQTT = system.tag.browse(path = dataTagPath, filter = {})
				for result in ElencoTagMQTT.getResults():
					instance={
				      "instanceStyle": {
				        "classes": ""
				      },
				      "instancePosition": {},
				      "NomeLabel": "value",
				      "SelectedValue":""
				    }
					instance['NomeLabel']=str(result['name'])
					FlexRepeaterInstances.append(instance)
				self.getChild("root").getChild("FlexRepeater").props.instances = FlexRepeaterInstances

or these two scripts on startup which populate two dropdown menues:

def runAction(self):

	userName= self.session.props.auth.user.userName
	options = []
	coreID = 0
	Elenco_core=system.tag.browse("[MQTT Engine]ctrlXboost/"+ userName +"/MQTT", filter={'tagType':'folder'})
	for core in Elenco_core.getResults():
		coreName = str(core['fullPath']).split("/")[3]
		options.append({'value': coreName, 'label': coreName})
		coreID += 1
	self.props.options = options
def runAction(self, event):
	userName= self.session.props.auth.user.userName
	options = []
	#coreID = int(self.getSibling("Dropdown_0").props.value)
	coreName = str( self.getSibling("Dropdown_0").props.value)
	deviceID = 0 
	system.perspective.print(coreName)
	Elenco_devices=system.tag.browse("[MQTT Engine]ctrlXboost/"+ userName +"/MQTT/"+ coreName, filter={'tagType':'folder'})
	for device in Elenco_devices.getResults():
		deviceName = str(device['fullPath']).split("/")[4]
		options.append({'value': deviceName, 'label': deviceName})
		deviceID += 1
		
	self.props.options = options

since this is the slowest page to load among pages i've done in this project

Oy! system.tag.browse is evil. Nesting it is even more evil. You should think about how to access your application's hierarchy without that.

I couldn't help noticing that you have per-user tag hierarchies. Why is that necessary?

1 Like

This app is designed so that each user sees only the data sent to our mqtt broker from their devices. Hence the need to divide the folder path by user name.
Also (unfortunately) I had to use nested system.tag.browse because each user can have multiple cores (one folder per core) to which multiple devices can be connected ( one folder per device). as a result I need two nested for loops using system.tag.browse to reconstruct the complete tag path.

schematically my structure is

ctrlxboost

  • UserName

    • CoreName

      • DeviceName

        • sts

          • tags

You should be performing a three-level browse in your project startup script and caching the result in a document tag in each folder. So your UI just indirect binds to the document tag in the user folder. No other browsing.

(Perhaps add an administrator button that will re-execute the browse when necessary.)

2 Likes

Thanks for the hint, i will give it a try.

i have more or less the same script in the gateway scripts.

it browses every 30 sec either MQTT engine and default folders and compare them in order to import to default new users' folders. i imagine that i should change that script too

A tip for your browsing algorithm: accumulate tag paths and document dictionaries in two lists that are initialized (empty) before the outer loop. Every summary tag to be written would be appended to them after any inner loop is finished.

Then a single system.tag.writeBlocking would update everything at the end.

If you wish to avoid disrupting users with tag updates when no changes are found, before writing, read all of those document tags into another list (you have the tag paths in a list at that point). Then iterate through the three lists, constructing a new pair of lists (tag paths and documents) for only the changed documents. Use system.tag.writeBlocking on just that pruned pair of lists.