1    	// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2    	// vim: ts=8 sw=2 smarttab
3    	
4    	#include <algorithm>
5    	#include <cstdlib>
6    	#include <iostream>
7    	
8    	#include <boost/lexical_cast.hpp>
9    	#include <boost/icl/interval_map.hpp>
10   	#include <boost/algorithm/string/join.hpp>
11   	
12   	#include "common/SubProcess.h"
13   	#include "common/fork_function.h"
14   	
15   	#include "include/stringify.h"
16   	#include "CrushTester.h"
17   	#include "CrushTreeDumper.h"
18   	#include "include/ceph_features.h"
19   	
20   	
21   	using std::cerr;
22   	using std::cout;
23   	using std::map;
24   	using std::ostringstream;
25   	using std::string;
26   	using std::stringstream;
27   	using std::vector;
28   	
29   	void CrushTester::set_device_weight(int dev, float f)
30   	{
31   	  int w = (int)(f * 0x10000);
32   	  if (w < 0)
33   	    w = 0;
34   	  if (w > 0x10000)
35   	    w = 0x10000;
36   	  device_weight[dev] = w;
37   	}
38   	
39   	int CrushTester::get_maximum_affected_by_rule(int ruleno)
40   	{
41   	  // get the number of steps in RULENO
42   	  int rule_size = crush.get_rule_len(ruleno);
43   	  vector<int> affected_types;
44   	  map<int,int> replications_by_type;
45   	
46   	  for (int i = 0; i < rule_size; i++){
47   	    // get what operation is done by the current step
48   	    int rule_operation = crush.get_rule_op(ruleno, i);
49   	
50   	    // if the operation specifies choosing a device type, store it
51   	    if (rule_operation >= 2 && rule_operation != 4){
52   	      int desired_replication = crush.get_rule_arg1(ruleno,i);
53   	      int affected_type = crush.get_rule_arg2(ruleno,i);
54   	      affected_types.push_back(affected_type);
55   	      replications_by_type[affected_type] = desired_replication;
56   	    }
57   	  }
58   	
59   	  /*
60   	   * now for each of the affected bucket types, see what is the
61   	   * maximum we are (a) requesting or (b) have
62   	   */
63   	
64   	  map<int,int> max_devices_of_type;
65   	
66   	  // loop through the vector of affected types
67   	  for (vector<int>::iterator it = affected_types.begin(); it != affected_types.end(); ++it){
68   	    // loop through the number of buckets looking for affected types
69   	    for (map<int,string>::iterator p = crush.name_map.begin(); p != crush.name_map.end(); ++p){
70   	      int bucket_type = crush.get_bucket_type(p->first);
71   	      if ( bucket_type == *it)
72   	        max_devices_of_type[*it]++;
73   	    }
74   	  }
75   	
76   	  for(std::vector<int>::iterator it = affected_types.begin(); it != affected_types.end(); ++it){
77   	    if ( replications_by_type[*it] > 0 && replications_by_type[*it] < max_devices_of_type[*it] )
78   	      max_devices_of_type[*it] = replications_by_type[*it];
79   	  }
80   	
81   	  /*
82   	   * get the smallest number of buckets available of any type as this is our upper bound on
83   	   * the number of replicas we can place
84   	  */
85   	  int max_affected = std::max( crush.get_max_buckets(), crush.get_max_devices() );
86   	
87   	  for(std::vector<int>::iterator it = affected_types.begin(); it != affected_types.end(); ++it){
88   	    if (max_devices_of_type[*it] > 0 && max_devices_of_type[*it] < max_affected )
89   	      max_affected = max_devices_of_type[*it];
90   	  }
91   	
92   	  return max_affected;
93   	}
94   	
95   	
96   	map<int,int> CrushTester::get_collapsed_mapping()
97   	{
98   	  int num_to_check = crush.get_max_devices();
99   	  int next_id = 0;
100  	  map<int, int> collapse_mask;
101  	
102  	  for (int i = 0; i < num_to_check; i++){
103  	    if (crush.check_item_present(i)){
104  	      collapse_mask[i] = next_id;
105  	      next_id++;
106  	    }
107  	  }
108  	  
109  	  return collapse_mask;
110  	}
111  	
112  	void CrushTester::adjust_weights(vector<__u32>& weight)
113  	{
114  	
(1) Event cond_true: Condition "this->mark_down_device_ratio > 0", taking true branch.
115  	  if (mark_down_device_ratio > 0) {
116  	    // active buckets
117  	    vector<int> bucket_ids;
(2) Event cond_false: Condition "i < this->crush->get_max_buckets()", taking false branch.
118  	    for (int i = 0; i < crush.get_max_buckets(); i++) {
119  	      int id = -1 - i;
120  	      if (crush.get_bucket_weight(id) > 0) {
121  	        bucket_ids.push_back(id);
122  	      }
(3) Event loop_end: Reached end of loop.
123  	    }
124  	
125  	    // get buckets that are one level above a device
126  	    vector<int> buckets_above_devices;
(4) Event cond_true: Condition "i < bucket_ids.size()", taking true branch.
(8) Event cond_true: Condition "i < bucket_ids.size()", taking true branch.
(12) Event cond_false: Condition "i < bucket_ids.size()", taking false branch.
127  	    for (unsigned i = 0; i < bucket_ids.size(); i++) {
128  	      // grab the first child object of a bucket and check if it's ID is less than 0
129  	      int id = bucket_ids[i];
(5) Event cond_true: Condition "this->crush->get_bucket_size(id) == 0", taking true branch.
(9) Event cond_true: Condition "this->crush->get_bucket_size(id) == 0", taking true branch.
130  	      if (crush.get_bucket_size(id) == 0)
(6) Event continue: Continuing loop.
(10) Event continue: Continuing loop.
131  	        continue;
132  	      int first_child = crush.get_bucket_item(id, 0); // returns the ID of the bucket or device
133  	      if (first_child >= 0) {
134  	        buckets_above_devices.push_back(id);
135  	      }
(7) Event loop: Looping back.
(11) Event loop: Looping back.
(13) Event loop_end: Reached end of loop.
136  	    }
137  	
138  	    // permute bucket list
(14) Event cond_true: Condition "i < buckets_above_devices.size()", taking true branch.
(16) Event loop_begin: Jumped back to beginning of loop.
(17) Event cond_true: Condition "i < buckets_above_devices.size()", taking true branch.
(19) Event loop_begin: Jumped back to beginning of loop.
(20) Event cond_false: Condition "i < buckets_above_devices.size()", taking false branch.
139  	    for (unsigned i = 0; i < buckets_above_devices.size(); i++) {
140  	      unsigned j = lrand48() % (buckets_above_devices.size() - 1);
141  	      std::swap(buckets_above_devices[i], buckets_above_devices[j]);
(15) Event loop: Jumping back to the beginning of the loop.
(18) Event loop: Jumping back to the beginning of the loop.
(21) Event loop_end: Reached end of loop.
142  	    }
143  	
144  	    // calculate how many buckets and devices we need to reap...
145  	    int num_buckets_to_visit = (int) (mark_down_bucket_ratio * buckets_above_devices.size());
146  	
(22) Event cond_true: Condition "i < num_buckets_to_visit", taking true branch.
147  	    for (int i = 0; i < num_buckets_to_visit; i++) {
148  	      int id = buckets_above_devices[i];
149  	      int size = crush.get_bucket_size(id);
150  	      vector<int> items;
(23) Event cond_true: Condition "o < size", taking true branch.
(25) Event loop_begin: Jumped back to beginning of loop.
(26) Event cond_true: Condition "o < size", taking true branch.
(28) Event loop_begin: Jumped back to beginning of loop.
(29) Event cond_false: Condition "o < size", taking false branch.
151  	      for (int o = 0; o < size; o++)
(24) Event loop: Jumping back to the beginning of the loop.
(27) Event loop: Jumping back to the beginning of the loop.
(30) Event loop_end: Reached end of loop.
152  	        items.push_back(crush.get_bucket_item(id, o));
153  	
154  	      // permute items
(31) Event cond_true: Condition "o < size", taking true branch.
(33) Event loop_begin: Jumped back to beginning of loop.
(34) Event cond_true: Condition "o < size", taking true branch.
(36) Event loop_begin: Jumped back to beginning of loop.
(37) Event cond_false: Condition "o < size", taking false branch.
155  	      for (int o = 0; o < size; o++) {
156  	        int j = lrand48() % (crush.get_bucket_size(id) - 1);
157  	        std::swap(items[o], items[j]);
(32) Event loop: Jumping back to the beginning of the loop.
(35) Event loop: Jumping back to the beginning of the loop.
(38) Event loop_end: Reached end of loop.
158  	      }
159  	
160  	      int local_devices_to_visit = (int) (mark_down_device_ratio*size);
(39) Event cond_true: Condition "o < local_devices_to_visit", taking true branch.
161  	      for (int o = 0; o < local_devices_to_visit; o++){
(40) Event negative_return_fn: Function "this->crush->get_bucket_item(id, o)" returns a negative number. [details]
(41) Event assign: Assigning: "item" = "this->crush->get_bucket_item(id, o)".
Also see events: [negative_returns]
162  	        int item = crush.get_bucket_item(id, o);
(42) Event negative_returns: "item" is passed to a parameter that cannot be negative. [Note: The source code implementation of the function has been overridden by a builtin model.]
Also see events: [negative_return_fn][assign]
163  	        weight[item] = 0;
164  	      }
165  	    }
166  	  }
167  	}
168  	
169  	bool CrushTester::check_valid_placement(int ruleno, vector<int> in, const vector<__u32>& weight)
170  	{
171  	
172  	  bool valid_placement = true;
173  	  vector<int> included_devices;
174  	  map<string,string> seen_devices;
175  	
176  	  // first do the easy check that all devices are "up"
177  	  for (vector<int>::iterator it = in.begin(); it != in.end(); ++it) {
178  	    if (weight[(*it)] == 0) {
179  	      valid_placement = false;
180  	      break;
181  	    } else if (weight[(*it)] > 0) {
182  	      included_devices.push_back( (*it) );
183  	    }
184  	  }
185  	
186  	  /*
187  	   * now do the harder test of checking that the CRUSH rule r is not violated
188  	   * we could test that none of the devices mentioned in out are unique,
189  	   * but this is a special case of this test
190  	   */
191  	
192  	  // get the number of steps in RULENO
193  	  int rule_size = crush.get_rule_len(ruleno);
194  	  vector<string> affected_types;
195  	
196  	  // get the smallest type id, and name
197  	  int min_map_type = crush.get_num_type_names();
198  	  for (map<int,string>::iterator it = crush.type_map.begin(); it != crush.type_map.end(); ++it ) {
199  	    if ( (*it).first < min_map_type ) {
200  	      min_map_type = (*it).first;
201  	    }
202  	  }
203  	
204  	  string min_map_type_name = crush.type_map[min_map_type];
205  	
206  	  // get the types of devices affected by RULENO
207  	  for (int i = 0; i < rule_size; i++) {
208  	    // get what operation is done by the current step
209  	    int rule_operation = crush.get_rule_op(ruleno, i);
210  	
211  	    // if the operation specifies choosing a device type, store it
212  	    if (rule_operation >= 2 && rule_operation != 4) {
213  	      int affected_type = crush.get_rule_arg2(ruleno,i);
214  	      affected_types.push_back( crush.get_type_name(affected_type));
215  	    }
216  	  }
217  	
218  	  // find in if we are only dealing with osd's
219  	  bool only_osd_affected = false;
220  	  if (affected_types.size() == 1) {
221  	    if ((affected_types.back() == min_map_type_name) && (min_map_type_name == "osd")) {
222  	      only_osd_affected = true;
223  	    }
224  	  }
225  	
226  	  // check that we don't have any duplicate id's
227  	  for (vector<int>::iterator it = included_devices.begin(); it != included_devices.end(); ++it) {
228  	    int num_copies = std::count(included_devices.begin(), included_devices.end(), (*it) );
229  	    if (num_copies > 1) {
230  	      valid_placement = false;
231  	    }
232  	  }
233  	
234  	  // if we have more than just osd's affected we need to do a lot more work
235  	  if (!only_osd_affected) {
236  	    // loop through the devices that are "in/up"
237  	    for (vector<int>::iterator it = included_devices.begin(); it != included_devices.end(); ++it) {
238  	      if (valid_placement == false)
239  	        break;
240  	
241  	      // create a temporary map of the form (device type, device name in map)
242  	      map<string,string> device_location_hierarchy = crush.get_full_location(*it);
243  	
244  	      // loop over the types affected by RULENO looking for duplicate bucket assignments
245  	      for (vector<string>::iterator t = affected_types.begin(); t != affected_types.end(); ++t) {
246  	        if (seen_devices.count( device_location_hierarchy[*t])) {
247  	          valid_placement = false;
248  	          break;
249  	        } else {
250  	          // store the devices we have seen in the form of (device name, device type)
251  	          seen_devices[ device_location_hierarchy[*t] ] = *t;
252  	        }
253  	      }
254  	    }
255  	  }
256  	
257  	  return valid_placement;
258  	}
259  	
260  	int CrushTester::random_placement(int ruleno, vector<int>& out, int maxout, vector<__u32>& weight)
261  	{
262  	  // get the total weight of the system
263  	  int total_weight = 0;
264  	  for (unsigned i = 0; i < weight.size(); i++)
265  	    total_weight += weight[i];
266  	
267  	  if (total_weight == 0 ||
268  	      crush.get_max_devices() == 0)
269  	    return -EINVAL;
270  	
271  	  // determine the real maximum number of devices to return
272  	  int devices_requested = std::min(maxout, get_maximum_affected_by_rule(ruleno));
273  	  bool accept_placement = false;
274  	
275  	  vector<int> trial_placement(devices_requested);
276  	  int attempted_tries = 0;
277  	  int max_tries = 100;
278  	  do {
279  	    // create a vector to hold our trial mappings
280  	    int temp_array[devices_requested];
281  	    for (int i = 0; i < devices_requested; i++){
282  	      temp_array[i] = lrand48() % (crush.get_max_devices());
283  	    }
284  	
285  	    trial_placement.assign(temp_array, temp_array + devices_requested);
286  	    accept_placement = check_valid_placement(ruleno, trial_placement, weight);
287  	    attempted_tries++;
288  	  } while (accept_placement == false && attempted_tries < max_tries);
289  	
290  	  // save our random placement to the out vector
291  	  if (accept_placement)
292  	    out.assign(trial_placement.begin(), trial_placement.end());
293  	
294  	  // or don't....
295  	  else if (attempted_tries == max_tries)
296  	    return -EINVAL;
297  	
298  	  return 0;
299  	}
300  	
301  	void CrushTester::write_integer_indexed_vector_data_string(vector<string> &dst, int index, vector<int> vector_data)
302  	{
303  	  stringstream data_buffer (stringstream::in | stringstream::out);
304  	  unsigned input_size = vector_data.size();
305  	
306  	  // pass the indexing variable to the data buffer
307  	  data_buffer << index;
308  	
309  	  // pass the rest of the input data to the buffer
310  	  for (unsigned i = 0; i < input_size; i++) {
311  	    data_buffer << ',' << vector_data[i];
312  	  }
313  	
314  	  data_buffer << std::endl;
315  	
316  	  // write the data buffer to the destination
317  	  dst.push_back( data_buffer.str() );
318  	}
319  	
320  	void CrushTester::write_integer_indexed_vector_data_string(vector<string> &dst, int index, vector<float> vector_data)
321  	{
322  	  stringstream data_buffer (stringstream::in | stringstream::out);
323  	  unsigned input_size = vector_data.size();
324  	
325  	  // pass the indexing variable to the data buffer
326  	  data_buffer << index;
327  	
328  	  // pass the rest of the input data to the buffer
329  	  for (unsigned i = 0; i < input_size; i++) {
330  	    data_buffer << ',' << vector_data[i];
331  	  }
332  	
333  	  data_buffer << std::endl;
334  	
335  	  // write the data buffer to the destination
336  	  dst.push_back( data_buffer.str() );
337  	}
338  	
339  	void CrushTester::write_integer_indexed_scalar_data_string(vector<string> &dst, int index, int scalar_data)
340  	{
341  	  stringstream data_buffer (stringstream::in | stringstream::out);
342  	
343  	  // pass the indexing variable to the data buffer
344  	  data_buffer << index;
345  	
346  	  // pass the input data to the buffer
347  	  data_buffer << ',' << scalar_data;
348  	  data_buffer << std::endl;
349  	
350  	  // write the data buffer to the destination
351  	  dst.push_back( data_buffer.str() );
352  	}
353  	void CrushTester::write_integer_indexed_scalar_data_string(vector<string> &dst, int index, float scalar_data)
354  	{
355  	  stringstream data_buffer (stringstream::in | stringstream::out);
356  	
357  	  // pass the indexing variable to the data buffer
358  	  data_buffer << index;
359  	
360  	  // pass the input data to the buffer
361  	  data_buffer << ',' << scalar_data;
362  	  data_buffer << std::endl;
363  	
364  	  // write the data buffer to the destination
365  	  dst.push_back( data_buffer.str() );
366  	}
367  	
368  	int CrushTester::test_with_fork(int timeout)
369  	{
370  	  ostringstream sink;
371  	  int r = fork_function(timeout, sink, [&]() {
372  	      return test();
373  	    });
374  	  if (r == -ETIMEDOUT) {
375  	    err << "timed out during smoke test (" << timeout << " seconds)";
376  	  }
377  	  return r;
378  	}
379  	
380  	namespace {
381  	  class BadCrushMap : public std::runtime_error {
382  	  public:
383  	    int item;
384  	    BadCrushMap(const char* msg, int id)
385  	      : std::runtime_error(msg), item(id) {}
386  	  };
387  	  // throws if any node in the crush fail to print
388  	  class CrushWalker : public CrushTreeDumper::Dumper<void> {
389  	    typedef void DumbFormatter;
390  	    typedef CrushTreeDumper::Dumper<DumbFormatter> Parent;
391  	    int max_id;
392  	  public:
393  	    CrushWalker(const CrushWrapper *crush, unsigned max_id)
394  	      : Parent(crush, CrushTreeDumper::name_map_t()), max_id(max_id) {}
395  	    void dump_item(const CrushTreeDumper::Item &qi, DumbFormatter *) override {
396  	      int type = -1;
397  	      if (qi.is_bucket()) {
398  		if (!crush->get_item_name(qi.id)) {
399  		  throw BadCrushMap("unknown item name", qi.id);
400  		}
401  		type = crush->get_bucket_type(qi.id);
402  	      } else {
403  		if (max_id > 0 && qi.id >= max_id) {
404  		  throw BadCrushMap("item id too large", qi.id);
405  		}
406  		type = 0;
407  	      }
408  	      if (!crush->get_type_name(type)) {
409  		throw BadCrushMap("unknown type name", qi.id);
410  	      }
411  	    }
412  	  };
413  	}
414  	
415  	bool CrushTester::check_name_maps(unsigned max_id) const
416  	{
417  	  CrushWalker crush_walker(&crush, max_id);
418  	  try {
419  	    // walk through the crush, to see if its self-contained
420  	    crush_walker.dump(NULL);
421  	    // and see if the maps is also able to handle straying OSDs, whose id >= 0.
422  	    // "ceph osd tree" will try to print them, even they are not listed in the
423  	    // crush map.
424  	    crush_walker.dump_item(CrushTreeDumper::Item(0, 0, 0, 0), NULL);
425  	  } catch (const BadCrushMap& e) {
426  	    err << e.what() << ": item#" << e.item << std::endl;
427  	    return false;
428  	  }
429  	  return true;
430  	}
431  	
432  	static string get_rule_name(CrushWrapper& crush, int rule)
433  	{
434  	  if (crush.get_rule_name(rule))
435  	    return crush.get_rule_name(rule);
436  	  else
437  	    return string("rule") + std::to_string(rule);
438  	}
439  	
440  	void CrushTester::check_overlapped_rules() const
441  	{
442  	  namespace icl = boost::icl;
443  	  typedef std::set<string> RuleNames;
444  	  typedef icl::interval_map<int, RuleNames> Rules;
445  	  // <ruleset, type> => interval_map<size, {names}>
446  	  typedef std::map<std::pair<int, int>, Rules> RuleSets;
447  	  using interval = icl::interval<int>;
448  	
449  	  // mimic the logic of crush_find_rule(), but it only return the first matched
450  	  // one, but I am collecting all of them by the overlapped sizes.
451  	  RuleSets rulesets;
452  	  for (int rule = 0; rule < crush.get_max_rules(); rule++) {
453  	    if (!crush.rule_exists(rule)) {
454  	      continue;
455  	    }
456  	    Rules& rules = rulesets[{crush.get_rule_mask_ruleset(rule),
457  				     crush.get_rule_mask_type(rule)}];
458  	    rules += make_pair(interval::closed(crush.get_rule_mask_min_size(rule),
459  						crush.get_rule_mask_max_size(rule)),
460  			       RuleNames{get_rule_name(crush, rule)});
461  	  }
462  	  for (auto i : rulesets) {
463  	    auto ruleset_type = i.first;
464  	    const Rules& rules = i.second;
465  	    for (auto r : rules) {
466  	      const RuleNames& names = r.second;
467  	      // if there are more than one rules covering the same size range,
468  	      // print them out.
469  	      if (names.size() > 1) {
470  		err << "overlapped rules in ruleset " << ruleset_type.first << ": "
471  		    << boost::join(names, ", ") << "\n";
472  	      }
473  	    }
474  	  }
475  	}
476  	
477  	int CrushTester::test()
478  	{
479  	  if (min_rule < 0 || max_rule < 0) {
480  	    min_rule = 0;
481  	    max_rule = crush.get_max_rules() - 1;
482  	  }
483  	  if (min_x < 0 || max_x < 0) {
484  	    min_x = 0;
485  	    max_x = 1023;
486  	  }
487  	
488  	  // initial osd weights
489  	  vector<__u32> weight;
490  	
491  	  /*
492  	   * note device weight is set by crushtool
493  	   * (likely due to a given a command line option)
494  	   */
495  	  for (int o = 0; o < crush.get_max_devices(); o++) {
496  	    if (device_weight.count(o)) {
497  	      weight.push_back(device_weight[o]);
498  	    } else if (crush.check_item_present(o)) {
499  	      weight.push_back(0x10000);
500  	    } else {
501  	      weight.push_back(0);
502  	    }
503  	  }
504  	
505  	  if (output_utilization_all)
506  	    cerr << "devices weights (hex): " << std::hex << weight << std::dec << std::endl;
507  	
508  	  // make adjustments
509  	  adjust_weights(weight);
510  	
511  	
512  	  int num_devices_active = 0;
513  	  for (vector<__u32>::iterator p = weight.begin(); p != weight.end(); ++p)
514  	    if (*p > 0)
515  	      num_devices_active++;
516  	
517  	  if (output_choose_tries)
518  	    crush.start_choose_profile();
519  	  
520  	  for (int r = min_rule; r < crush.get_max_rules() && r <= max_rule; r++) {
521  	    if (!crush.rule_exists(r)) {
522  	      if (output_statistics)
523  	        err << "rule " << r << " dne" << std::endl;
524  	      continue;
525  	    }
526  	    if (ruleset >= 0 &&
527  		crush.get_rule_mask_ruleset(r) != ruleset) {
528  	      continue;
529  	    }
530  	    int minr = min_rep, maxr = max_rep;
531  	    if (min_rep < 0 || max_rep < 0) {
532  	      minr = crush.get_rule_mask_min_size(r);
533  	      maxr = crush.get_rule_mask_max_size(r);
534  	    }
535  	    
536  	    if (output_statistics)
537  	      err << "rule " << r << " (" << crush.get_rule_name(r)
538  	      << "), x = " << min_x << ".." << max_x
539  	      << ", numrep = " << minr << ".." << maxr
540  	      << std::endl;
541  	
542  	    for (int nr = minr; nr <= maxr; nr++) {
543  	      vector<int> per(crush.get_max_devices());
544  	      map<int,int> sizes;
545  	
546  	      int num_objects = ((max_x - min_x) + 1);
547  	      float num_devices = (float) per.size(); // get the total number of devices, better to cast as a float here 
548  	
549  	      // create a structure to hold data for post-processing
550  	      tester_data_set tester_data;
551  	      vector<float> vector_data_buffer_f;
552  	
553  	      // create a map to hold batch-level placement information
554  	      map<int, vector<int> > batch_per;
555  	      int objects_per_batch = num_objects / num_batches;
556  	      int batch_min = min_x;
557  	      int batch_max = min_x + objects_per_batch - 1;
558  	
559  	      // get the total weight of the system
560  	      int total_weight = 0;
561  	      for (unsigned i = 0; i < per.size(); i++)
562  	        total_weight += weight[i];
563  	
564  	      if (total_weight == 0)
565  		continue;
566  	
567  	      // compute the expected number of objects stored per device in the absence of weighting
568  	      float expected_objects = std::min(nr, get_maximum_affected_by_rule(r)) * num_objects;
569  	
570  	      // compute each device's proportional weight
571  	      vector<float> proportional_weights( per.size() );
572  	
573  	      for (unsigned i = 0; i < per.size(); i++)
574  	        proportional_weights[i] = (float) weight[i] / (float) total_weight;
575  	
576  	      if (output_data_file) {
577  	        // stage the absolute weight information for post-processing
578  	        for (unsigned i = 0; i < per.size(); i++) {
579  	          tester_data.absolute_weights[i] = (float) weight[i] / (float)0x10000;
580  	        }
581  	
582  	        // stage the proportional weight information for post-processing
583  	        for (unsigned i = 0; i < per.size(); i++) {
584  	          if (proportional_weights[i] > 0 )
585  	            tester_data.proportional_weights[i] = proportional_weights[i];
586  	
587  	          tester_data.proportional_weights_all[i] = proportional_weights[i];
588  	        }
589  	
590  	      }
591  	      // compute the expected number of objects stored per device when a device's weight is considered
592  	      vector<float> num_objects_expected(num_devices);
593  	
594  	      for (unsigned i = 0; i < num_devices; i++)
595  	        num_objects_expected[i] = (proportional_weights[i]*expected_objects);
596  	
597  	      for (int current_batch = 0; current_batch < num_batches; current_batch++) {
598  	        if (current_batch == (num_batches - 1)) {
599  	          batch_max = max_x;
600  	          objects_per_batch = (batch_max - batch_min + 1);
601  	        }
602  	
603  	        float batch_expected_objects = std::min(nr, get_maximum_affected_by_rule(r)) * objects_per_batch;
604  	        vector<float> batch_num_objects_expected( per.size() );
605  	
606  	        for (unsigned i = 0; i < per.size() ; i++)
607  	          batch_num_objects_expected[i] = (proportional_weights[i]*batch_expected_objects);
608  	
609  	        // create a vector to hold placement results temporarily 
610  	        vector<int> temporary_per ( per.size() );
611  	
612  	        for (int x = batch_min; x <= batch_max; x++) {
613  	          // create a vector to hold the results of a CRUSH placement or RNG simulation
614  	          vector<int> out;
615  	
616  	          if (use_crush) {
617  	            if (output_mappings)
618  		      err << "CRUSH"; // prepend CRUSH to placement output
619  	            uint32_t real_x = x;
620  	            if (pool_id != -1) {
621  	              real_x = crush_hash32_2(CRUSH_HASH_RJENKINS1, x, (uint32_t)pool_id);
622  	            }
623  	            crush.do_rule(r, real_x, out, nr, weight, 0);
624  	          } else {
625  	            if (output_mappings)
626  		      err << "RNG"; // prepend RNG to placement output to denote simulation
627  	            // test our new monte carlo placement generator
628  	            random_placement(r, out, nr, weight);
629  	          }
630  	
631  		  if (output_mappings)
632  		    err << " rule " << r << " x " << x << " " << out << std::endl;
633  	
634  	          if (output_data_file)
635  	            write_integer_indexed_vector_data_string(tester_data.placement_information, x, out);
636  	
637  	          bool has_item_none = false;
638  	          for (unsigned i = 0; i < out.size(); i++) {
639  	            if (out[i] != CRUSH_ITEM_NONE) {
640  	              per[out[i]]++;
641  	              temporary_per[out[i]]++;
642  	            } else {
643  	              has_item_none = true;
644  	            }
645  	          }
646  	
647  	          batch_per[current_batch] = temporary_per;
648  	          sizes[out.size()]++;
649  	          if (output_bad_mappings && 
650  	              (out.size() != (unsigned)nr ||
651  	               has_item_none)) {
652  	            err << "bad mapping rule " << r << " x " << x << " num_rep " << nr << " result " << out << std::endl;
653  	          }
654  	        }
655  	
656  	        batch_min = batch_max + 1;
657  	        batch_max = batch_min + objects_per_batch - 1;
658  	      }
659  	
660  	      for (unsigned i = 0; i < per.size(); i++)
661  	        if (output_utilization && !output_statistics)
662  	          err << "  device " << i
663  	          << ":\t" << per[i] << std::endl;
664  	
665  	      for (map<int,int>::iterator p = sizes.begin(); p != sizes.end(); ++p)
666  	        if (output_statistics)
667  	          err << "rule " << r << " (" << crush.get_rule_name(r) << ") num_rep " << nr
668  	          << " result size == " << p->first << ":\t"
669  	          << p->second << "/" << (max_x-min_x+1) << std::endl;
670  	
671  	      if (output_statistics)
672  	        for (unsigned i = 0; i < per.size(); i++) {
673  	          if (output_utilization) {
674  	            if (num_objects_expected[i] > 0 && per[i] > 0) {
675  	              err << "  device " << i << ":\t"
676  	                  << "\t" << " stored " << ": " << per[i]
677  	                  << "\t" << " expected " << ": " << num_objects_expected[i]
678  	                  << std::endl;
679  	            }
680  	          } else if (output_utilization_all) {
681  	            err << "  device " << i << ":\t"
682  	                << "\t" << " stored " << ": " << per[i]
683  	                << "\t" << " expected " << ": " << num_objects_expected[i]
684  	                << std::endl;
685  	          }
686  	        }
687  	
688  	      if (output_data_file)
689  	        for (unsigned i = 0; i < per.size(); i++) {
690  	          vector_data_buffer_f.clear();
691  	          vector_data_buffer_f.push_back( (float) per[i]);
692  	          vector_data_buffer_f.push_back( (float) num_objects_expected[i]);
693  	
694  	          write_integer_indexed_vector_data_string(tester_data.device_utilization_all, i, vector_data_buffer_f);
695  	
696  	          if (num_objects_expected[i] > 0 && per[i] > 0)
697  	            write_integer_indexed_vector_data_string(tester_data.device_utilization, i, vector_data_buffer_f);
698  	        }
699  	
700  	      if (output_data_file && num_batches > 1) {
701  	        // stage batch utilization information for post-processing
702  	        for (int i = 0; i < num_batches; i++) {
703  	          write_integer_indexed_vector_data_string(tester_data.batch_device_utilization_all, i, batch_per[i]);
704  	          write_integer_indexed_vector_data_string(tester_data.batch_device_expected_utilization_all, i, batch_per[i]);
705  	        }
706  	      }
707  	
708  	      string rule_tag = crush.get_rule_name(r);
709  	
710  	      if (output_csv)
711  	        write_data_set_to_csv(output_data_file_name+rule_tag,tester_data);
712  	    }
713  	  }
714  	
715  	  if (output_choose_tries) {
716  	    __u32 *v = 0;
717  	    int n = crush.get_choose_profile(&v);
718  	    for (int i=0; i<n; i++) {
719  	      cout.setf(std::ios::right);
720  	      cout << std::setw(2)
721  	      << i << ": " << std::setw(9) << v[i];
722  	      cout.unsetf(std::ios::right);
723  	      cout << std::endl;
724  	    }
725  	
726  	    crush.stop_choose_profile();
727  	  }
728  	
729  	  return 0;
730  	}
731  	
732  	int CrushTester::compare(CrushWrapper& crush2)
733  	{
734  	  if (min_rule < 0 || max_rule < 0) {
735  	    min_rule = 0;
736  	    max_rule = crush.get_max_rules() - 1;
737  	  }
738  	  if (min_x < 0 || max_x < 0) {
739  	    min_x = 0;
740  	    max_x = 1023;
741  	  }
742  	
743  	  // initial osd weights
744  	  vector<__u32> weight;
745  	
746  	  /*
747  	   * note device weight is set by crushtool
748  	   * (likely due to a given a command line option)
749  	   */
750  	  for (int o = 0; o < crush.get_max_devices(); o++) {
751  	    if (device_weight.count(o)) {
752  	      weight.push_back(device_weight[o]);
753  	    } else if (crush.check_item_present(o)) {
754  	      weight.push_back(0x10000);
755  	    } else {
756  	      weight.push_back(0);
757  	    }
758  	  }
759  	
760  	  // make adjustments
761  	  adjust_weights(weight);
762  	
763  	  map<int,int> bad_by_rule;
764  	
765  	  int ret = 0;
766  	  for (int r = min_rule; r < crush.get_max_rules() && r <= max_rule; r++) {
767  	    if (!crush.rule_exists(r)) {
768  	      if (output_statistics)
769  	        err << "rule " << r << " dne" << std::endl;
770  	      continue;
771  	    }
772  	    if (ruleset >= 0 &&
773  		crush.get_rule_mask_ruleset(r) != ruleset) {
774  	      continue;
775  	    }
776  	    int minr = min_rep, maxr = max_rep;
777  	    if (min_rep < 0 || max_rep < 0) {
778  	      minr = crush.get_rule_mask_min_size(r);
779  	      maxr = crush.get_rule_mask_max_size(r);
780  	    }
781  	    int bad = 0;
782  	    for (int nr = minr; nr <= maxr; nr++) {
783  	      for (int x = min_x; x <= max_x; ++x) {
784  		vector<int> out;
785  		crush.do_rule(r, x, out, nr, weight, 0);
786  		vector<int> out2;
787  		crush2.do_rule(r, x, out2, nr, weight, 0);
788  		if (out != out2) {
789  		  ++bad;
790  		}
791  	      }
792  	    }
793  	    if (bad) {
794  	      ret = -1;
795  	    }
796  	    int max = (maxr - minr + 1) * (max_x - min_x + 1);
797  	    double ratio = (double)bad / (double)max;
798  	    cout << "rule " << r << " had " << bad << "/" << max
799  		 << " mismatched mappings (" << ratio << ")" << std::endl;
800  	  }
801  	  if (ret) {
802  	    cerr << "warning: maps are NOT equivalent" << std::endl;
803  	  } else {
804  	    cout << "maps appear equivalent" << std::endl;
805  	  }
806  	  return ret;
807  	}
808