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
115 if (mark_down_device_ratio > 0) {
116 // active buckets
117 vector<int> bucket_ids;
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 }
123 }
124
125 // get buckets that are one level above a device
126 vector<int> buckets_above_devices;
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];
130 if (crush.get_bucket_size(id) == 0)
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 }
136 }
137
138 // permute bucket list
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]);
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
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;
151 for (int o = 0; o < size; o++)
152 items.push_back(crush.get_bucket_item(id, o));
153
154 // permute items
155 for (int o = 0; o < size; o++) {
(1) Event dont_call: |
"lrand48()" should not be used for security-related applications, because linear congruential algorithms are too easy to break. |
(2) Event remediation: |
Use a compliant random number generator, such as "/dev/random" or "/dev/urandom" on Unix-like systems, and CNG (Cryptography API: Next Generation) on Windows. |
156 int j = lrand48() % (crush.get_bucket_size(id) - 1);
157 std::swap(items[o], items[j]);
158 }
159
160 int local_devices_to_visit = (int) (mark_down_device_ratio*size);
161 for (int o = 0; o < local_devices_to_visit; o++){
162 int item = crush.get_bucket_item(id, o);
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