Enhancing Loading Performance for Volume Calculation in XYChart DataSource

Good morning community,

I'm seeking assistance with optimizing the performance of a code snippet within the DataSource of an XYChart. The code is responsible for fetching data from a flow meter to calculate daily and monthly volumes. It's worth noting that there might be more than one flow meter, and when switching between months, there's a noticeable delay in the data retrieval process.

I'm wondering if there's a solution to implement a loading indicator to notify users when data is being fetched. Alternatively, I'm open to suggestions on improving the overall loading efficiency of this data.

Here's a snippet of the code in question:

def transform(self, value, quality, timestamp):
	"""
	Transform the incoming value and return a result.

	Arguments:
		self: A reference to the component this binding is configured on.
		value: The incoming value from the binding or the previous transform.
		quality: The quality code of the incoming value.
		timestamp: The timestamp of the incoming value as a java.util.Date
	"""
	# Definir parâmetros da consulta
	tag_path = "[default]_VTIS_01_/LED/FQI001_10/Valor"
	
	# Definir ano e mês desejados
	ano = value["ano"]
	mes = value["mes"]
	

	# Calcular as datas de início e fim do mês
	start_date  = system.date.getDate(ano, mes, 1)
	end_date = system.date.addDays(system.date.getDate(ano, mes + 1, 1), -1)
	end_date = system.date.setTime(end_date, 23, 59, 59)
	
	# Inicializar lista para armazenar resultados
	results = []
	
	# Loop through each day of the month
	current_date = start_date
	while current_date <= end_date:
	    next_date = system.date.addDays(current_date, 1)
	    
	    # Consultar histórico da tag para o dia atual e o próximo dia
	    query_result = system.tag.query(
	        paths=[tag_path],
	        startDate=current_date,
	        endDate=next_date,
	        returnSize=-1, # Obter todos os valores do dia
	        columnNames=["value"]
	    )
	    
	    # Verificar se a consulta retornou valores
	    if query_result.getRowCount() > 1:
	        # Obter o valor inicial e final do dia
	        first_value = query_result.getValueAt(0, "value")
	        last_value = query_result.getValueAt(query_result.getRowCount() - 2, "value")
	        
	        # Calcular a diferença de volume para o dia
	        daily_volume = max(last_value - first_value, 0)
	        
	        # Armazenar resultado em um dicionário
	        result = {
	            "date": current_date,
	            "volume": daily_volume
	        }
	        
	        # Adicionar resultado à lista
	        results.append(result)
	    
	    current_date = next_date
	
	# Retornar objeto com os resultados
	returnObj = {'data': results}
	return results

Quaisquer insights ou recomendações sobre como tornar o processo de carregamento de dados mais eficiente ou como incorporar um indicador de carregamento seriam muito apreciados.

Agradeço pela valiosa contribuição!

Interesting, I wasn't aware of this possibility to work with cache. How can I do this within Ignition 8.1 Perspective?

Don't pay too much attention to his responses as I and others have gathered he's using an LLM like ChatGPT to generate "answers" which aren't accurate or correct many times. You could possibly use cache, but not in the way you're pulling the data.

I'm not sure how you're even getting the data using system.tag.query as this would need to be system.tag.queryTagHistory but if this is frequently needed data, you may be better off trying to do it with a tag history binding and getting the "Range" aggregate value, or pull in the MinMax aggregate to do it yourself somehow. I'd really have to see what you're trying to accomplish.

We ended up on our flow meters, pulling the data via modbus and it provided a yesterday total already, so we just historize this since it only changes once a day.

1 Like

This is the code I am using in production.

Notice that I make a daily query, iterating and building an array with the data to pass it to the XYChart series.

I will run a test here and then come back to let you know if it worked. I got an idea from it.

	tag_path = "[default]_VTIS_01_/LED/FQI001_10/Valor"
	
	ano = value["ano"]
	mes = value["mes"]
	
	start_date  = system.date.getDate(ano, mes, 1)
	end_date = system.date.addDays(system.date.getDate(ano, mes + 1, 1), -1)
	end_date = system.date.setTime(end_date, 23, 59, 59)
	
	results = []
	
	current_date = start_date
	while current_date <= end_date:
	    next_date = system.date.addDays(current_date, 1)
	    
	    query_result = system.tag.queryTagHistory(
	        paths=[tag_path],
	        startDate=current_date,
	        endDate=next_date,
	        returnSize=-1, 
	        columnNames=["value"]
	    )
	    
	    if query_result.getRowCount() > 1:
	        # Obter o valor inicial e final do dia
	        first_value = query_result.getValueAt(0, "value")
	        last_value = query_result.getValueAt(query_result.getRowCount() - 2, "value")
	      
	        daily_volume = max(last_value - first_value, 0)
	        
	  
	        result = {
	            "date": current_date,
	            "volume": daily_volume
	        }
	        
	        results.append(result)
	    
	    current_date = next_date
	
	returnObj = {'data': results}
	return results

There are a number of caching mechanisms in Ignition--some provided for you, some do-it-yourself:

  • Named Queries offer optional caching when given the same exact parameters. If you are querying times series data with an NQ, be sure to provide start and end timestamps that have been truncated to even multiples of minutes, 5 minutes, hours, or similar, to give the NQ system better chances to cache.

  • The tag historian appears to do something similar under the hood, given identical criteria.

  • Jython variables created in a project library script outside any classes or functions are persistent until the project is saved in the designer. Careful use of dictionaries or lists, where you never re-assign at that top level, provides a place where event or transform scripts can place information to recover in other contexts.

  • Jython objects placed in the dictionary returned by system.util.getGlobals() are persistent across the entire JVM, and through project saves. Some care is needed to utilize this feature without creating monster memory leaks, though. (Also, I recommend my Integration Toolkit module's system.util.globalVarMap() alternative, for a number of reasons.)

Edit: I suppose, for completeness, I should mention:

  • Time-series data in wide database tables (not the historian) can be cached in fine detail, both in the gateway and in Vision clients, using my Time Series Database Cache module, without needing to tweak start/end query timestamps. (My NoteChart module's Vision EasyNoteChart can automatically use this for DB pens when both modules are installed.)

{ /shameless plug }

2 Likes