output Total Task Time

I was flipping through the scripting section of the user guide and didn’t see a script function to output Total Task Time for a job. Essentially I’m trying to output the amount of time a job spent rendering once a job is completed. Can you give me some guidance or point me in a direction to get this information?

Many thanks!!

There is a command line option that can retrieve this value, and you can call deadlinecommand from a script using ClientUtils.ExecuteCommandAndGetOutput():
thinkboxsoftware.com/deadlin … _Utilities

from System.Collections.Specialized import StringCollection
...
jobId = ... # Get your job's ID

args = StringCollection()
args.Add( "GetJobTaskTotalTime" )
args.Add( jobId )

totalTimeString = ClientUtils.ExecuteCommandAndGetOutput( args )

Note that totalTimeString is a string, so you’ll have to parse out the actual numbers if that’s what you need.

We recognize that this is a roundabout way of doing this, and we would like to add actual script functions to get this info in the future.

Cheers,

  • Ryan

[s]Ok, I’ve been playing with this and I’m wondering if I could find more information somewhere about this command “ClientUtils.ExecuteCommandAndGetOutput”. I noticed (not sure if it was a coincidence) that the command was very similar to using command line calls with deadlinecommand like “deadlinecommand -getjobtasktotaltime jobId”.

I’m currently on the hunt to see if I can do something like “deadlinecommand -getjobsidsfilter pool=poolname” and return a list of job ids.[/s]

I figured the above section out…

On a similar note, I was doing really well using the command line “deadlinecommand” calls. But I could not get the information to read back into deadline using Python (specifically the subprocess command) because it seems Iron Python doesn’t support that. Is there a way to use the full python library in Deadline? As a novice who does his best learning by example and reading online what others have done… I’m not finding a lot of IronPython examples.

I hope I’m not asking too much, I’m honestly giving all of these things lots of research before I ask!

brad

If you are using Deadline 5.1 or later, you can use the native python install that comes with Deadline by adding this as the first line in your script:

#Python.NET

This is covered a bit here:
thinkboxsoftware.com/deadlin … /#Overview

Cheers,

  • Ryan

I will give that a shot.

I’m had some confusion when querying jobs using "deadlinecommand -getjobsfilter " (or the equivalent ClientUtils.ExecuteCommandAndGetOutput command). When filtering jobs by pool or group, the results are only based on the submitted job information. So if you change the pool of the job after its submitted, it won’t be filtered by the updated information. My example:
A job is submitted to group “1” and pool “A”, then you change it to group “2” and pool “B” using the monitor’s Modify Properties after its been submitted. If you run “deadlinecommand -getjobsfilter pool=B” you will yield no results. But if you did pool=A, said job would show up.

Is there a way to query the most up-to-date information?

Thanks,
brad

Hey Brad,

Try using “JobPool” instead of “Pool” as the filter. The internal property names for the original pool and the pool override is a bit weird, and the getjobsfilter just looks at property names directly. Using “JobPool”, which is the same “JobPool” that is available in the Scripting API for the Job object, should always return the correct pool. If this doesn’t help, let me know!

Cheers,

  • Ryan

You are just too helpful! That did the trick!!

One last question… using the “deadlinecommand -getjobidsfilterand …”, is there a way to find jobs that are of a certain pool and have either completed or archived status? Currently I’m finding jobs in pool A that are completed, then finding jobs in pool A that are archived archived, and finally merging those lists. This works, but doubles the time searching for jobs. So my code looks like this:

[code]# get job from selection box
job = scriptDialog.GetValue( “PoolBox” )

get list of job IDs for the pool that are either completed or archived

filter = "jobPool="
filter += job
p1args = StringCollection()
p1args.Add("GetJobIdsFilterAnd")
p1args.Add( filter )
p1args.Add( "status=Completed" )

p2args = StringCollection()
p2args.Add("GetJobIdsFilterAnd")
p2args.Add( filter )
p2args.Add( "status=Archived" )


y1 = ClientUtils.ExecuteCommandAndGetOutput( p1args )
y1=y1.split()
y2 = ClientUtils.ExecuteCommandAndGetOutput( p2args )
y2=y2.split()
y = y1 + y2[/code]

Thanks again, and again and again!
brad

Unfortunately no. It’s either all ANDs or all ORs. Glad to hear you at least have a working solution for now.

I’ll add it to the wish list though. I’m sure there is a way we could support this in the future.

+1. Filtering in 1 hit against archived jobs AS WELL as what is referred to in Deadline as ALL other job states would be most useful.
I had to do the same thing as Brad once to get the desired result.

I didn’t realize this until just now… “archived” is not a job status. This may be common knowledge, but I’m saying it out-loud for those that might be searching the forums for it…

Archived jobs are found with this command:
deadlinecommand -getjobidsfilter archived=true

So all completed, archived jobs would be:
deadlinecommand -getjobidsfilterand archived=true status=completed

I appreciate that you guys made the repository files human understandable… a bit of poking around in the .job file was how I discovered this.

Where do additional libraries need to be installed for use using the Python.NET engine?

Thanks!

You don’t need to install any additional libraries to use the Python.NET engine. Deadline just uses the python installation that is installed with it (which is in the local Deadline installation folder).

I guess if you are installing additional libraries that don’t come with the standard python installation, they would go in Deadline’s python folder.

Sorry, should have specified. (whether this is a good idea is up for debate) but I’m going to attempt writing Excel spreadsheets from my script and want to use these libraries: python-excel.org/

Do those libraries need to go on all machines that will run the script? Or can they go into the Python folder in the repository?

If you can put them in a global location and reference them from there, you can add that global path to the Python Search Paths:
thinkboxsoftware.com/deadlin … n_Settings

Still battling, getting closer everyday. My current road bump is passing variables to functions. What is happening when I use this command: selectButton.ValueModified += SelectButtonPressed
I get that we are calling a function SelectButtonPressed, and in many examples I see that the defined function called SelectButtonPressed accepts ( *args )… but where can we define what arguments to send to the new function? I’ve tried this:

selectButton.ValueModified += SelectButtonPressed(a, b, c)
and while I am able to get that to work, I believe other variables that I’m unaware of are being left behind. Further more adding those variable to the end as shown above breaks the dialog box, it just by passes being displayed and selects whatever default options are displayed (for instance I have 4 options in a drop down and it just selects the first and moves on, like I had hit the OK or Select button).

I should add that I am able to get arguements/variables passed to functions if they appear in my scriptdialog window using scriptDialog.GetValue. But I’m interested in getting other variables from a previous function. If you need more info into what I’m doing, please let me know!

brad

No arguments are currently passed to the functions when the ValueModified event is triggered, so *args should be empty in this case. What parameters do you want to pass to the SelectButtonPressed function? Could you just make them global variables so that the function can access them directly?

Some sample code would definitely help us understand what you’re trying to do!

Thanks!

  • Ryan

Sure! There is no doubt some nonsense in here and things that I use to see what’s going on as this code progresses… but here is my beast.

[code]#Python.NET

from System.Collections.Specialized import *
from System.Drawing import *
from System.IO import *
from Deadline.Scripting import *
import os
import xlwt
from xlwt import Workbook, easyxf, Borders
from datetime import datetime
import shutil

scriptDialog = None
settings = None

def main():
global scriptDialog
global settings

job-pool selection window

scriptDialog = DeadlineScriptEngine.GetScriptDialog()
scriptDialog.SetSize( 400, 130 )
scriptDialog.SetTitle( "Job Selection" )
scriptDialog.SetIcon( Path.Combine( GetRootDirectory(), "scripts/General/RenderBilling/RenderBilling.ico" ) )

scriptDialog.AddRow()
scriptDialog.AddControl( "DefineTool", "LabelControl", "This tool will calculate the total render time for job billing.", 380, -1 )
scriptDialog.EndRow()
scriptDialog.AddRow()
scriptDialog.AddControl( "PoolLabel", "LabelControl", "Select the job:", 100, -1 )
scriptDialog.AddControl( "PoolBox", "PoolComboControl", "job", 200, -1 )
scriptDialog.EndRow()

scriptDialog.AddRow()
scriptDialog.AddControl( "DummyLabel1", "LabelControl", "", 205, -1 )
scriptDialog.EndRow()
scriptDialog.AddRow()
scriptDialog.AddControl( "DummyLabel2", "LabelControl", "Once you hit Select, please be patient. A pop up will appear with your total.", 385, -1 )
scriptDialog.EndRow()

scriptDialog.AddRow()
scriptDialog.AddControl( "DummyLabel3", "LabelControl", "", 175, -1 )
selectButton = scriptDialog.AddControl( "SelectButton", "ButtonControl", "Select", 100, -1 )
selectButton.ValueModified += SelectButtonPressed
closeButton = scriptDialog.AddControl( "CloseButton", "ButtonControl", "Close", 100, -1 )
closeButton.ValueModified += CloseButtonPressed
scriptDialog.EndRow()

scriptDialog.ShowDialog( False )

def SelectButtonPressed( *args ):

get job from selection box

job = scriptDialog.GetValue( "PoolBox" )

check to make sure a job was selected

if job == "none":
# pop up with error
	scriptDialog.ShowMessageBox( "    Please select a job, you can't calculate NONE.    ", "Error Message")
else:
	CalculateJob()

def CalculateJob( *args ):
global scriptDialog
CloseButtonPressed()

begin = datetime.now()

get job from selection box

job = scriptDialog.GetValue( "PoolBox" )

get list of job IDs for the pool that are completed, archived or not

filter = "jobPool="
filter += job
poolArgs = StringCollection()
poolArgs.Add("GetJobIdsFilterAnd")
poolArgs.Add( filter )
poolArgs.Add( "status=Completed" )

jobidlist = []
poollist = ClientUtils.ExecuteCommandAndGetOutput( poolArgs )
poollist = poollist.split()

if poollist == []:
	NoHours(job)
else:

declaring some variables

	tt = 0	# total job time tallied
	jt = 0  # job time

write to file for diagnostics

inp=file(’//queue/DeadlineRepository/scripts/General/RenderBilling/output.txt’, ‘w’)

	cmp = len(poollist)	# number of jobs that are completed
	jobname = []
	jobtimelist = []
	totaltimelist = []
	plugname = []
	username = []
	cmpdate = []

get time for each job

	for i in range(len(poollist)):
		jobid = str(poollist[i])
		jobArgs = StringCollection()
		jobArgs.Add("GetJobTaskTotalTime")
		jobArgs.Add( jobid )
		jobtime = ClientUtils.ExecuteCommandAndGetOutput( jobArgs )
		d = float(jobtime[20:22])
		h = float(jobtime[24:26])
		m = float(jobtime[28:30])
		s = float(jobtime[32:34])
		jt = (86400*d+3600*h+60*m+s)
		jt /= 3600
		tt += jt

add job time to list

		jobtimelist.append(jt)		

add total job time to list

		totaltimelist.append(tt)				

add job name to list

		nameArgs = StringCollection()
		nameArgs.Add("GetJobSetting")
		nameArgs.Add(jobid)
		nameArgs.Add("name")
		jobname.append( ClientUtils.ExecuteCommandAndGetOutput( nameArgs ))

add plug-in to list

		plugArgs = StringCollection()
		plugArgs.Add("GetJobSetting")
		plugArgs.Add(jobid)
		plugArgs.Add("pluginname")
		plugname.append( ClientUtils.ExecuteCommandAndGetOutput( plugArgs ))		

add artist to list

		userArgs = StringCollection()
		userArgs.Add("GetJobSetting")
		userArgs.Add(jobid)
		userArgs.Add("username")
		username.append( ClientUtils.ExecuteCommandAndGetOutput( userArgs ))

add completion date to list

		dateArgs = StringCollection()
		dateArgs.Add("GetJobSetting")
		dateArgs.Add(jobid)
		dateArgs.Add("completeddatetime")
		cmpdate.append( ClientUtils.ExecuteCommandAndGetOutput( dateArgs ))		

pop up if no hours exist

	if tt == 0:
		NoHours()

inp.write('job name = ’ + str(jobname[i]) + ‘\n’)

#################################################
#################################################

define excel doc and sheet

	wb = xlwt.Workbook()
	ws = wb.add_sheet('RenderStats')

style1 = xlwt.XFStyle()

style1.num_format_str = ‘D-MMM-YY’

	font2 = xlwt.Font()
	font2.colour_index = 1
	style2 = xlwt.XFStyle()
	style2.font = font2

define a style for a bottom border

	borders = Borders()
	borders.bottom = Borders.MEDIUM
	styleB = xlwt.XFStyle()
	styleB.borders = borders

	avgrentime = round(tt/cmp,2)
	datemin = min(cmpdate)[:10]
	datemax = max(cmpdate)[:10]

	ws.write(0, 1, job)
	ws.write(1, 1, "Report date: " + str(datetime.now().strftime('%Y-%m-%d')) + " @ " + str(datetime.now().strftime('%H:%M')))
	ws.write(2, 1, "Total render time: " + str(round(tt,2)) + " hours")
	ws.write(3, 1, "Number of jobs submitted: " + str(cmp) + " jobs")
	ws.write(4, 1, "Average render time: " + str(avgrentime) + " hours")
	ws.write(5, 1, "Project dates: " + str(datemin) + " to " + str(datemax))

establish table headers

	ws.write(7, 0, "#", styleB)
	ws.write(7, 1, "ID", styleB)
	ws.write(7, 2, "Name", styleB)
	ws.write(7, 3, "Application", styleB)
	ws.write(7, 4, "Artist", styleB)
	ws.write(7, 5, "Date", styleB)
	ws.write(7, 6, "Time", styleB)
	ws.write(7, 7, "Deadline Tally", styleB)
	ws.write(7, 8, "Excel Tally", styleB)
	ws.write(7, 9, "Discrepancies?", styleB)

start for loop here

declare max length variables

	maxId = 20
	maxName = 8
	maxApp = 8 
	maxArtist = 5
	maxDate = 8
	maxTime = 4
	maxTallyDl = 10
	maxTallyEx = 8
	maxDscp = 10
		
	for r in range(len(poollist)):
		myRow = r + 8
		myCell = r + 9
		jobCounter = r+1
		formula1 = "round(sum(L9:L" + str(myCell) + "),2)"
		formula2 = "if(H" + str(myCell) + "-I" + str(myCell) + "=0,\"\",\"yes\")"
		formulaJT = "round(L" + str(myCell) + ",2)"
		formulaTT = "round(M" + str(myCell) + ",2)"
		ws.write(myRow, 0, jobCounter)
		ws.write(myRow, 1, poollist[r])
		ws.write(myRow, 2, jobname[r])
		ws.write(myRow, 3, plugname[r])
		ws.write(myRow, 4, username[r])
		ws.write(myRow, 5, cmpdate[r][:10])
		ws.write(myRow, 6, xlwt.Formula(formulaJT))		
		ws.write(myRow, 7, xlwt.Formula(formulaTT))		
		ws.write(myRow, 11, jobtimelist[r], style2)
		ws.write(myRow, 12, totaltimelist[r], style2)
		ws.write(myRow, 8, xlwt.Formula(formula1))

discrepancy

		ws.write(myRow, 9, xlwt.Formula(formula2))

find max lengths

		if maxName < len(jobname[r]):
			maxName = len(jobname[r])
		if maxApp < len(plugname[r]):
			maxApp = len(plugname[r])
		if maxArtist < len(username[r]):
			maxArtist = len(username[r])
		if maxTime < len(str(round(jobtimelist[r],2))):
			maxTime = len(str(round(jobtimelist[r],2)))

set column widths

	ws.col(0).width = 1250
	ws.col(1).width = (maxId+4)*256 # max letter length + 4
	ws.col(2).width = (maxName+1)*256
	ws.col(3).width = (maxApp+4)*256
	ws.col(4).width = (maxArtist+4)*256
	ws.col(5).width = (maxDate+4)*256
	ws.col(6).width = (maxTime+4)*256
	ws.col(7).width = (maxTallyDl+4)*256
	ws.col(8).width = (maxTallyEx+4)*256
	ws.col(9).width = (maxDscp+4)*256

save spreadsheet

	location = '\\\\queue\\DeadlineRepository\\scripts\\General\\RenderBilling\\output\\' + job + '_' + str(datetime.now().strftime('%Y-%m-%d')) + '.xls'
	wb.save(location)

##################################################
##################################################

inp.write(’\n\ncompleted jobs ’ + str(cmp))

inp.close()

how long did this process take

	end = datetime.now()
	runtime = end - begin

round total render time to 2 decimal points

	ttr = str(round(tt,2))	

results and email window

	scriptDialog = DeadlineScriptEngine.GetScriptDialog()
	scriptDialog.SetSize( 450, 200 )
	scriptDialog.SetTitle( "Render Results" )
	scriptDialog.SetIcon( Path.Combine( GetRootDirectory(), "scripts/General/RenderBilling/RenderBilling.ico" ) )

	scriptDialog.AddRow()
	scriptDialog.AddControl( "DTool2", "LabelControl", "Total "+ ttr + " hours for job " + filter[8:] + ". (" + str(cmp) + " completed jobs)", 380, -1)
	scriptDialog.EndRow()

	scriptDialog.AddRow()
	scriptDialog.EndRow()

	scriptDialog.AddRow()
	scriptDialog.AddControl( "DefineTool3", "LabelControl", "Choose an action below:", 380, -1)
	scriptDialog.EndRow()
		
	scriptDialog.AddRow()
	scriptDialog.AddControl( "ComboLabel", "LabelControl", "Select An Action", 120, -1 )
	scriptDialog.AddComboControl( "CBox", "ComboControl", "Save and Email", ("Save and Email","Save only","Email only","Discard results"), 175, -1 )
	scriptDialog.EndRow()

	scriptDialog.AddRow()
	scriptDialog.AddControl( "EmailLabel", "LabelControl", "Enter email address here: ", 120, -1 )
	scriptDialog.AddControl( "EmailAddress", "TextControl", "", 300, -1 )
	scriptDialog.EndRow()
	scriptDialog.AddRow()
	scriptDialog.AddControl( "SaveFolderLabel", "LabelControl", "Select folder:", 120, -1 )
	scriptDialog.AddSelectionControl( "SaveFolder", "FolderBrowserControl", "", "", 300, -1 )
	scriptDialog.EndRow()

	scriptDialog.AddRow()
	scriptDialog.AddControl( "DummyLabel5", "LabelControl", "", 205, -1 )
	scriptDialog.EndRow()

	scriptDialog.AddRow()
	scriptDialog.EndRow()
	scriptDialog.AddRow()
	scriptDialog.AddControl( "DefineTool10", "LabelControl", "Script run time: " + str(runtime)[:7], 380, -1)
	scriptDialog.EndRow()	
	
	scriptDialog.AddRow()
	scriptDialog.AddControl( "DummyLabel6", "LabelControl", "", 225, -1 )
	selectButton = scriptDialog.AddControl( "SendButton", "ButtonControl", "OK", 100, -1 )
	selectButton.ValueModified += SendButtonPressed
	closeButton = scriptDialog.AddControl( "CloseButton", "ButtonControl", "Discard", 100, -1 )
	closeButton.ValueModified += CloseButtonPressed
	scriptDialog.EndRow()
	
	scriptDialog.ShowDialog( False )

def SendButtonPressed( location, *args ):
global scriptDialog
scriptDialog.CloseDialog()
action = scriptDialog.GetValue( “CBox” )
if action == “Save and Email”:
save = scriptDialog.GetValue( “SaveFolder” )
email = scriptDialog.GetValue( “EmailAddress” )
scriptDialog.ShowMessageBox( "Save to " + save + " and Email to " + email, “action01”)
if action == “Save only”:
save = scriptDialog.GetValue( “SaveFolder” )
shutil.copy2(location,“C:\moved.xls”)
if action == “Email only”:
email = scriptDialog.GetValue( “EmailAddress” )
scriptDialog.ShowMessageBox( "Email only to " + email, “action03”)
if action == “Discard results”:
scriptDialog.ShowMessageBox( “Discard results”, “action04”)
CloseButtonPressed()

def CloseButtonPressed( *args ):
global scriptDialog
scriptDialog.CloseDialog()

def NoHours( x ):
global scriptDialog
scriptDialog.CloseDialog()

job = scriptDialog.GetValue( “PoolBox” )

scriptDialog.ShowMessageBox( "        There are no hours for job " + x + "       ", "No Render Time")[/code]

if I had to paraphrase, this is what is going on.

[code]main():
popup window where you select the job you want to tally
ok or cancel buttons

SelectButtonPressed(*args):
makes sure you selected a job, if not it takes you back to main, if you did it send you to calculate

CalculateJob(*args):
makes tons of lists with all the data and also gets some running totals
makes the excel document
pops up a review dialog box with 4 options (save and email, save, email or discard) - all options lead to the next function which has if statements for the options

SendButtonPressed(*args):
if save and email, do that
if save, do that
if email, do that
if discard, delete the file

CloseButtonPressed(*arg):
closes dialog boxes and exits functions
NoHours(*args):
pop up returned if there are no rendered jobs for the project[/code]

I could use global variable… I was trying to avoid them only based on reading online over and over that they were bad or a cheap way out (I believe everything I read online when it comes to code since I don’t know any better!). This code works until you get to the last SendButtonPressed in the 2nd dialog box at the end CalculateJob function. If using global variable is an easy and suggested solution I’ll do that! I didn’t want to re-declare variables that I need to match, and accidentally have them mismatch and break stuff.

Let me give the global variables a try. Thanks!
brad

PS - if you look at my code and scratch your head, well, I’m learning as I go!

Thanks for posting the code! Are you wanting to pass variables to the SelectButtonPressed function, so that you can then pass them to CalculateJob? If so, what variables would you be passing? I guess I’m just not seeing what these variables are or where they fit in.

Generally, it’s a good idea to avoid global variables. However, in small standalone scripts, it’s not really a big deal - I mean, you’re already using a global scriptDialog… :slight_smile:

Currently I’m wanting to pass the variable “location” (currently set to: location = ‘\\queue\DeadlineRepository\scripts\General\RenderBilling\output\’ + job + ‘_’ + str(datetime.now().strftime(’%Y-%m-%d’)) + ‘.xls’) to SendButtonPressed. At first I thought I would build the excel spreadsheet and let it just live in memory (or heaven if the code believes in it). Then the user would be presented with a 2nd pop up window… save, email, both or discard. If you did one of the first 3 options then I would write it to disk. If you chose discard then it simply wouldn’t be written to disk (with this code: wb.save(location)). But since I have to do my wb.save to save the spreadsheet IN the CalculateJob function, I decided to always just write the spreadsheet to a known, shared location on the deadline system with a variable called “location”. That way, when you move on to SendButtonPressed you can email the file from known “location” to the email address entered into the dialog box. You can save it using a copy command from “location” to the new location entered in the dialog box. or you can delete it since you know that it lives in “location”. That way if I ever change the path, I change it once and it is known throughout. Sounds like the type of thing a global variable would be used for.

SO…

TL;DR I was trying to pass “location” from CalculateJob to SendButtonPressed.

I’m having trouble sending an email using ClientUtils.ExecuteCommand. I’m trying to replicate this command line into my python script:

deadlinecommand -sendemail -subject "email" -to me@address.com -message "here it is"
I’ve tested this and it does work, confirming the repository has the ability to send emails.

One attempt that didn’t work…

emailArgs = StringCollection() emailArgs.Add("SendEmail") emailArgs.Add("subject email") emailArgs.Add("to me@address.com") emailArgs.Add("message \"here it is\"") ClientUtils.ExecuteCommand( emailArgs )

Another failed attempt…

emailArgs = StringCollection() emailArgs.Add("SendEmail") emailArgs.Add("subject") emailArgs.Add("email") emailArgs.Add("to") emailArgs.Add("me@address.com") emailArgs.Add("message") emailArgs.Add("here it is") ClientUtils.ExecuteCommand( emailArgs )

I’m just not sure how it wants the arguments fed to it. Can you lend a hand? The global variables worked like a charm.

brad