623 lines
15 KiB
C
623 lines
15 KiB
C
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "kvmgr.h"
|
|
#include "plugin.h"
|
|
#include "preason.h"
|
|
#include "resreq.h"
|
|
#include "sched/types.h"
|
|
#include "scheduler.h"
|
|
#include "utils.h"
|
|
#include "xce/types.h"
|
|
#include "xcealloc.h"
|
|
#include "xcelimit.h"
|
|
#include "zarray.h"
|
|
|
|
int get_resvals(zarray_t* array, int nres, int* idxs, float* vals)
|
|
{
|
|
xce_val_t* xvals[nres + 1];
|
|
int rc = 0;
|
|
int i;
|
|
|
|
if ((array == NULL) || (nres < 0) || (idxs == NULL) || (vals == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (nres == 0) {
|
|
return 0;
|
|
}
|
|
|
|
memset(vals, 0x0, sizeof(*vals) * nres);
|
|
|
|
rc = zarray_get_ex(array, nres, idxs, (void**)xvals);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < nres; i++) {
|
|
if (xvals[i] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (xvals[i]->which != Value_u_fvalue) {
|
|
continue;
|
|
}
|
|
|
|
vals[i] = xvals[i]->u.fvalue;
|
|
}
|
|
|
|
return nres;
|
|
}
|
|
|
|
int put_resvals(zarray_t* array, int nres, int* idxs, float* vals)
|
|
{
|
|
xce_val_t* xvals[nres + 1];
|
|
int rc, i;
|
|
|
|
if ((array == NULL) || (nres < 0) || (idxs == NULL) || (vals == NULL)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (nres == 0) {
|
|
return 0;
|
|
}
|
|
|
|
rc = zarray_get_ex(array, nres, idxs, (void**)xvals);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < nres; i++) {
|
|
if (idxs[i] <= 0) {
|
|
continue;
|
|
}
|
|
|
|
if (xvals[i] == NULL) {
|
|
xce_val_t* val = (xce_val_t*)calloc(1, sizeof(*val));
|
|
if (val == NULL) {
|
|
continue;
|
|
}
|
|
|
|
val->which = Value_u_fvalue;
|
|
val->u.fvalue = 0.0;
|
|
zarray_put(array, idxs[i], val);
|
|
xvals[i] = val;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nres; i++) {
|
|
xvals[i]->u.fvalue = vals[i];
|
|
}
|
|
|
|
return nres;
|
|
}
|
|
|
|
static int unsatisfy_(xce_scheduler_t* sched, int nres, int* idxs, float* total,
|
|
float* inuse, float* uncomitted, float* wanted,
|
|
int* unmeet_idxs, float* unmeet_vals)
|
|
{
|
|
int i, j;
|
|
int count;
|
|
|
|
count = 0;
|
|
for (i = 0; i < nres; i++) {
|
|
if (total[i] < inuse[i] + uncomitted[i] + wanted[i]) {
|
|
TRACE("%s resource %d total %f inuse %f uncomit %f wanted %f",
|
|
__func__, idxs[i], total[i], inuse[i], uncomitted[i],
|
|
wanted[i]);
|
|
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
j = 0;
|
|
for (i = 0; i < nres; i++) {
|
|
if (total[i] < inuse[i] + uncomitted[i] + wanted[i]) {
|
|
xce_resource_t* r = NULL;
|
|
float need = inuse[i] + uncomitted[i] + wanted[i] - total[i];
|
|
|
|
vget(sched->resmgr, idxs[i], (void**)&r);
|
|
TRACE("%s unmet resource %d (%s) shortfall %f", __func__, idxs[i],
|
|
r ? r->name : "?", need);
|
|
|
|
unmeet_idxs[j] = idxs[i];
|
|
unmeet_vals[j] = need;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static host_eval_ctx_t* build_host_eval_ctx(const xce_sched_worker_t* w,
|
|
int num_res)
|
|
{
|
|
host_eval_ctx_t* ctx = calloc(1, sizeof(*ctx));
|
|
if (ctx == NULL)
|
|
return NULL;
|
|
|
|
if (num_res <= 0) {
|
|
/* valid empty context — numvals/strvals remain NULL, bounds checks
|
|
* protect access */
|
|
ctx->num_res = 0;
|
|
return ctx;
|
|
}
|
|
|
|
double* numvals = calloc(num_res, sizeof(double));
|
|
const char** strvals = calloc(num_res, sizeof(char*));
|
|
if (numvals == NULL || strvals == NULL) {
|
|
free(numvals);
|
|
free(strvals);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < num_res; i++) {
|
|
xce_val_t* v = (xce_val_t*)zarray_get((zarray_t*)&w->resvals, i);
|
|
if (v == NULL)
|
|
continue;
|
|
switch (v->which) {
|
|
case Value_u_fvalue:
|
|
numvals[i] = (double)v->u.fvalue;
|
|
break;
|
|
case Value_u_bvalue:
|
|
numvals[i] = v->u.bvalue ? 1.0 : 0.0;
|
|
break;
|
|
case Value_u_svalue:
|
|
strvals[i] = v->u.svalue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ctx->num_res = num_res;
|
|
ctx->numvals = numvals;
|
|
ctx->strvals = strvals;
|
|
return ctx;
|
|
}
|
|
|
|
static void free_host_eval_ctx(host_eval_ctx_t* ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
return;
|
|
free((void*)ctx->numvals);
|
|
free((void*)ctx->strvals);
|
|
free(ctx);
|
|
}
|
|
|
|
void sched_worker_release_host_ctx(xce_sched_worker_t* w)
|
|
{
|
|
if (w == NULL)
|
|
return;
|
|
free_host_eval_ctx(w->host_eval_ctx);
|
|
w->host_eval_ctx = NULL;
|
|
}
|
|
|
|
int sched_meet_host_selection(xce_scheduler_t* sched, xce_sched_worker_t* w,
|
|
xce_select_t* s)
|
|
{
|
|
if (s == NULL || s->root == NULL)
|
|
return 1;
|
|
|
|
/* Build host context lazily — reused for all jobs against this host. */
|
|
if (w->host_eval_ctx == NULL) {
|
|
int num_res = (int)w->resvals.caps;
|
|
w->host_eval_ctx = build_host_eval_ctx(w, num_res);
|
|
if (w->host_eval_ctx == NULL) {
|
|
/* OOM: conservatively pass host */
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Fast path: use session-level memoisation context when available. */
|
|
if (sched->sel_eval_ctx != NULL) {
|
|
/* Reset memoisation bitmap only when moving to a different host. */
|
|
if (sched->sel_eval_ctx->host != w->host_eval_ctx)
|
|
select_eval_ctx_reset(sched->sel_eval_ctx, w->host_eval_ctx);
|
|
|
|
int rc = eval_select(s->root, sched->sel_eval_ctx);
|
|
return (rc > 0) ? 1 : 0;
|
|
}
|
|
|
|
/* Fallback (no session context — e.g. unit tests or OOM at session start).
|
|
*/
|
|
int rc = eval_select_simple(s->root, w->host_eval_ctx);
|
|
return (rc > 0) ? 1 : 0;
|
|
}
|
|
|
|
static int sched_check_shared_satisfy_(xce_scheduler_t* sched, xvec_t* total,
|
|
xvec_t* inuse, xvec_t* uncomitted,
|
|
xvec_idval_t* wanted,
|
|
xvec_idval_t* unmeet)
|
|
{
|
|
int nres = wanted->count;
|
|
int rc;
|
|
|
|
rc = xvec_init(total, nres, sizeof(float));
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = xvec_init(inuse, nres, sizeof(float));
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = xvec_idval_init(unmeet, nres);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = get_resvals(&(sched->sharedInst), nres, (int*)wanted->idxs.data,
|
|
(float*)total->data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = get_resvals(&(sched->sharedInUse), nres, (int*)wanted->idxs.data,
|
|
(float*)inuse->data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = unsatisfy_(sched, nres, (int*)wanted->idxs.data, (float*)total->data,
|
|
(float*)inuse->data, (float*)uncomitted->data,
|
|
(float*)wanted->vals.data, (int*)unmeet->idxs.data,
|
|
(float*)unmeet->vals.data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (rc > 0) {
|
|
unmeet->count = rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int sched_check_shared_satisfy(xce_scheduler_t* sched, xvec_t* uncomitted,
|
|
xvec_idval_t* wanted, xvec_idval_t* unmeet)
|
|
{
|
|
xvec_t total;
|
|
xvec_t inuse;
|
|
int rc;
|
|
|
|
if (wanted->count == 0) {
|
|
return 1;
|
|
}
|
|
|
|
rc = sched_check_shared_satisfy_(sched, &total, &inuse, uncomitted, wanted,
|
|
unmeet);
|
|
if (rc < 0) {
|
|
xvec_idval_fini(unmeet);
|
|
}
|
|
|
|
xvec_fini(&total);
|
|
xvec_fini(&inuse);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sched_check_host_satisfy_(xce_scheduler_t* sched,
|
|
xce_sched_worker_t* w, xvec_t* total,
|
|
xvec_t* inuse, xvec_t* uncomitted,
|
|
xvec_idval_t* wanted, xvec_idval_t* unmeet)
|
|
{
|
|
int nres = wanted->count;
|
|
int rc = 0;
|
|
|
|
rc = xvec_init(total, nres, sizeof(float));
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = xvec_init(inuse, nres, sizeof(float));
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = xvec_idval_init(unmeet, nres);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = get_resvals(&(w->resvals), nres, (int*)wanted->idxs.data,
|
|
(float*)total->data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = get_resvals(&(w->resInUse), nres, (int*)wanted->idxs.data,
|
|
(float*)inuse->data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = unsatisfy_(sched, nres, (int*)wanted->idxs.data, (float*)total->data,
|
|
(float*)inuse->data, (float*)uncomitted->data,
|
|
(float*)wanted->vals.data, (int*)unmeet->idxs.data,
|
|
(float*)unmeet->vals.data);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
if (rc > 0) {
|
|
unmeet->count = rc;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
int sched_check_host_satisfy(xce_scheduler_t* sched, xce_sched_worker_t* w,
|
|
xvec_t* uncomitted, xvec_idval_t* wanted,
|
|
xvec_idval_t* unmeet)
|
|
{
|
|
xvec_t total;
|
|
xvec_t inuse;
|
|
int rc;
|
|
|
|
if (wanted->count == 0) {
|
|
return 1;
|
|
}
|
|
|
|
rc = sched_check_host_satisfy_(sched, w, &total, &inuse, uncomitted, wanted,
|
|
unmeet);
|
|
if (rc < 0) {
|
|
xvec_idval_fini(unmeet);
|
|
}
|
|
|
|
xvec_fini(&total);
|
|
xvec_fini(&inuse);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sched_update_res_accounts(xce_scheduler_t* sched, zarray_t* array,
|
|
int global, int naccounts,
|
|
xce_res_account_t** accounts, int charge)
|
|
{
|
|
int i;
|
|
int idxs[naccounts + 1];
|
|
float vals[naccounts + 1];
|
|
|
|
if (naccounts < 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (naccounts == 0) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < naccounts; i++) {
|
|
xce_res_account_t* acct = accounts[i];
|
|
|
|
if (acct->residx < 0) {
|
|
xce_resource_t* r = NULL;
|
|
int rc = 0;
|
|
|
|
lock(sched->resmgr, RTTI_LOCK_READ);
|
|
rc = key_find(sched->resmgr, (void*)acct->resname, (void**)&r);
|
|
unlock(sched->resmgr);
|
|
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
/* It's possible that r return NULL if acct->resname is a
|
|
* virtual resource of limit */
|
|
if (r == NULL) {
|
|
int rc;
|
|
|
|
rc = add_vres(&r, sched->resmgr, acct->resname, global);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
lock(sched->resmgr, RTTI_LOCK_READ);
|
|
rc = key_find(sched->resmgr, (void*)acct->resname, (void**)&r);
|
|
unlock(sched->resmgr);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (r != NULL) {
|
|
acct->residx = r->idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < naccounts; i++) {
|
|
idxs[i] = accounts[i]->residx;
|
|
}
|
|
|
|
get_resvals(array, naccounts, idxs, vals);
|
|
|
|
for (i = 0; i < naccounts; i++) {
|
|
float val = accounts[i]->used + accounts[i]->reserved;
|
|
TRACE("%s resource %d old %f delta %f charge %d", __func__, idxs[i],
|
|
vals[i], val, charge);
|
|
if (charge) {
|
|
vals[i] += val;
|
|
} else {
|
|
vals[i] -= val;
|
|
}
|
|
}
|
|
|
|
put_resvals(array, naccounts, idxs, vals);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sched_try_alloc_on_host_(xce_sched_worker_t* w, va_list ap)
|
|
{
|
|
xce_scheduler_t* sched = va_arg(ap, xce_scheduler_t*);
|
|
xce_sched_ctx_t* ctx = va_arg(ap, xce_sched_ctx_t*);
|
|
xce_sched_obj_t* schedobj = ctx->schedobj;
|
|
xce_primary_resreq_t* primary = ctx->primary;
|
|
int rc;
|
|
xvec_t* uncomit = NULL;
|
|
|
|
rc = sched_meet_host_selection(sched, w, primary->select);
|
|
if (rc == 0) {
|
|
if (ctx->reason == NULL) {
|
|
ctx->reason = make_pendreason(0);
|
|
}
|
|
|
|
if (ctx->reason == NULL) {
|
|
return -errno;
|
|
}
|
|
|
|
add_job_preason(ctx->reason->reasons, PREASON_MATCH_FAIL, w->name,
|
|
NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
rc = sched_ctx_get_uncomit_host(&uncomit, ctx, w->idx);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
ctx->worker = w;
|
|
|
|
rc = plugin_prepare_allocation(sched, ctx);
|
|
if (rc < 0) {
|
|
ctx->worker = NULL;
|
|
|
|
return rc;
|
|
}
|
|
|
|
rc = sched_check_host_satisfy(sched, w, uncomit, &(ctx->alloc_req.host),
|
|
&(ctx->unmeet));
|
|
if (rc < 0) {
|
|
/* FIXME: */
|
|
return rc;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
int i;
|
|
if (ctx)
|
|
|
|
for (i = 0; i < ctx->unmeet.count; i++) {
|
|
xce_resource_t* r = NULL;
|
|
|
|
rc = vget(sched->resmgr, ((int*)ctx->unmeet.idxs.data)[i],
|
|
(void**)&r);
|
|
if (rc < 0) {
|
|
continue;
|
|
}
|
|
|
|
add_job_preason(ctx->reason->reasons, PREASON_NO_RESOURCE,
|
|
w->name, r->name);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
rc = xvec_idval(uncomit, &(ctx->alloc_req.host), XVEC_ADD_FLOAT);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
|
|
/* host is OK */
|
|
return 1;
|
|
}
|
|
|
|
int sched_try_alloc_on_hosts(xce_worker_group_t* g, va_list ap)
|
|
{
|
|
/* FIXME: !!! */
|
|
xce_sched_worker_t* w = xlist_get(&(g->members), 0);
|
|
return sched_try_alloc_on_host_(w, ap);
|
|
}
|
|
|
|
static int sched_update_account(xce_scheduler_t* sched,
|
|
xce_job_account_t* account, int charge)
|
|
{
|
|
int i;
|
|
|
|
sched_update_res_accounts(sched, &(sched->sharedInUse), 1,
|
|
account->nres_shared, account->accounts_shared,
|
|
charge);
|
|
for (i = 0; i < account->nhosts; i++) {
|
|
xce_host_account_t* haccount = account->accounts[i];
|
|
xce_sched_worker_t* w = NULL;
|
|
|
|
if (haccount == NULL) {
|
|
continue;
|
|
}
|
|
|
|
w = sched_get_worker(sched, haccount->host);
|
|
if (w == NULL) {
|
|
continue;
|
|
}
|
|
|
|
sched_update_res_accounts(sched, &(w->resInUse), 0, haccount->naccount,
|
|
haccount->accounts, charge);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int sched_charge_account(xce_scheduler_t* sched, xce_job_account_t* acct)
|
|
{
|
|
return sched_update_account(sched, acct, 1);
|
|
}
|
|
|
|
int sched_uncharge_account(xce_scheduler_t* sched, xce_job_account_t* acct)
|
|
{
|
|
return sched_update_account(sched, acct, 0);
|
|
}
|
|
|
|
int sched_commit_job_alloc(xce_scheduler_t* sched, xce_sched_job_t* job)
|
|
{
|
|
if (job->accounts == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return sched_charge_account(sched, job->accounts);
|
|
}
|
|
|
|
static float get_resval_by_id(xvec_idval_t* vec, int idx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < vec->count; i++) {
|
|
int residx;
|
|
|
|
xvec_get(&(vec->idxs), i, &residx);
|
|
|
|
if (residx == idx) {
|
|
float resval;
|
|
|
|
xvec_get(&(vec->vals), i, &resval);
|
|
|
|
return resval;
|
|
}
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
int sched_fill_limit_requests(xce_scheduler_t* sched, xvec_idval_t* vec,
|
|
xlist_t* limits)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < limits->count; i++) {
|
|
xce_vres_t* vres = (xce_vres_t*)xlist_get(limits, i);
|
|
float val = 0.0;
|
|
|
|
if (vres == NULL) {
|
|
continue;
|
|
}
|
|
|
|
val = get_resval_by_id(vec, vres->res->idx);
|
|
|
|
xvec_idval_store(vec, vres->vres->idx, val);
|
|
}
|
|
|
|
return 0;
|
|
}
|