Tractor and Nuke

Tractor and Nuke

Check the status of your renders on Tractor directly in Nuke

Tractor is a modern and robust solution for network rendering, capable of scaling up to the largest render farms.
Tractor distributes tasks to a farm of execution servers. It manages large queues of concurrent jobs from many users, enforcing dependencies and scheduling policies.
Tractor can drive all of the computational tools used in modern VFX and animation pipelines. It is used to run everything from rendering and compositing to simulation, transcoding, archiving, database updates, code builds, online asset delivery and notifications. Tractor can launch almost any executable available on your Linux, Windows, and Mac OS X systems.

You can download my scripts from Nukepedia and follow the file Read Me to install the plug-in in Nuke (special thanks to Marco Curado and the Trixter comp team):


Download from Nukepedia



Useful Links



Overview

  • READ ATTRIBUTES FROM 'TRACTOR'

Let's start with this Python code.
The easiest way is to open the Script Editor in Nuke and use this simple code.
With this code, done by Marco Curado, you can contact Tractor directly from Nuke with the Script Editor.


import sys

import tractor
from tractor.api import query
from tractor import api
import tractor.api.query as tq

#get only jobs from project TOYSTOY that are already done, with owner=andrea. 
done = tq.jobs("projects={TOYSTORY} and done and owner={andrea}", columns=["jid", "title"], sortby=["-priority"],
               limit=60)
               
#get me all the active jobs
active = tq.jobs("active", columns=["jid", "title"], sortby=["-priority"], limit=20)

#get me all the ready jobs
ready = tq.jobs("ready", columns=["jid", "title"], sortby=["-priority"], limit=40)

#get me all the jobs with errors
error = tq.jobs("error", columns=["jid", "title"], sortby=["-priority"], limit=10)

#print all the jobs done
for elem in done:
	print elem

In the variable columns you can select the attribute you want to get.
This is a list of attributes you can return from Tractor.

"""Example of job query return from tractor DB:
{
    u'comment': u' Rendering: Write1.images',
    u'maxslots': 0,
    u'numblocked': 0,
    u'pil': 1701267110,
    u'spoolcwd': u'/tmp',
    u'assignments': u'',
    u'numerror': 0,
    u'elapsedsecs': 21713.669921875,
    u'owner': u'andrea',
    u'spoolhost': u'pc001',
    u'spooladdr': u'10.11.100.110',
    u'jid': 1701267110,
    u'service': u'',
    u'title': u'ToyStory_comp_v002',
    u'numactive': 0,
    u'editpolicy': u'',
    u'spooltime': u'2018-07-22 13:38:05+02',
    u'projects': [u'TOYSTORY'],
    u'crews': [],
    u'maxcid': 82,
    u'metadata': u'',
    u'numready': 0,
    u'tags': [],
    u'afterjids': [],
    u'lastnoteid': 0,
    u'stoptime': u'2018-07-22 14:17:13.046+02',
    u'envkey': [],
    u'tier': u'',
    u'etalevel': 1,
    u'numtasks': 164,
    u'minslots': 0,
    u'serialsubtasks': False,
    u'numdone': 164,
    u'maxtid': 164,
    u'aftertime': None,
    u'maxactive': 0,
    u'spoolfile': u'/unknown_origin',
    u'esttotalsecs': 21713.669921875,
    u'starttime': u'2018-07-22 13:55:56.563+02',
    u'pausetime': None,
    u'dirmap': None,
    u'deletetime': None,
    u'priority': 37315.0,
}

BY Marco Curado"""



GET MY JOBS FROM TRACTOR

Here we are going to create a menu in Nuke where you can check the status of your renders on Tractor and if necessary, even modify them (for example retry an error job).

import sys

#here you have to insert the path of your folder, something like: tractor/2/linux_x86-64/lib/python2.7/site-packages/
sys.path.insert(0, 'insert here the path of tractor folder') 

import nuke
import nukescripts
import tractor
import getpass
from tractor.api import query
from tractor import api
import tractor.api.query as tq
import time
																								
#get me my username											
global username
username = getpass.getuser()

#or insert it manually with: username = "insert here your username"

#list of jobs done in the last hour with the owner equals to my username
my_jobs_done = tq.jobs("done and stoptime > -1h and owner=" + username + "", sortby=["-stoptime"], limit=40)

#list of jobs active without errors
my_jobs_active = tq.jobs("active and not error and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid"], sortby=["-priority"], limit=20)

#list of jobs ready and not active yet
my_jobs_ready = tq.jobs("ready and not active and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid"], sortby=["-priority"], limit=40)

#list of jobs with error
my_jobs_error = tq.jobs("error and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid"], sortby=["-priority"], limit=10)

#A job is in pause if the attribute pause is > 1000-01-01 00:00:00.000000+00 
my_jobs_paused = tq.jobs("not done and Job.pausetime > '1000-01-01 00:00:00.000000+00' and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid", "pausetime"], sortby=["-priority"], limit=20)



CREATE A MENU FOR NUKE

This is the final script. In this case you are going to create a window with all the jobs from you.

An example of what you will get. In this image you have only 1 job active:


Here you get 1 active job and 3 jobs ready to start:


You have an example about the jobs in pause. Clicking on the button you can unpause a job:



The same is with jobs with errors. You can retry them easily:


And you will get a list of jobs done in the last hour:



Add this string to the meny.py if you want to create a custom menu. Put the file hey_tractor in your folder .nuke:

#add this script to your file menu.py
import sys
import nuke

#import all the Python files
import hey_tractor

menubar = nuke.menu("Nuke")

toolbar = nuke.toolbar("Nodes")
m = toolbar.addMenu("Custom"")


menubar.addCommand('Custom/Hey Tractor!', "hey_tractor.main()" )
m.addCommand('Hey Tractor!', "hey_tractor.main()" )
import sys
import nuke
import nukescripts
import tractor
import getpass
from tractor.api import query
from tractor import api
import tractor.api.query as tq
import time



def talk_with_tractor():
    global username
    username = getpass.getuser()


#################### INSTRUCTIONS ########################
# Modify the user name and the password of your profile  #
##########################################################
    
    SIMTRACKER = {"user": "insert_here_your_username",
                  "password": "insert_here_your_password!"}
    
    tq.setEngineClientParam(user=SIMTRACKER["user"], password=SIMTRACKER["password"])
    
    global my_jobs_done
    my_jobs_done = tq.jobs("done and stoptime > -1h and owner=" + username + "", sortby=["-stoptime"], limit=40)
    global my_jobs_active
    my_jobs_active = tq.jobs("active and not error and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid"], sortby=["-priority"], limit=20)
    global my_jobs_ready
    my_jobs_ready = tq.jobs("ready and not active and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid"], sortby=["-priority"], limit=40)
    global my_jobs_error
    my_jobs_error = tq.jobs("error and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid"], sortby=["-priority"], limit=10)
    global my_jobs_paused
    my_jobs_paused = tq.jobs("not done and Job.pausetime > '1000-01-01 00:00:00.000000+00' and owner=" + username + "", columns=["jid", "title", "numdone", "maxtid", "pausetime"], sortby=["-priority"], limit=20)
    
    
    n_jobs_active = len(my_jobs_active) + len(my_jobs_ready) + len(my_jobs_error)
    
    array= []



def update():
    global p
    p.finishModalDialog(True)

    p = ShapePanel()
    
    p.show_modal_dialog()

#-----------------------------------------------------

def retry_job(id, title):
    if nuke.ask('Are you sure you want to retry this job: ' + title + ' ?'):
        tq.retry("jid=" + id + " and error")
        time.sleep(2)
        update()



#-------------------------------------------------------

def unpause_job(id, title):
    if nuke.ask('Are you sure you want to unpause this job: ' + title + ' ?'):
        tq.unpause("jid=" + id + " and Job.pausetime > '1000-01-01 00:00:00.000000+00'")
        time.sleep(2)
        update()

#----------------------------------------
def get_elementPercentage(elem):
        if elem['numdone'] > 0:
            percentage = int(elem['numdone']/float(elem['maxtid'])*100)
        else:
            percentage = 0
        return str(elem['jid']) + '    ' + elem['title'] + '    ' + str(percentage) + "%"

#-------------------------------------------

def get_element(elem):
        return str(elem['jid']) + '    ' + elem['title']

#-------------------------------------------

def get_id(elem):
        return elem['jid']
#------------------------------------------

def get_title(elem):
        return elem['title']

#--------------------------------------------


class ShapePanel(nukescripts.PythonPanel):
    def __init__(self):
        talk_with_tractor()
        #create window
        nukescripts.PythonPanel.__init__(self, 'Tractor elements from ' + username )
        self.setMinimumSize(800, 300)
        
      
        def add_knob():
            #add KNOBS
            
            #ACTIVE JOBS
            if(len(my_jobs_active)>0):
                self.addKnob(nuke.Text_Knob("ACTIVE JOBS (" + str(len(my_jobs_active)) + "): "))
                for elem in my_jobs_active:
                    knob = nuke.Text_Knob(str(get_id(elem)), get_elementPercentage(elem))
                    knob.setValue(" ")
                    knob.clearFlag(nuke.ENDLINE)
                    self.addKnob(knob)
    
    
            #READY JOBS
            if(len(my_jobs_ready)>0):
                self.addKnob(nuke.Text_Knob("READY JOBS (" + str(len(my_jobs_ready)) + "): "))
                for elem in my_jobs_ready:
                    knob = nuke.Text_Knob(str(get_id(elem)), get_elementPercentage(elem))
                    knob.setValue(" ")
                    knob.clearFlag(nuke.ENDLINE)
                    self.addKnob(knob)
            
            #PAUSED JOBS
            #get_paused_job(my_jobs_paused)
            if(len(my_jobs_paused)>0):
                self.addKnob(nuke.Text_Knob("PAUSED JOBS (" + str(len(my_jobs_paused)) + "): "))
                for elem in my_jobs_paused:
                    knob = nuke.Text_Knob(str(get_id(elem)), get_elementPercentage(elem))
                    knob.setValue(" ")
                    knob.clearFlag(nuke.ENDLINE)
                    self.addKnob(knob)                    

                    self.button = nuke.PyScript_Knob("unpause_" + str(get_id(elem)), "Unpause", "hey_tractor.unpause_job('" + str(get_id(elem)) + "','" + str(get_title(elem)) + "')")
                    self.button.setTooltip(str(get_id(elem)))
                    self.addKnob(self.button)
            
    
            #ERROR JOBS
            if(len(my_jobs_error)>0):
                self.addKnob(nuke.Text_Knob("JOBS with ERROR (" + str(len(my_jobs_error)) + "): "))
                for elem in my_jobs_error:
                    knob = nuke.Text_Knob(str(get_id(elem)), get_elementPercentage(elem))
                    knob.setValue(" ")
                    knob.clearFlag(nuke.ENDLINE)
                    self.addKnob(knob)

                    self.button = nuke.PyScript_Knob("retry_" + str(get_id(elem)), "Retry", "hey_tractor.retry_job('" + str(get_id(elem)) + "','" + str(get_title(elem)) + "')")
                    self.button.setTooltip(str(get_id(elem)))
                    self.addKnob(self.button)
                    #print self.button.tooltip()
    
    
            #JOBS DONE
            if(len(my_jobs_done)>0):
                self.addKnob(nuke.Text_Knob("JOBS DONE in the last hour (" + str(len(my_jobs_done)) + "): "))
                for elem in my_jobs_done:
                    knob = nuke.Text_Knob(get_element(elem))
                    knob.setValue(" ")
                    knob.clearFlag(nuke.ENDLINE)
                    self.addKnob(knob)

    
            #ALL OK!
            if(len(my_jobs_paused)==0 and len(my_jobs_error)==0 and len(my_jobs_ready)==0 and len(my_jobs_active)==0 and len(my_jobs_done)==0):
                knob = nuke.Text_Knob("all your jobs are DONE!!")
                self.addKnob(knob)
            

            #divider
            self.divider = nuke.Text_Knob("divider","")
            self.addKnob(self.divider)
    
            self.button = nuke.PyScript_Knob("update", "Update", "hey_tractor.update()")
            self.button.setFlag(nuke.STARTLINE)
            self.addKnob(self.button)
#------------------------------------------------------------------------
        add_knob()
        
 
   
    def show_modal_dialog(self):
        nukescripts.PythonPanel.showModal(self)


def main():
    global p
    p = ShapePanel()
    talk_with_tractor()
    p.show_modal_dialog()

#main()

Leave a comment