myxce/src/scheduler/src/resource.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;
}