258 lines
7.5 KiB
Python
258 lines
7.5 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
import time
|
|
|
|
from netrender.utils import *
|
|
import netrender.model
|
|
|
|
class RatingRule:
|
|
def __init__(self):
|
|
self.enabled = True
|
|
self.editable = False
|
|
|
|
def id(self):
|
|
return str(id(self))
|
|
|
|
def rate(self, job):
|
|
return 0
|
|
|
|
class ExclusionRule:
|
|
def __init__(self):
|
|
self.enabled = True
|
|
self.editable = True
|
|
def id(self):
|
|
return str(id(self))
|
|
|
|
def test(self, job):
|
|
return False
|
|
|
|
class PriorityRule:
|
|
def __init__(self):
|
|
self.enabled = True
|
|
self.editable = True
|
|
def id(self):
|
|
return str(id(self))
|
|
|
|
def test(self, job):
|
|
return False
|
|
|
|
class Balancer:
|
|
def __init__(self):
|
|
self.rules = []
|
|
self.priorities = []
|
|
self.exceptions = []
|
|
|
|
def ruleByID(self, rule_id):
|
|
for rule in self.rules:
|
|
if rule.id() == rule_id:
|
|
return rule
|
|
for rule in self.priorities:
|
|
if rule.id() == rule_id:
|
|
return rule
|
|
for rule in self.exceptions:
|
|
if rule.id() == rule_id:
|
|
return rule
|
|
|
|
return None
|
|
|
|
def addRule(self, rule):
|
|
self.rules.append(rule)
|
|
|
|
def addPriority(self, priority):
|
|
self.priorities.append(priority)
|
|
|
|
def addException(self, exception):
|
|
self.exceptions.append(exception)
|
|
|
|
def applyRules(self, job):
|
|
return sum((rule.rate(job) for rule in self.rules if rule.enabled))
|
|
|
|
def applyPriorities(self, job):
|
|
for priority in self.priorities:
|
|
if priority.enabled and priority.test(job):
|
|
return True # priorities are first
|
|
|
|
return False
|
|
|
|
def applyExceptions(self, job):
|
|
for exception in self.exceptions:
|
|
if exception.enabled and exception.test(job):
|
|
return True # exceptions are last
|
|
|
|
return False
|
|
|
|
def sortKey(self, job):
|
|
return (1 if self.applyExceptions(job) else 0, # exceptions after
|
|
0 if self.applyPriorities(job) else 1, # priorities first
|
|
self.applyRules(job))
|
|
|
|
def balance(self, jobs):
|
|
if jobs:
|
|
# use inline copy to make sure the list is still accessible while sorting
|
|
jobs[:] = sorted(jobs, key=self.sortKey)
|
|
return jobs[0]
|
|
else:
|
|
return None
|
|
|
|
# ==========================
|
|
|
|
class RatingUsage(RatingRule):
|
|
def __str__(self):
|
|
return "Usage per job"
|
|
|
|
def rate(self, job):
|
|
# less usage is better
|
|
return job.usage / job.priority
|
|
def serialize(self):
|
|
return { "type": "rating",
|
|
"enabled": self.enabled,
|
|
"descritpiton":str(self),
|
|
"limit":"",
|
|
"id":self.id()
|
|
}
|
|
|
|
|
|
class RatingUsageByCategory(RatingRule):
|
|
def __init__(self, get_jobs):
|
|
super().__init__()
|
|
self.getJobs = get_jobs
|
|
|
|
def __str__(self):
|
|
return "Usage per category"
|
|
|
|
def rate(self, job):
|
|
total_category_usage = sum([j.usage for j in self.getJobs() if j.category == job.category])
|
|
maximum_priority = max([j.priority for j in self.getJobs() if j.category == job.category])
|
|
|
|
# less usage is better
|
|
return total_category_usage / maximum_priority
|
|
|
|
def serialize(self):
|
|
return { "type": "rating",
|
|
"enabled": self.enabled,
|
|
"editable": self.editable,
|
|
"descritpiton":str(self),
|
|
"limit":"",
|
|
"id":self.id()
|
|
}
|
|
|
|
|
|
class NewJobPriority(PriorityRule):
|
|
def __init__(self, limit = 1):
|
|
super().__init__()
|
|
self.limit = limit
|
|
|
|
def setLimit(self, value):
|
|
self.limit = int(value)
|
|
|
|
def str_limit(self):
|
|
return "less than %i frame%s done" % (self.limit, "s" if self.limit > 1 else "")
|
|
|
|
def __str__(self):
|
|
return "Priority to new jobs"
|
|
|
|
def test(self, job):
|
|
return job.countFrames(status = netrender.model.FRAME_DONE) < self.limit
|
|
def serialize(self):
|
|
return { "type": "priority",
|
|
"enabled": self.enabled,
|
|
"editable": self.editable,
|
|
"descritpiton":str(self),
|
|
"limit": self.limit,
|
|
"limit_str":self.str_limit(),
|
|
"id":self.id()
|
|
}
|
|
|
|
class MinimumTimeBetweenDispatchPriority(PriorityRule):
|
|
def __init__(self, limit = 10):
|
|
super().__init__()
|
|
self.limit = limit
|
|
|
|
def setLimit(self, value):
|
|
self.limit = int(value)
|
|
|
|
def str_limit(self):
|
|
return "more than %i minute%s since last" % (self.limit, "s" if self.limit > 1 else "")
|
|
|
|
def __str__(self):
|
|
return "Priority to jobs that haven't been dispatched recently"
|
|
|
|
def test(self, job):
|
|
return job.countFrames(status = netrender.model.FRAME_DISPATCHED) == 0 and (time.time() - job.last_dispatched) / 60 > self.limit
|
|
|
|
def serialize(self):
|
|
return { "type": "priority",
|
|
"enabled": self.enabled,
|
|
"editable": self.editable,
|
|
"descritpiton":str(self),
|
|
"limit": self.limit,
|
|
"limit_str":self.str_limit(),
|
|
"id":self.id()
|
|
}
|
|
|
|
class ExcludeQueuedEmptyJob(ExclusionRule):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.editable= False
|
|
def __str__(self):
|
|
return "Exclude non queued or empty jobs"
|
|
|
|
def test(self, job):
|
|
return job.status != netrender.model.JOB_QUEUED or job.countFrames(status = netrender.model.FRAME_QUEUED) == 0
|
|
|
|
def serialize(self):
|
|
return { "type": "exception",
|
|
"enabled": self.enabled,
|
|
"editable": self.editable,
|
|
"descritpiton":str(self),
|
|
"limit": "",
|
|
"limit_str":"",
|
|
"id":self.id()
|
|
}
|
|
|
|
|
|
class ExcludeSlavesLimit(ExclusionRule):
|
|
def __init__(self, count_jobs, count_slaves, limit = 0.75):
|
|
super().__init__()
|
|
self.count_jobs = count_jobs
|
|
self.count_slaves = count_slaves
|
|
self.limit = limit
|
|
|
|
def setLimit(self, value):
|
|
self.limit = float(value)
|
|
|
|
def str_limit(self):
|
|
return "more than %.0f%% of all slaves" % (self.limit * 100)
|
|
|
|
def __str__(self):
|
|
return "Exclude jobs that would use too many slaves"
|
|
|
|
def test(self, job):
|
|
return not ( self.count_jobs() == 1 or self.count_slaves() <= 1 or float(job.countSlaves() + 1) / self.count_slaves() <= self.limit )
|
|
|
|
def serialize(self):
|
|
return { "type": "exception",
|
|
"enabled": self.enabled,
|
|
"editable": self.editable,
|
|
"descritpiton":str(self),
|
|
"limit": self.limit,
|
|
"limit_str":self.str_limit(),
|
|
"id":self.id()
|
|
}
|