Commit 83cfa8ec authored by Robert Ricci's avatar Robert Ricci

Major changes to the way assign handles LAN nodes.

LAN nodes are no longer treated specially. Instead, I've introduced
the idea of 'static' types (old-style types retroactively become
'dynamic' types). While a pnode can only satisfy one dynamic type at a
time, it can always satisfy its static types (assuming it has enough
capacity left.) Static types are flagged by prepending them with a '*'
in the ptop file. So, for example, you may give switches the
'*lan:10000' type so that they can satisfy virtual LAN nodes. Of
course, other pnodes can have this type too, so that we can get
'trivial LANs'.

Actually, removing special treatment for LANs cleans up a lot of code.
However, it may have some negative impacts on solutions, since we're
not as smart about where to place LAN nodes as we used to be (they get
annealed along with everything else, and not migrated.) I haven't seen
any evidence of this yet, however.

This leaves us with a single type of special pnode, a switch.

Also added a new bit of syntax in ptop files - when '*' is given as a
the maxiumum load for a type, the node is allowed to take on an
infinite (well, actually, just a really big number of) vnodes of that
type.

ptopgen was modified to always report switches as being capable of
hosting LANs, and assign_wrapper now understands direct links to LANs,
which is what we get when the LAN is hosted directly on a switch.

Fixed a bug in scoring direct links, in which the penatly was being
added once when a direct link was mapped, but subtracted only once
when it was freed.

Added a '-T' option for doing simple self-testing. When adding a node
to the solution, assign records the score, adds the node, removes it
again, and checks to make sure that the resulting score is the same as
the original score. The usefulness of this feature in debugging
scoring problems cannot be understated...
parent 84ff51eb
......@@ -48,6 +48,18 @@ inline int accept(double change, double temperature)
return 0;
}
// finds a random pnode, of any type at all
tb_pnode *find_random_pnode() {
int choice = std::random() % num_vertices(PG);
pvertex_iterator vit, vendit;
tie(vit,vendit) = vertices(PG);
cout << "Chose pnode " << choice << " of " << num_vertices(PG) << endl;
for (int i = 0; i < choice;++vit, ++i) {
}
tb_pnode *curP = get(pvertex_pmap,*vit);
return curP;
}
tb_pnode *find_pnode(tb_vnode *vn)
{
#ifdef PER_VNODE_TT
......@@ -67,7 +79,6 @@ tb_pnode *find_pnode(tb_vnode *vn)
#ifdef PCLASS_SIZE_BALANCE
int acceptchance = 1000 * (*acceptable_types)[i]->size * 1.0 /
npnodes;
//cout << "Chance was " << acceptchance << endl;
if ((std::rand() % 1000) < acceptchance) {
continue;
}
......@@ -119,7 +130,8 @@ REDO_SEARCH:
}
#else
if ((*it)->typed && ((*it)->current_type.compare(vn->type) ||
((*it)->current_load >= (*it)->max_load))) {
((*it)->current_type_record->current_load >=
(*it)->current_type_record->max_load))) {
it++;
} else {
break;
......@@ -164,7 +176,7 @@ REDO_SEARCH:
/* When this is finished the state will reflect the best solution found. */
void anneal()
void anneal(bool scoring_selftest)
{
cout << "Annealing." << endl;
......@@ -213,6 +225,10 @@ void anneal()
/* Set up the initial counts */
init_score();
/* We'll check against this later to make sure that whe we've unmapped
* everything, the score is the same */
double initial_score = get_score();
/* Set up fixed nodes */
for (name_name_map::iterator fixed_it=fixed_nodes.begin();
fixed_it!=fixed_nodes.end();++fixed_it) {
......@@ -383,12 +399,7 @@ void anneal()
} else {
int start = std::random()%nnodes;
int choice = start;
#if defined(FIX_LAN_NODES) || defined(AUTO_MIGRATE)
while (get(vvertex_pmap,virtual_nodes[choice])->fixed ||
!get(vvertex_pmap,virtual_nodes[choice])->type.compare("lan")) {
#else
while (get(vvertex_pmap,virtual_nodes[choice])->fixed) {
#endif
choice = (choice +1) % nnodes;
if (choice == start) {
choice = -1;
......@@ -423,21 +434,6 @@ void anneal()
vn->vclass->dominant << endl;
#endif
}
if (vn->type.compare("lan") == 0) {
// LAN node
pvertex lanv = make_lan_node(vv);
#ifndef FREE_IMMEDIATELY
if (oldassigned) {
RDEBUG(cout << "removing: lan,oldassigned" << endl;)
remove_node(vv);
}
#endif
if (add_node(vv,lanv,false) != 0) {
delete_lan_node(lanv);
unassigned_nodes.push(vvertex_int_pair(vv,std::random()));
continue;
}
} else {
tb_pnode *newpnode = find_pnode(vn);
#ifndef FREE_IMMEDIATELY
if (oldassigned) {
......@@ -506,11 +502,6 @@ void anneal()
#else
int start = std::random()%nnodes;
int toremove = start;
#if defined(FIX_LAN_NODES) || defined(AUTO_MIGRATE)
while (get(vvertex_pmap,virtual_nodes[toremove])->fixed ||
(!get(vvertex_pmap,virtual_nodes[toremove])->assigned) ||
(get(vvertex_pmap,virtual_nodes[toremove])->type.compare("lan"))) {
#else
#ifdef SMART_UNMAP
#ifdef PER_VNODE_TT
......@@ -547,7 +538,6 @@ void anneal()
#else
while (get(vvertex_pmap,virtual_nodes[toremove])->fixed ||
(! get(vvertex_pmap,virtual_nodes[toremove])->assigned)) {
#endif
#endif
toremove = (toremove +1) % nnodes;
if (toremove == start) {
......@@ -570,6 +560,16 @@ void anneal()
#endif
if (newpnode != NULL) {
newpos = pnode2vertex[newpnode];
if (scoring_selftest) {
// Run a little test here - see if the score we get by adding
// this node, then removing it, is the same one we would have
// gotten otherwise
double oldscore = get_score();
if (!add_node(vv,newpos,false)) {
remove_node(vv);
}
assert(oldscore == get_score());
}
if (add_node(vv,newpos,false) != 0) {
unassigned_nodes.push(vvertex_int_pair(vv,std::random()));
continue;
......@@ -584,31 +584,6 @@ void anneal()
}
#ifndef SMART_UNMAP
}
#endif
}
#ifdef FIX_LAN_NODES
// OK, we're going to do something silly here: Migrate LAN nodes!
vvertex_iterator lanvertex_it,end_lanvertex_it;
vvertex_list migrate_lan_nodes;
tie(lanvertex_it,end_lanvertex_it) = vertices(VG);
for (;lanvertex_it!=end_lanvertex_it;++lanvertex_it) {
tb_vnode *vnode = get(vvertex_pmap,*lanvertex_it);
if (vnode->assigned) {
if (vnode->type.compare("lan") == 0) {
migrate_lan_nodes.push_front(*lanvertex_it);
}
}
}
while (migrate_lan_nodes.size() > 0) {
vvertex lanv = migrate_lan_nodes.front();
migrate_lan_nodes.pop_front();
RDEBUG(cout << "removing: migration" << endl;)
remove_node(lanv);
pvertex lanpv = make_lan_node(lanv);
add_node(lanv,lanpv,true);
}
#endif
newscore = get_score();
......@@ -717,9 +692,6 @@ void anneal()
RDEBUG(cout << "removing: rejected change" << endl;)
remove_node(vv);
if (oldassigned) {
if (vn->type.compare("lan") == 0) {
oldpos = make_lan_node(vv);
}
add_node(vv,oldpos,false);
}
}
......@@ -904,18 +876,7 @@ NOTQUITEDONE:
// Only revert if the best configuration has better violations
vvertex_list lan_nodes;
vvertex_iterator vvertex_it,end_vvertex_it;
if (!revert) {
// Just find LAN nodes, for migration
tie(vvertex_it,end_vvertex_it) = vertices(VG);
for (;vvertex_it!=end_vvertex_it;++vvertex_it) {
tb_vnode *vnode = get(vvertex_pmap,*vvertex_it);
if (vnode->assigned) {
if (vnode->type.compare("lan") == 0) {
lan_nodes.push_front(*vvertex_it);
}
}
}
} else {
if (revert) {
cout << "Reverting to best solution\n";
// Do a full revert
tie(vvertex_it,end_vvertex_it) = vertices(VG);
......@@ -929,14 +890,20 @@ NOTQUITEDONE:
RDEBUG(cout << "not removing: revert " << vnode->name << endl;)
}
}
// Check to make sure that our 'clean' solution scores the same as
// the initial score - if not, that indicates a bug
if (get_score() != initial_score) {
cerr << "*** WARNING: 'Clean' score does not match initial score" <<
endl << " This indicates a bug - contact the operators" <<
endl << " (initial score: " << initial_score <<
", current score: " << get_score() << ")" << endl;
}
tie(vvertex_it,end_vvertex_it) = vertices(VG);
for (;vvertex_it!=end_vvertex_it;++vvertex_it) {
tb_vnode *vnode = get(vvertex_pmap,*vvertex_it);
if (vnode->fixed) continue;
if (absassigned[*vvertex_it]) {
if (vnode->type.compare("lan") == 0) {
lan_nodes.push_front(*vvertex_it);
} else {
if (vnode->vclass != NULL) {
vnode->type = abstypes[*vvertex_it];
}
......@@ -944,20 +911,6 @@ NOTQUITEDONE:
}
}
}
}
// Do LAN migration
RDEBUG(cout << "Doing LAN migration" << endl;)
while (lan_nodes.size() > 0) {
vvertex lanv = lan_nodes.front();
lan_nodes.pop_front();
if (!revert) { // If reverting, we've already done this
RDEBUG(cout << "removing: migration" << endl;)
remove_node(lanv);
}
pvertex lanpv = make_lan_node(lanv);
add_node(lanv,lanpv,true);
}
tsteps++;
......
......@@ -80,8 +80,6 @@ inline int accept(double change, double temperature);
tb_pnode *find_pnode(tb_vnode *vn);
/* The big guy! */
void anneal();
void anneal(bool scoring_selftest);
#endif
......@@ -298,6 +298,7 @@ void print_help()
#ifdef PER_VNODE_TT
cerr << " -P - Prune unusable pclasses." << endl;
#endif
cerr << " -T - Doing some scoring self-testing." << endl;
exit(2);
}
......@@ -312,11 +313,6 @@ int type_precheck() {
for (name_count_map::iterator vtype_it=vtypes.begin();
vtype_it != vtypes.end();++vtype_it) {
// Ignore LAN vnodes
if (vtype_it->first == crope("lan")) {
continue;
}
// Check to see if there were any pnodes of the type at all
name_count_map::iterator ptype_it = ptypes.find(vtype_it->first);
if (ptype_it == ptypes.end()) {
......@@ -366,11 +362,6 @@ int mapping_precheck() {
for (;vit != vendit;vit++) {
tb_vnode *v = get(vvertex_pmap,*vit);
//
// No reason to do this work for LAN nodes!
if (!v->type.compare("lan")) {
continue;
}
pclass_vector *vec = new pclass_vector();
vnode_type_table[v->name] = tt_entry(0,vec);
......@@ -405,8 +396,12 @@ int mapping_precheck() {
// Grab the first node of the pclass as a representative sample
tb_pnode *pnode = *((*it)->members[this_type]->L.begin());
// Check the number of interfaces
if (pnode->total_interfaces >= v->num_links) {
// Check the number of interfaces - if the pnode is a switch,
// for now, we don't check this, since it can end up with more
// 'interfaces' due to the fact that it can have interswitch
// links
if ((pnode->total_interfaces >= v->num_links) ||
pnode->current_type.compare("switch")) {
matched_links++;
} else {
potential_match = false;
......@@ -529,6 +524,7 @@ int main(int argc,char **argv)
{
int seed = 0;
crope viz_prefix;
bool scoring_selftest = false;
// Handle command line
char ch;
......@@ -566,6 +562,8 @@ int main(int argc,char **argv)
case 'P':
prune_pclasses = true; break;
#endif
case 'T':
scoring_selftest = true; break;
default:
print_help();
}
......@@ -653,8 +651,9 @@ int main(int argc,char **argv)
}
timestart = used_time();
anneal();
anneal(scoring_selftest);
timeend = used_time();
#ifdef GNUPLOT_OUTPUT
fclose(scoresout);
fclose(tempout);
......
......@@ -63,20 +63,24 @@ Each vnode has a type and a valid solution must match each vnode to a
pnode that can support that type. A vnode has a single type but a
pnode may be able to support several types and even have multiple
vnodes in a single vnode. For example, a standard testbed PC with
four interfaces can either be a test node or up to two delay nodes. A
pnode that has not been matched to a vnode is called virgin and can
take on any of its types. Once it has been mapped to a vnode it
becomes typed and is locked into a certain type (that of the vnode).
When add_node is called the first thing checked is whether the pnode
has a suitable type for the vnode. If it is virgin then all its types
are checked, otherwise only its current type is checked. If the types
can not be matched then add_node returns an invalid condition,
otherwise it continues on.
four interfaces can either be a test node or up to two delay nodes.
The type system is composed of two classes of types: 'dynamic' types,
and 'static' types. A pnode can always satisfy any of its static
types, but has only one of its dynamic types 'active' at any time. A
pnode that has not been matched to a vnode using one of its dynamic
types is called virgin and can take on any of its dynamic types. Once
it has been mapped to a vnode it becomes typed and is locked into a
certain type (that of the vnode). When add_node is called the first
thing checked is whether the pnode has a suitable type for the vnode.
If the vnode uses a static type, the pnode must simply have available
capacity for that type. Otherwise, dynamic types are checked. If it is
virgin then all its types are checked, otherwise only its current type
is checked. If the types can not be matched then add_node returns an
invalid condition, otherwise it continues on.
Each type of a pnode also has a limit. This is the number of vnodes
of that type that can fit in the pnode. A vnode can not be placed in a pnode unless there is room left.
Under no conditions is a vnode mapped to a switch.
of that type that can fit in the pnode. A vnode can not be placed in
a pnode unless there is room left.
Links
-----
......
......@@ -67,8 +67,18 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
if (split_two(parsed_line[i],':',type,load,"1") != 0) {
ptop_error("Bad node line, no load for type: " << type << ".");
}
// Check to see if this is a static type
bool is_static = false;
if (type[0] == '*') {
is_static = true;
type.pop_front();
}
int iload;
if (sscanf(load.c_str(),"%d",&iload) != 1) {
if (!load.compare("*")) { // Allow * to mean unlimited
// (okay, a really big number)
iload = 10000000;
} else if (sscanf(load.c_str(),"%d",&iload) != 1) {
ptop_error("Bad node line, bad load: " << load << ".");
iload = 1;
}
......@@ -79,14 +89,14 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
}
if (type.compare("switch") == 0) {
isswitch = true;
p->types["switch"] = 1;
p->types["switch"] = new tb_pnode::type_record(1,false);
svertex sv = add_vertex(SG);
tb_switch *s = new tb_switch();
put(svertex_pmap,sv,s);
s->mate = pv;
p->sgraph_switch = sv;
} else {
p->types[type] = iload;
p->types[type] = new tb_pnode::type_record(iload,is_static);
}
}
for (i=i+1;(i<parsed_line.size()) && (parsed_line[i].compare("-")) ;++i) {
......@@ -103,7 +113,7 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
p->features[feature] = gcost;
}
/*
* Parse any other node optios or flags
* Parse any other node options or flags
*/
for (i=i+1; i < parsed_line.size(); ++i) {
crope flag,value;
......@@ -215,10 +225,11 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
pl->type = tb_plink::PLINK_INTERSWITCH;
}
}
srcnode->total_interfaces++;
dstnode->total_interfaces++;
if (ISSWITCH(srcnode) &&
! ISSWITCH(dstnode)) {
dstnode->switches.insert(srcv);
dstnode->total_interfaces++;
#ifdef PER_VNODE_TT
dstnode->total_bandwidth += ibw;
#endif
......@@ -226,7 +237,6 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
else if (ISSWITCH(dstnode) &&
! ISSWITCH(srcnode)) {
srcnode->switches.insert(dstv);
srcnode->total_interfaces++;
#ifdef PER_VNODE_TT
srcnode->total_bandwidth += ibw;
#endif
......
......@@ -66,10 +66,10 @@ int pclass_equiv(tb_pgraph &PG, tb_pnode *a,tb_pnode *b)
for (tb_pnode::types_map::iterator it=a->types.begin();
it!=a->types.end();++it) {
const crope &a_type = (*it).first;
const int a_max_nodes = (*it).second;
tb_pnode::type_record *a_type_record = (*it).second;
tb_pnode::types_map::iterator bit = b->types.find(a_type);
if ((bit == b->types.end()) ||((*bit).second != a_max_nodes))
if ((bit == b->types.end()) || ! ( *(*bit).second == *a_type_record) )
return 0;
}
......@@ -222,7 +222,8 @@ int pclass_set(tb_vnode *v,tb_pnode *p)
for (dit=c->members.begin();dit!=c->members.end();dit++) {
if ((*dit).first == p->current_type) {
// same class - only remove if node is full
if (p->current_load == p->max_load) {
if (p->types[p->current_type]->current_load ==
p->types[p->current_type]->max_load) {
(*dit).second->remove(p);
//#ifdef SMART_UNMAP
// c->used_members[(*dit).first]->push_back(p);
......@@ -246,7 +247,7 @@ int pclass_set(tb_vnode *v,tb_pnode *p)
}
c->used += 1.0/(p->max_load);
c->used += 1.0/(p->current_type_record->max_load);
return 0;
}
......@@ -266,12 +267,12 @@ int pclass_unset(tb_pnode *p)
// empty and the front if it's not. Since unset is called before
// remove_node empty means only one user.
if (! (*dit).second->exists(p)) {
assert(p->current_load > 0);
assert(p->types[p->current_type]->current_load > 0);
#ifdef PNODE_ALWAYS_FRONT
(*dit).second->push_front(p);
#else
#ifdef PNODE_SWITCH_LOAD
if (p->current_load == 0) {
if (p->types[p->current_type]->current_load == 0) {
#else
if (p->current_load == 1) {
#endif
......@@ -295,7 +296,7 @@ int pclass_unset(tb_pnode *p)
//cout << endl;
c->used -= 1.0/(p->max_load);
c->used -= 1.0/(p->current_type_record->max_load);
return 0;
}
......
......@@ -71,13 +71,34 @@ class tb_pnode {
public:
tb_pnode() { tb_pnode("(unnamed)"); }
tb_pnode(crope _name) : types(), features(), name(_name), typed(false),
max_load(0), current_load(0), switches(),
sgraph_switch(), switch_used_links(0),
current_type_record(NULL), total_load(0),
switches(), sgraph_switch(), switch_used_links(0),
total_interfaces(0), used_interfaces(0),
total_bandwidth(0), my_class(NULL), assigned_nodes(),
trivial_bw(0), trivial_bw_used(0) {;}
typedef hash_map<crope,int> types_map;
class type_record {
public:
type_record(int _max_load, bool _is_static) :
max_load(_max_load), current_load(0),
is_static(_is_static) { ; }
int max_load; // maximum load for this type
int current_load; // how many vnodes are assigned of this type
bool is_static; // whether this type is static or dynamic
bool operator==(const type_record &b) {
return ((max_load == b.max_load) && (is_static == b.is_static));
}
friend ostream &operator<<(ostream &o, const type_record& node)
{
o << "max_load = " << node.max_load <<
" current_load = " << node.current_load <<
" is_static = " << node.is_static;
}
};
typedef hash_map<crope,type_record*> types_map;
typedef hash_map<crope,double> features_map;
// contains max nodes for each type
......@@ -89,8 +110,10 @@ public:
crope name; // name of the node
bool typed; // has it been typed
crope current_type; // type the node is currently being used as
int max_load; // maxmium load for current type
int current_load; // how many vnodes are assigned to this pnode
type_record* current_type_record;
int total_load; // total load for all types
//int max_load; // maxmium load for current type
//int current_load; // how many vnodes are assigned to this pnode
pvertex_set switches; // what switches the node is attached to
svertex sgraph_switch; // only for switches, the corresponding
......@@ -111,6 +134,22 @@ public:
int trivial_bw_used; // the amount of bandwidth currently used by
// trivial links
bool set_current_type(crope type) {
if (types.find(type) == types.end()) {
//cout << "Failed to find type " << type << endl;
return false;
}
current_type = type;
typed = true;
current_type_record = types[type];
return true;
}
void remove_current_type() {
typed = false;
current_type_record = NULL;
}
// Output operator for debugging
friend ostream &operator<<(ostream &o, const tb_pnode& node)
{
......@@ -123,8 +162,8 @@ public:
for (features_map::const_iterator it = node.features.begin();
it!=node.features.end();it++)
cout << " " << (*it).first << " -> " << (*it).second << endl;
o << " Current Type: " << node.current_type <<
" (" << node.current_load << "/" << node.max_load << ")" << endl;
o << " Current Type: " << node.current_type << endl; /* <<
" (" << node.current_load << "/" << node.max_load << ")" << endl; */
o << " switches=";
for (pvertex_set::const_iterator it = node.switches.begin();
it != node.switches.end();++it) {
......
......@@ -144,16 +144,18 @@ void unscore_link_info(vedge ve,tb_pnode *src_pnode,tb_pnode *dst_pnode)
if (vlink->link_info.type == tb_link_info::LINK_DIRECT) {
// DIRECT LINK
SDEBUG(cerr << " direct link" << endl);
SSUB(SCORE_DIRECT_LINK);
unscore_link(vlink->link_info.plinks.front(),ve,src_pnode,dst_pnode);
vlink->link_info.plinks.clear();
} else if (vlink->link_info.type == tb_link_info::LINK_INTERSWITCH) {
// INTERSWITCH LINK
SDEBUG(cerr << " interswitch link" << endl);
pedge_path &path = vlink->link_info.plinks;
#ifndef INTERSWITCH_LENGTH
SSUB(SCORE_INTERSWITCH_LINK);
#endif
pedge_path &path = vlink->link_info.plinks;
// XXX: Potentially bogus;
int numinterlinks;
numinterlinks = -2;
......@@ -231,12 +233,33 @@ void remove_node(vvertex vv)
assert(pnode != NULL);
/*
* Find the type on the pnode that this vnode is associated with
*/
tb_pnode::types_map::iterator mit = pnode->types.find(vnode->type);
tb_pnode::type_record *tr;
if (mit == pnode->types.end()) {
// This is kind of a hack - if we don't find the type, then the vnode
// must have a vtype. So, we assume it must have been assigned to this
// node's dynamic type at some point.
// A consequence of this hack is that vtypes can't map to static types,
// for now.
RDEBUG(cerr << "Failed to find type " << vnode->type << " (for vnode " <<
vnode->name << ") on pnode " << pnode->name << endl;)
tr = pnode->current_type_record;
} else {
tr = mit->second;
}
/*
* Clean up the pnode's state
*/
if (!tr->is_static) {
if (pnode->my_class) {
pclass_unset(pnode);
}
}
#ifdef SMART_UNMAP
pnode->assigned_nodes.erase(vnode);
......@@ -288,7 +311,13 @@ void remove_node(vvertex vv)
// Only unscore the link if the vnode on the other end is assigned - this
// way, only the first end to be unmapped causes unscoring
if (! dest_vnode->assigned) continue;
if (! dest_vnode->assigned) {
//cerr << "Skipping link unassignment, because " << dest_vnode->name
//<< " is unassigned " << endl;
continue;
} else {
//cerr << "Unassigning, " << dest_vnode->name << " is assigned " << endl;
}
// Find the pnode on the ther end of the link, and unscore it!
pvertex dest_pv = dest_vnode->assignment;
......@@ -305,7 +334,8 @@ void remove_node(vvertex vv)
/*
* Adjust scores for the pnode