// -*- c-basic-offset: 4 -*-
/*
 * sortedsched.{cc,hh} -- bin-packing sort for tasks (SMP Click)
 * Benjie Chen, Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2004 Regents of the University of California
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <click/standard/scheduleinfo.hh>
#include "sortedsched.hh"
#include <click/task.hh>
#include <click/routerthread.hh>
#include <click/router.hh>
#include <click/master.hh>
#include <click/glue.hh>
#include <click/confparse.hh>
#include <click/task.hh>
#include <click/error.hh>

#define DEBUG 0
#define KEEP_GOOD_ASSIGNMENT 1


BalancedThreadSched::BalancedThreadSched()
    : _timer(this)
{
}

BalancedThreadSched::~BalancedThreadSched()
{
}
  
int 
BalancedThreadSched::configure(Vector<String> &conf, ErrorHandler *errh)
{
    _interval = 1000;
    _increasing = true;
    if (cp_va_parse(conf, this, errh, 
		    cpOptional,
		    cpUnsigned, "interval", &_interval, 
		    cpBool, "increasing?", &_increasing, cpEnd) < 0)
	return -1;
    return 0;
}

int
BalancedThreadSched::initialize(ErrorHandler *)
{
    _timer.initialize(this);
    _timer.schedule_after_msec(10);
    return 0;
}

static int task_increasing_sorter(const void *va, const void *vb) {
    Task **a = (Task **)va, **b = (Task **)vb;
    return (*a)->cycles() - (*b)->cycles();
}

static int task_decreasing_sorter(const void *va, const void *vb) {
    Task **a = (Task **)va, **b = (Task **)vb;
    return (*b)->cycles() - (*a)->cycles();
}

void
BalancedThreadSched::run_timer(Timer *)
{
    Master *m = router()->master();

    // develop load list
    Vector<int> load;
    int total_load = 0;
    for (int tid = 0; tid < m->nthreads(); tid++) {
	RouterThread *thread = m->thread(tid);
	thread->lock_tasks();
	int thread_load = 0;
	Task *end = thread->task_end();
	for (Task *t = thread->task_begin(); t != end; t = thread->task_next(t))
	    thread_load += t->cycles();
	thread->unlock_tasks();
	total_load += thread_load;
	load.push_back(thread_load);
    }
    int avg_load = total_load / m->nthreads();

    for (int rounds = 0; rounds < m->nthreads(); rounds++) {
	// find min and max loaded threads
	int min_tid = 0, max_tid = 0;
	for (int tid = 1; tid < m->nthreads(); tid++)
	    if (load[tid] < load[min_tid])
		min_tid = tid;
	    else if (load[tid] > load[max_tid])
		max_tid = tid;

#if KEEP_GOOD_ASSIGNMENT
	// do nothing if load difference is minor
	if ((avg_load - load[min_tid] < (avg_load >> 3))
	    && (load[max_tid] - avg_load < (avg_load >> 3)))
	    break;
#endif

	// lock max_thread
	RouterThread *thread = m->thread(max_tid);
	thread->lock_tasks();
	
	// collect tasks from max-loaded thread
	total_load -= load[max_tid];
	load[max_tid] = 0;
	Vector<Task *> tasks;
	Task *end = thread->task_end();
	for (Task *t = thread->task_begin(); t != end; t = thread->task_next(t)) {
	    load[max_tid] += t->cycles();
	    tasks.push_back(t);
	}
	total_load += load[max_tid];
	avg_load = total_load / m->nthreads();
	
	// sort tasks by cycle count
	click_qsort(tasks.begin(), tasks.size(), sizeof(Task *), (_increasing ? task_increasing_sorter : task_decreasing_sorter));

	// move tasks
	int highwater = avg_load + (avg_load >> 2);
	for (Task **tt = tasks.begin(); tt < tasks.end(); tt++)
	    if (load[min_tid] + (*tt)->cycles() < highwater
		&& load[min_tid] + 2*(*tt)->cycles() <= load[max_tid]) {
		load[min_tid] += (*tt)->cycles();
		load[max_tid] -= (*tt)->cycles();
		(*tt)->move_thread(min_tid);
	    }

	// done with this round!
	thread->unlock_tasks();
    }
  
    _timer.schedule_after_msec(_interval);
}

#if 0
void
BalancedThreadSched::run_timer(Timer *)
{
    Vector<Task*> tasks;
    unsigned total_load = 0;
    unsigned avg_load;
    int n = router()->nthreads();
    int load[n];
  
    for(int i=0; i<n; i++) load[i] = 0;

    TaskList *task_list = router()->task_list();
    task_list->lock();
    Task *t = task_list->all_tasks_next();
    while (t != task_list) {
	total_load += t->cycles();
	if (t->home_thread_id() >= 0) 
	    load[t->home_thread_id()] += t->cycles();
	tasks.push_back(t);
	t = t->all_tasks_next();
    }
    task_list->unlock();
    avg_load = total_load / n;

#if KEEP_GOOD_ASSIGNMENT
    int ii;
    for(ii=0; ii<n; ii++) {
	unsigned diff = avg_load>load[ii] ? avg_load-load[ii] : load[ii]-avg_load;
	if (diff > (avg_load>>3)) {
#if DEBUG > 1
	    click_chatter("load balance, avg %u, diff %u", avg_load, diff);
#endif
	    break;
	}
    }
    if (ii == n) {
	_timer.schedule_after_msec(_interval);
	return;
    }
#endif
  
#if DEBUG > 1
    int print = 0;
    int high = 0;
#endif

    // slow sorting algorithm, but works okay for small number of tasks
    Vector<Task*> sorted;
    for (int i=0; i<tasks.size(); i++) {
	int max = 0;
	int which = -1;
	for (int j=0; j<tasks.size(); j++) {
	    if (tasks[j] && tasks[j]->cycles() > max) { 
		which = j; 
		max = tasks[j]->cycles();
	    }
	}
	assert(which >= 0);
	sorted.push_back(tasks[which]);
	tasks[which] = 0;
#if DEBUG > 1
	if (i == 0) high = max;
#endif
    }

#if DEBUG > 1
    if (high >= 500) {
	print = 1;
	unsigned now = click_jiffies();
	for(int i=0; i<sorted.size(); i++) {
	    Element *e = sorted[i]->element();
	    if (e)
		click_chatter("%u: %s %d, was on %d", 
			      now, e->name().c_str(), 
			      sorted[i]->cycles(), 
			      sorted[i]->home_thread_id());
	}
    }
#endif

    Vector<Task*> schedule[n];
    for(int i=0; i<n; i++) load[i] = 0;
    int min, which;
    int i = _increasing ? sorted.size()-1 : 0;
    while (1) {
	min = load[0];
	which = 0;
	for (int j = 1; j < n; j++) {
	    if (load[j] < min) {
		which = j;
		min = load[j];
	    }
	}
	load[which] += sorted[i]->cycles();
	schedule[which].push_back(sorted[i]);
	sorted[i]->move_thread(which);
	if (_increasing) {
	    if (i == 0) break;
	    else i--;
	} else {
	    if (i == sorted.size()-1) break;
	    else i++;
	}
    }
  
#if DEBUG > 1
    if (print) {
	unsigned now = click_jiffies();
	for(int i=0; i<sorted.size(); i++) {
	    Element *e = sorted[i]->element();
	    if (e) 
		click_chatter("%u: %s %d, now on %d (%d)", 
			      now, e->name().c_str(), 
			      sorted[i]->cycles(), 
			      sorted[i]->home_thread_id(), avg_load);
	}
	print = 0;
	click_chatter("\n");
    }
#endif

    _timer.schedule_after_msec(_interval);
}
#endif

ELEMENT_REQUIRES(linuxmodule smpclick)
EXPORT_ELEMENT(BalancedThreadSched BalancedThreadSched-SortedTaskSched)


syntax highlighted by Code2HTML, v. 0.9.1