crossroads

Git mirror of https://crossroads.e-tunity.com/
git clone git://git.finwo.net/app/crossroads
Log | Files | Refs

choosebackend.c (11774B)


      1 /*************************************************************************
      2  * This file is part of Crosroads 1.23, a load balancer and fail over
      3  * utility for TCP. Copyright (c) Karel Kubat, distributed under GPL.
      4  * Visit http://crossroads.e-tunity.com for information.
      5  *************************************************************************/
      6 #include "crossroads.h"
      7 
      8 void choose_backend () {
      9     int backends[MAX_BACKEND], nbackends = 0, i, j, k,
     10 	target_set = 0, flat_weights = 1, weights[MAX_BACKEND], *sel_weights,
     11 	tot_weights, lo_val, hi_val, done;
     12     unsigned long long values[MAX_BACKEND], nbest;
     13     unsigned nclients;
     14     char *exthandler;
     15     FILE *f;
     16     char buf[80];
     17 #   ifndef HAVE_SRANDDEV    
     18     struct timeval tv;
     19 #   endif
     20 
     21     /* Check that we're allowed to accept at all. */
     22     if (activeservice->maxconnections &&
     23 	servicereport->nclients >= activeservice->maxconnections) {
     24 	warning ("Service %s: max clients %d exceeded",
     25 		 activeservice->name,
     26 		 activeservice->maxconnections);
     27 	current_backend = -1;
     28 	return;
     29     }
     30 
     31     /* Populate the array of selectable backends. */
     32     for (i = 0; i < activeservice->nbackend; i++) {
     33 	/* Backend i is a candidate if:
     34 	 * (a) it is available, AND
     35 	 * (b) there's no max of connections, or the actual nr of connections
     36 	 *     is below max
     37 	 */
     38 	if (servicereport->backendstate[i].avail == st_available &&
     39 	    (activeservice->backend[i].maxconnections == 0 ||
     40 	     activeservice->backend[i].maxconnections >
     41 	     servicereport->backendstate[i].nclients)) {
     42 	    msg ("Service %s: "
     43 		 "candidate back end: %d (max clients %d, active %d, "
     44 		 "state %s)",
     45 		 activeservice->name,
     46 		 i, activeservice->backend[i].maxconnections,
     47 		 servicereport->backendstate[i].nclients,
     48 		 state_to_string(servicereport->backendstate[i].avail));
     49 	    backends[nbackends++] = i;
     50 	}
     51     }
     52 
     53     /* No backends? Nogo. */
     54     if (!nbackends) {
     55 	current_backend = -1;
     56 	warning ("No active backends to select!");
     57 	return;
     58     }
     59 
     60     /* Only one backend? Then it's always the first one,
     61      * whatever you do. */
     62     if (nbackends == 1) {
     63 	current_backend = backends[0];
     64 	servicereport->last_backend = current_backend;
     65 	msg ("Service %s: only 1 backend to select (%d)",
     66 	     activeservice->name, current_backend);
     67 	return;
     68     }
     69 
     70     /* More than 1 backend available.. make a wise choice. */
     71     switch (activeservice->dispatchtype) {
     72 
     73     case ds_roundrobin:
     74 	/* Find the currently used backend, go one forward. */
     75 	msg ("Service %s: last roundrobin back end was %d",
     76 	     activeservice->name, servicereport->last_backend);
     77 	for (i = 0; i < nbackends; i++)
     78 	    if (backends[i] == servicereport->last_backend) {
     79 		i++;
     80 		i %= nbackends;
     81 		current_backend = backends[i];
     82 		servicereport->last_backend = current_backend;
     83 		msg ("Service %s: next roundrobin back end is %d",
     84 		     activeservice->name, current_backend);
     85 		return;
     86 	    }
     87 	/* None found.. try the first one (run  to momma). */
     88 	current_backend = backends[0];
     89 	servicereport->last_backend = current_backend;
     90 	msg ("Service %s: "
     91 	     "chosen backend (roundrobin, without historical info): %d",
     92 	     activeservice->name, current_backend);
     93 	return;
     94 	
     95     case ds_random:
     96 	/* Re-randomize. */
     97 #	ifdef HAVE_SRANDDEV
     98 	sranddev();
     99 	msg ("Service %s: randomier seeded with randdev", activeservice->name);
    100 #	else	
    101 	gettimeofday (&tv, 0);
    102 	srand ((unsigned) tv.tv_usec);
    103 	msg ("Service %s: randomizer seeded with %u",
    104 	     activeservice->name, (unsigned) tv.tv_usec);
    105 #	endif
    106 	
    107 	/* First of all let's see if all the weights are the same. */
    108 	tot_weights = 0;
    109 	for (i = 0; i < nbackends; i++) {
    110 	    values[i] = activeservice->backend[backends[i]].weight;
    111 	    if (activeservice->backend[backends[i]].weight != 1)
    112 		flat_weights = 0;
    113 	}
    114 
    115 	if (! flat_weights) {
    116 	    /* Non-flat weight random balancing. Determine the chance
    117 	     * to hit a given back end. First we populate the weights array.
    118 	     * Eg imagine we have 4 back ends with weights 1,2,3,4. Then
    119 	     * ultimately we need to get a selection array of indices:
    120 	     * 0000111223, meaning that the chance to hit index 0 is 4/10
    121 	     * and so on */
    122 
    123 	    /* First of all we reverse the values vector, to get 4,3,2,1 */
    124 	    done = 0;
    125 	    while (!done) {
    126 		done = 1;
    127 		lo_val = 9999999;
    128 		hi_val = 0;
    129 		/* Find highest/lowest weights in values vector. */
    130 		for (i = 0; i < nbackends; i++) {
    131 		    if (!values[i])
    132 			continue;
    133 		    done = 0;
    134 		    if (lo_val >= values[i])
    135 			lo_val = values[i];
    136 		    if (hi_val <= values[i])
    137 			hi_val = values[i];
    138 		}
    139 		
    140 		if (done)
    141 		    break;
    142 
    143 		/* Stick the values into the duplicate 'weights' array,
    144 		   but the other way 'round. */
    145 		for (i = 0; i < nbackends; i++)
    146 		    if (values[i] == lo_val)
    147 			weights[i] = hi_val;
    148 		    else if (values[i] == hi_val)			
    149 			weights[i] = lo_val;
    150 
    151 		for (i = 0; i < nbackends; i++)
    152 		    if (values[i] == lo_val) {
    153 			tot_weights += hi_val;
    154 			values[i] = 0;
    155 		    } else if (values[i] == hi_val) {
    156 			tot_weights += lo_val;
    157 			values[i] = 0;
    158 		    }
    159 	    }
    160 	    
    161 	    /* The 'weights' vector is now the reverse of 'values'.
    162 	     * Create a selection array. */
    163 	    sel_weights = xmalloc (tot_weights * sizeof(int));
    164 	    k = 0;
    165 	    for (i = 0; i < nbackends; i++)
    166 		for (j = 0; j < weights[i]; j++) {
    167 		    sel_weights[k] = i;
    168 		    k++;
    169 		}
    170 
    171 	    /* Debugging: 
    172 	     * for (k = 0; k < tot_weights; k++)
    173 	     *	 msg ("random: sel_weight[%d] = %d", k, sel_weights[k]);
    174 	     */
    175 	    
    176 	    /* Select the next random back end. Algorithm is:
    177 	     * i = rand() % tot_weights;
    178 	     * j = sel_weights[i];
    179 	     * k = backends[j];
    180 	     * current_backend = k;
    181 	     * msg ("i=%d j=%d, k=%d", i, j, k);
    182 	     */
    183 	    i = rand() % tot_weights;
    184 	    current_backend = backends[sel_weights[i]];
    185 	    servicereport->last_backend = current_backend;
    186 	    free (sel_weights);
    187 	    msg ("Service %s: "
    188 		 "chosen backend (weighted random): %d at index %d",
    189 		 activeservice->name, current_backend, i);
    190 	    return;
    191 	}
    192 
    193 	/* ELSE: Choose a random one of the availables. */
    194 	current_backend = backends[rand() % nbackends];
    195 	servicereport->last_backend = current_backend;
    196 	msg ("Service %s: chosen backend (flat-weight random): %d",
    197 	     activeservice->name, current_backend);
    198 	return;
    199 
    200     case ds_bysize:
    201 	/* Fill the values */
    202 	for (i = 0; i < nbackends; i++) {
    203 	    /* When dispatch over #-connections is in place, then we
    204 	     * compute using the averaged value. However if the averaged
    205 	     * value is 0 (not yet computed), we take the true value. */
    206 	    if (activeservice->dispatchover &&
    207 		servicereport->backendstate[backends[i]].avg_nbytes)
    208 		values[i] =
    209 		    servicereport->backendstate[backends[i]].avg_nbytes *
    210 		    activeservice->backend[backends[i]].weight;
    211 	    else
    212 		values[i] = servicereport->backendstate[backends[i]].nbytes *
    213 		    activeservice->backend[backends[i]].weight;
    214 	    msg ("Service %s: "
    215 		 "by size weighing backend %d has weight %d, value %u"
    216 		 " (bytes=%u, avgbytes=%u)",
    217 		 activeservice->name, 
    218 		 backends[i], 
    219 		 activeservice->backend[backends[i]].weight,
    220 		 (unsigned) values[i],
    221 		 (unsigned) servicereport->backendstate[backends[i]].nbytes,
    222 		 (unsigned)
    223 		     servicereport->backendstate[backends[i]].avg_nbytes);
    224 	}
    225 	/* Get the backend that has transported the least bytes */
    226 	for (i = 0; i < nbackends; i++)
    227 	    if (!target_set++ || values[i] < nbest) {
    228 		nbest = values[i];
    229 		current_backend = backends[i];
    230 		servicereport->last_backend = current_backend;
    231 		msg ("Service %s: "
    232 		     "by size weighing: best so far is %d (value %g)",
    233 		     activeservice->name, current_backend, nbest);
    234 	    }
    235 	msg ("Service %s: chosen backend (by size): %d",
    236 	     activeservice->name, current_backend);
    237 	return;
    238 
    239     case ds_byduration:
    240 	/* Fill the values */
    241 	for (i = 0; i < nbackends; i++) {
    242 	    /* When dispatch over #-connections is in place, then we
    243 	     * compute using the averaged value. However if the averaged
    244 	     * value is 0 (not yet computed), we take the true value. */
    245 	    if (activeservice->dispatchover &&
    246 		servicereport->backendstate[backends[i]].avg_nsec)
    247 		values[i] =
    248 		    servicereport->backendstate[backends[i]].avg_nsec *
    249 		    1000000 *
    250 		    activeservice->backend[backends[i]].weight;
    251 	    else
    252 		values[i] = servicereport->backendstate[backends[i]].nsec *
    253 		    1000000 *
    254 		    activeservice->backend[backends[i]].weight;
    255 	    msg ("Service %s: By duration weighing: backend %d has value %lu"
    256 		 " (sec=%g, avgsec=%g, weight=%d)",
    257 		 activeservice->name, 
    258 		 backends[i], values[i],
    259 		 servicereport->backendstate[backends[i]].nsec,
    260 		 servicereport->backendstate[backends[i]].avg_nsec,
    261 		 activeservice->backend[backends[i]].weight);
    262 	}
    263 	/* Get the backend that has transported the least connection secs */
    264 	for (i = 0; i < nbackends; i++)
    265 	    if (!target_set++ || values[i] < nbest) {
    266 		nbest = values[i];
    267 		current_backend = backends[i];
    268 		servicereport->last_backend = current_backend;
    269 		msg ("Service %s: "
    270 		     "by duration weighing: best so far is %d (value %g)",
    271 		     activeservice->name, current_backend, nbest);
    272 	    }
    273 	msg ("Service %s: chosen backend (by duration): %d",
    274 	     activeservice->name, current_backend);
    275 	return;
    276 
    277     case ds_byorder:
    278 	/* Get the first available back end in line. */
    279 	current_backend = backends[0];
    280 	servicereport->last_backend = current_backend;
    281 	msg ("Service %s: chosen backend (by order): %d",
    282 	     activeservice->name, current_backend);
    283 	return;
    284 
    285     case ds_byconnections:
    286 	/* Get the weighing values. */
    287 	for (i = 0; i < nbackends; i++) {
    288 	    values[i] =
    289 		servicereport->backendstate[backends[i]].nclients *
    290 		activeservice->backend[backends[i]].weight;
    291 	    msg ("Service %s: "
    292 		 "by connections weighing: backend %d has value %u",
    293 		 activeservice->name, backends[i], values[i]);
    294 	}
    295 	/* Find the minimum in the values array. */
    296 	for (i = 0; i < nbackends; i++)
    297 	    if (!target_set++ || values[i] < nclients) {
    298 		nclients = values[i];
    299 		current_backend = backends[i];
    300 		servicereport->last_backend = current_backend;
    301 		msg ("Service %s: "
    302 		     "by connections weighing: best so far is %d (value %u)",
    303 		     activeservice->name, current_backend, nclients);
    304 	    }
    305 	msg ("Service %s: chosen backend (by connections): %d",
    306 	     activeservice->name, current_backend);
    307 	return;
    308 
    309     case ds_externalhandler:
    310 	/* External handler to be called */
    311 	exthandler = str_expand_format (activeservice->dispatchext);
    312 	msg ("Service %s: calling external handler '%s'",
    313 	     activeservice->name, exthandler);
    314 	uid_assume();
    315 	if (! (f = popen (exthandler, "r")) ) {
    316 	    warning ("Service %s: failed to start external handler '%s': %s",
    317 		     activeservice->name, exthandler, strerror(errno));
    318 	    current_backend = backends[0];
    319 	    servicereport->last_backend = current_backend;
    320 	    uid_restore();
    321 	    return;
    322 	}
    323 	while (1) {
    324 	    if (fscanf (f, " %80s ", buf) < 1) {
    325 		msg ("Service %s: external handler signals end",
    326 		     activeservice->name);
    327 		pclose (f);
    328 		uid_restore();
    329 		break;
    330 	    }
    331 	    msg ("Service %s: external handler says '%s'",
    332 		 activeservice->name, buf);
    333 	    for (i = 0; i < nbackends; i++)
    334 		if (!strcmp (buf, activeservice->backend[backends[i]].name)) {
    335 		    msg ("Service %s: selecting back end %s due to "
    336 			 "external handler", activeservice->name, buf);
    337 		    current_backend = backends[i];
    338 		    servicereport->last_backend = current_backend;
    339 		    pclose (f);
    340 		    uid_restore();
    341 		    return;
    342 		}
    343 	}
    344 	warning ("Service %s: external handler didn't reply with "
    345 		 "a selectable back end", activeservice->name);
    346 	current_backend = backends[0];
    347 	servicereport->last_backend = current_backend;
    348 	return;
    349 
    350     default:
    351 	/* Internal fry.. */
    352 	error ("Service %s: internal error: unhandled dispatch type %d",
    353 	       activeservice->name, activeservice->dispatchtype);
    354     }
    355 }