summaryrefslogblamecommitdiffstats
path: root/kernel/net.c
blob: e5c396bce9e46e138fe610a82bbaea548840685b (plain) (tree)























































































































































































































































                                                                                      
/*
 * net.c - network stuff for DNBD 
 * Copyright (C) 2006 Thorsten Zitterell <thorsten@zitterell.de>
 */

#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/random.h>

#include "net.h"

/* return pointer to server structure */
dnbd_server_t *dnbd_get_server(dnbd_servers_t * servers, int id)
{
	if ((0 < id) && (id <= SERVERS_MAX))
		return &servers->serverlist[id - 1];
	else
		return NULL;
}

/* add a new server */
int dnbd_set_serverid(dnbd_servers_t * servers, int id)
{
	int result = -EINVAL;

	dnbd_server_t *server;

	if (!(server = dnbd_get_server(servers, id)))
		goto out;

	switch (server->state) {
	case SERVER_INACTIVE:
		break;
	case SERVER_ACTIVE:
		result = -EEXIST;
		goto out;
	case SERVER_STALLED:
		server->state = SERVER_ACTIVE;
		result = 0;
		goto out;
	}

	server->state = SERVER_ACTIVE;
	server->id = id;
	server->srtt = servers->timeout_min;
	server->weight = 0;
	server->last_rx = jiffies;
	server->last_tx = jiffies;

	servers->count++;
	result = 0;
      out:
	return result;
}

/* return server according to their weights (= probability) */
int dnbd_next_server(dnbd_servers_t * servers)
{
	int i;
	char rnd;
	dnbd_server_t *server = NULL;
	int id = 0;
	int weightsum = 0;

	/* get random byte from kernel */
	get_random_bytes(&rnd, 1);
	
	for (i = 0; i < SERVERS_MAX; i++) {
		server = &servers->serverlist[i];
		if ((server->state == SERVER_ACTIVE)
		    && ((weightsum += server->weight) > (unsigned char) rnd)) {
			id = server->id;
			    break;
		    }
	}

	/* alternatively, use server with highest weight */
/*	for (i = 0; i < SERVERS_MAX; i++) {
		server = &servers->serverlist[i];
		if ((server->state == SERVER_ACTIVE)
		    && (server->weight > weight))
			id = server->id;
	}*/

	return id;
}

/* remove a server */
void dnbd_rem_servers(dnbd_servers_t * servers)
{
	if (!servers->serverlist)
		return;

	kfree(servers->serverlist);
	servers->serverlist = NULL;
}

/* remove all servers */
void dnbd_clean_servers(dnbd_servers_t * servers)
{
	int i;
	for (i = 0; i < SERVERS_MAX; i++) {
		servers->serverlist[i].state = 0;
	}

}

/* update round trip time of a server */
void dnbd_rtt_server(dnbd_servers_t * servers, int id, int rtt)
{
	dnbd_server_t *server;

	if (!(server = dnbd_get_server(servers, id)))
		goto out;

	if (rtt > servers->timeout_max)
		rtt = TIMEOUT_MAX;
	else if (rtt < servers->timeout_min)
		rtt = TIMEOUT_MIN;

	down(&servers->sema);
	server->srtt = ((SRTT_BETA * server->srtt
			 + (((SRTT_BETA_BASE - SRTT_BETA) * rtt) << SRTT_SHIFT))
			/ SRTT_BETA_BASE);
	up(&servers->sema);

      out:
	return;
}

/* recalculate server weights */
void dnbd_servers_weight(dnbd_servers_t * servers)
{
	int i;
	int num_servers = 0;
	long weightsum = 0;
	long prod = 0;
	long asrtt = 0;
	int srtt = 0;
	dnbd_server_t *server;

	/* 
	 * float arithmetics in kernel would be nice...
	 */
	down(&servers->sema);

	for (i = 0; i < SERVERS_MAX; i++) {
		server = &servers->serverlist[i];

		if (server->state == SERVER_ACTIVE) {
			if (server->last_tx >
			    server->last_rx + servers->timeout_stalled) {
				printk(KERN_ERR
				       "dnbd: disable server #%i\n",
				       i + 1);
				server->state = SERVER_STALLED;
				continue;
			}
			srtt = (server->srtt ? server->srtt : 1);
			weightsum += WEIGHT_FACTOR / srtt;
			asrtt += srtt;
			num_servers++;
		}
	}

	if (!num_servers)
		goto out;

	servers->asrtt = asrtt / num_servers;

	for (i = 0; i < SERVERS_MAX; i++) {
		server = &servers->serverlist[i];

		if (server->state == SERVER_ACTIVE) {
			srtt = (server->srtt ? server->srtt : 1);
			prod = srtt * weightsum;

			if (prod > 0)
				server->weight = WEIGHT_NORMAL * WEIGHT_FACTOR / prod;
			else
				server->weight = WEIGHT_NORMAL / num_servers;
		}
	}
      out:
	up(&servers->sema);

}

/* fill buffer with server statistics in human readable form for /proc */
int dnbd_show_servers(dnbd_servers_t * servers, void *buf, int size)
{
	int i, n = 0;
	dnbd_server_t *server;

	n += snprintf(buf + n, size - n,
		      " timeout_min: %i jiffies\n timeout_max: %i jiffies\n",
		      servers->timeout_min, servers->timeout_max);

	n += snprintf(buf + n, size - n, "Average SRTT: %i\n",
		      servers->asrtt >> SRTT_SHIFT);

	for (i = 0; i < SERVERS_MAX; i++) {
		server = &servers->serverlist[i];

		switch (server->state) {
		case SERVER_INACTIVE:
			continue;
		case SERVER_STALLED:
			n += snprintf(buf + n, size - n,
				      " id: %i (stalled)\n", server->id);
			continue;
		default:
			n += snprintf(buf + n, size - n, " id: %i\n",
				      server->id);
		}
		n += snprintf(buf + n, size - n,
			      " srtt: %i\n", server->srtt >> SRTT_SHIFT);
		n += snprintf(buf + n, size - n,
			      " weight: %i (of %i)\n", server->weight,WEIGHT_NORMAL);
	}

	return n;
}

/* initialize servers */
int dnbd_servers_init(dnbd_servers_t * servers)
{
	int i;

	spin_lock_init(&servers->lock);
	init_MUTEX(&servers->sema);

	if (!(servers->serverlist =
	      (dnbd_server_t *) kmalloc(SERVERS_MAX *
					sizeof(dnbd_server_t),
					GFP_KERNEL)))
		return -EINVAL;

	for (i = 0; i < SERVERS_MAX; i++) {
		servers->serverlist[i].state = 0;
	}

	servers->count = 0;
	servers->timeout_min = TIMEOUT_MIN;
	servers->timeout_max = TIMEOUT_MAX;
	servers->timeout_stalled = TIMEOUT_STALLED;
	return 0;
}