1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 #include "common/Mutex.h"
4 #include "common/Cond.h"
5 #include "common/errno.h"
6 #include "common/version.h"
17 #include "test/osd/RadosModel.h"
22 class WeightedTestGenerator
: public TestOpGenerator
26 WeightedTestGenerator(int ops
,
28 map
<TestOpType
, unsigned int> op_weights
,
35 m_nextop(NULL
), m_op(0), m_ops(ops
), m_seconds(max_seconds
),
36 m_objects(objects
), m_stats(stats
),
39 m_balance_reads(balance_reads
),
40 m_set_redirect(set_redirect
),
41 m_set_chunk(set_chunk
)
44 for (map
<TestOpType
, unsigned int>::const_iterator it
= op_weights
.begin();
45 it
!= op_weights
.end();
47 m_total_weight
+= it
->second
;
48 m_weight_sums
.insert(pair
<TestOpType
, unsigned int>(it
->first
,
51 if (m_set_redirect
|| m_set_chunk
) {
53 m_ops
= ops
+m_objects
+m_objects
;
55 /* create 10 chunks per an object*/
56 m_ops
= ops
+m_objects
+m_objects
*10;
61 TestOp
*next(RadosTestContext
&context
) override
63 TestOp
*retval
= NULL
;
66 if (m_op
<= m_objects
&& !m_set_redirect
&& !m_set_chunk
) {
70 // make it a long name
71 oid
<< " " << string(300, 'o');
73 cout
<< m_op
<< ": write initial oid " << oid
.str() << std::endl
;
74 context
.oid_not_flushing
.insert(oid
.str());
76 return new WriteOp(m_op
, &context
, oid
.str(), true, true);
78 return new WriteOp(m_op
, &context
, oid
.str(), false, true);
80 } else if (m_op
>= m_ops
) {
84 if (m_set_redirect
|| m_set_chunk
) {
85 if (init_extensible_tier(context
, retval
)) {
96 while (retval
== NULL
) {
97 unsigned int rand_val
= rand() % m_total_weight
;
100 if (m_seconds
&& now
- m_start
> m_seconds
)
103 for (map
<TestOpType
, unsigned int>::const_iterator it
= m_weight_sums
.begin();
104 it
!= m_weight_sums
.end();
106 if (rand_val
< it
->second
) {
107 retval
= gen_op(context
, it
->first
);
115 bool init_extensible_tier(RadosTestContext
&context
, TestOp
*& op
) {
117 * set-redirect or set-chunk test (manifest test)
118 * 0. make default objects (using create op)
119 * 1. set-redirect or set-chunk
120 * 2. initialize target objects (using write op)
121 * 3. wait for set-* completion
123 int copy_manifest_end
= 0;
125 copy_manifest_end
= m_objects
*2;
127 copy_manifest_end
= m_objects
*3;
129 int make_manifest_end
= copy_manifest_end
;
131 /* make 10 chunks per an object*/
132 make_manifest_end
= make_manifest_end
+ m_objects
* 10;
135 make_manifest_end
= make_manifest_end
+ m_objects
;
138 if (m_op
<= m_objects
) {
142 oid
<< " " << string(300, 'o');
144 cout
<< m_op
<< ": write initial oid " << oid
.str() << std::endl
;
145 context
.oid_not_flushing
.insert(oid
.str());
147 op
= new WriteOp(m_op
, &context
, oid
.str(), true, true);
149 op
= new WriteOp(m_op
, &context
, oid
.str(), false, true);
152 } else if (m_op
<= copy_manifest_end
) {
153 stringstream oid
, oid2
;
154 //int _oid = m_op-m_objects;
155 int _oid
= m_op
% m_objects
+ 1;
158 oid
<< " " << string(300, 'o');
160 int _oid2
= m_op
- m_objects
+ 1;
161 if (_oid2
> copy_manifest_end
- m_objects
) {
162 _oid2
-= (copy_manifest_end
- m_objects
);
164 oid2
<< _oid2
<< " " << context
.low_tier_pool_name
;
166 oid2
<< " " << string(300, 'm');
168 cout
<< m_op
<< ": " << "copy oid " << oid
.str() << " target oid "
169 << oid2
.str() << std::endl
;
170 op
= new CopyOp(m_op
, &context
, oid
.str(), oid2
.str(), context
.low_tier_pool_name
);
172 } else if (m_op
<= make_manifest_end
) {
173 if (m_set_redirect
) {
174 stringstream oid
, oid2
;
175 int _oid
= m_op
-copy_manifest_end
;
178 oid
<< " " << string(300, 'o');
180 oid2
<< _oid
<< " " << context
.low_tier_pool_name
;
182 oid2
<< " " << string(300, 'm');
184 if (context
.oid_in_use
.count(oid
.str())) {
185 /* previous copy is not finished */
188 cout
<< m_op
<< " retry set_redirect !" << std::endl
;
191 cout
<< m_op
<< ": " << "set_redirect oid " << oid
.str() << " target oid "
192 << oid2
.str() << std::endl
;
193 op
= new SetRedirectOp(m_op
, &context
, oid
.str(), oid2
.str(), context
.pool_name
);
195 } else if (m_set_chunk
) {
197 int _oid
= m_op
% m_objects
+1;
200 oid
<< " " << string(300, 'o');
202 if (context
.oid_in_use
.count(oid
.str())) {
203 /* previous set-chunk is not finished */
206 cout
<< m_op
<< " retry set_chunk !" << std::endl
;
210 oid2
<< _oid
<< " " << context
.low_tier_pool_name
;
212 oid2
<< " " << string(300, 'm');
215 /* make a chunk (random offset, random length -->
216 * target object's random offset)
218 ObjectDesc contents
, contents2
;
219 context
.find_object(oid
.str(), &contents
);
220 uint32_t max_len
= contents
.most_recent_gen()->get_length(contents
.most_recent());
221 uint32_t rand_offset
= rand() % max_len
;
222 uint32_t rand_length
= rand() % max_len
;
223 rand_offset
= rand_offset
- (rand_offset
% 512);
224 rand_length
= rand_length
- (rand_length
% 512);
226 while (rand_offset
+ rand_length
> max_len
|| rand_length
== 0) {
227 rand_offset
= rand() % max_len
;
228 rand_length
= rand() % max_len
;
229 rand_offset
= rand_offset
- (rand_offset
% 512);
230 rand_length
= rand_length
- (rand_length
% 512);
232 uint32_t rand_tgt_offset
= rand_offset
;
233 cout
<< m_op
<< ": " << "set_chunk oid " << oid
.str() << " offset: " << rand_offset
234 << " length: " << rand_length
<< " target oid " << oid2
.str()
235 << " tgt_offset: " << rand_tgt_offset
<< std::endl
;
236 op
= new SetChunkOp(m_op
, &context
, oid
.str(), rand_offset
, rand_length
, oid2
.str(),
237 context
.low_tier_pool_name
, rand_tgt_offset
, m_stats
);
240 } else if (m_op
== make_manifest_end
+ 1) {
241 int set_size
= context
.oid_not_in_use
.size();
242 int set_manifest_size
= context
.oid_redirect_not_in_use
.size();
243 cout
<< m_op
<< " oid_not_in_use " << set_size
<< " oid_redirect_not_in_use " << set_manifest_size
<< std::endl
;
244 /* wait for redirect or set_chunk initialization */
245 if (set_size
!= m_objects
|| set_manifest_size
!= 0) {
248 cout
<< m_op
<< " wait for manifest initialization " << std::endl
;
251 for (int t_op
= m_objects
+1; t_op
<= m_objects
*2; t_op
++) {
253 oid
<< t_op
<< " " << context
.low_tier_pool_name
;
255 oid
<< " " << string(300, 'm');
257 cout
<< " redirect_not_in_use: " << oid
.str() << std::endl
;
258 context
.oid_redirect_not_in_use
.insert(oid
.str());
267 TestOp
*gen_op(RadosTestContext
&context
, TestOpType type
)
270 ceph_assert(context
.oid_not_in_use
.size());
274 oid
= *(rand_choose(context
.oid_not_in_use
));
275 return new ReadOp(m_op
, &context
, oid
, m_balance_reads
, m_stats
);
278 oid
= *(rand_choose(context
.oid_not_in_use
));
279 cout
<< m_op
<< ": " << "write oid " << oid
<< " current snap is "
280 << context
.current_snap
<< std::endl
;
281 return new WriteOp(m_op
, &context
, oid
, false, false, m_stats
);
283 case TEST_OP_WRITE_EXCL
:
284 oid
= *(rand_choose(context
.oid_not_in_use
));
285 cout
<< m_op
<< ": " << "write (excl) oid "
286 << oid
<< " current snap is "
287 << context
.current_snap
<< std::endl
;
288 return new WriteOp(m_op
, &context
, oid
, false, true, m_stats
);
290 case TEST_OP_WRITESAME
:
291 oid
= *(rand_choose(context
.oid_not_in_use
));
292 cout
<< m_op
<< ": " << "writesame oid "
293 << oid
<< " current snap is "
294 << context
.current_snap
<< std::endl
;
295 return new WriteSameOp(m_op
, &context
, oid
, m_stats
);
298 oid
= *(rand_choose(context
.oid_not_in_use
));
299 cout
<< m_op
<< ": " << "delete oid " << oid
<< " current snap is "
300 << context
.current_snap
<< std::endl
;
301 return new DeleteOp(m_op
, &context
, oid
, m_stats
);
303 case TEST_OP_SNAP_CREATE
:
304 cout
<< m_op
<< ": " << "snap_create" << std::endl
;
305 return new SnapCreateOp(m_op
, &context
, m_stats
);
307 case TEST_OP_SNAP_REMOVE
:
308 if (context
.snaps
.size() <= context
.snaps_in_use
.size()) {
312 int snap
= rand_choose(context
.snaps
)->first
;
313 if (context
.snaps_in_use
.lookup(snap
))
314 continue; // in use; try again!
315 cout
<< m_op
<< ": " << "snap_remove snap " << snap
<< std::endl
;
316 return new SnapRemoveOp(m_op
, &context
, snap
, m_stats
);
319 case TEST_OP_ROLLBACK
:
321 string oid
= *(rand_choose(context
.oid_not_in_use
));
322 cout
<< m_op
<< ": " << "rollback oid " << oid
<< " current snap is "
323 << context
.current_snap
<< std::endl
;
324 return new RollbackOp(m_op
, &context
, oid
);
327 case TEST_OP_SETATTR
:
328 oid
= *(rand_choose(context
.oid_not_in_use
));
329 cout
<< m_op
<< ": " << "setattr oid " << oid
330 << " current snap is " << context
.current_snap
<< std::endl
;
331 return new SetAttrsOp(m_op
, &context
, oid
, m_stats
);
334 oid
= *(rand_choose(context
.oid_not_in_use
));
335 cout
<< m_op
<< ": " << "rmattr oid " << oid
336 << " current snap is " << context
.current_snap
<< std::endl
;
337 return new RemoveAttrsOp(m_op
, &context
, oid
, m_stats
);
340 oid
= *(rand_choose(context
.oid_not_in_use
));
341 cout
<< m_op
<< ": " << "watch oid " << oid
342 << " current snap is " << context
.current_snap
<< std::endl
;
343 return new WatchOp(m_op
, &context
, oid
, m_stats
);
345 case TEST_OP_COPY_FROM
:
346 oid
= *(rand_choose(context
.oid_not_in_use
));
348 oid2
= *(rand_choose(context
.oid_not_in_use
));
349 } while (oid
== oid2
);
350 cout
<< m_op
<< ": " << "copy_from oid " << oid
<< " from oid " << oid2
351 << " current snap is " << context
.current_snap
<< std::endl
;
352 return new CopyFromOp(m_op
, &context
, oid
, oid2
, m_stats
);
354 case TEST_OP_HIT_SET_LIST
:
356 uint32_t hash
= rjhash32(rand());
357 cout
<< m_op
<< ": " << "hit_set_list " << hash
<< std::endl
;
358 return new HitSetListOp(m_op
, &context
, hash
, m_stats
);
361 case TEST_OP_UNDIRTY
:
363 oid
= *(rand_choose(context
.oid_not_in_use
));
364 cout
<< m_op
<< ": " << "undirty oid " << oid
<< std::endl
;
365 return new UndirtyOp(m_op
, &context
, oid
, m_stats
);
368 case TEST_OP_IS_DIRTY
:
370 oid
= *(rand_choose(context
.oid_not_flushing
));
371 return new IsDirtyOp(m_op
, &context
, oid
, m_stats
);
374 case TEST_OP_CACHE_FLUSH
:
376 oid
= *(rand_choose(context
.oid_not_in_use
));
377 return new CacheFlushOp(m_op
, &context
, oid
, m_stats
, true);
380 case TEST_OP_CACHE_TRY_FLUSH
:
382 oid
= *(rand_choose(context
.oid_not_in_use
));
383 return new CacheFlushOp(m_op
, &context
, oid
, m_stats
, false);
386 case TEST_OP_CACHE_EVICT
:
388 oid
= *(rand_choose(context
.oid_not_in_use
));
389 return new CacheEvictOp(m_op
, &context
, oid
, m_stats
);
393 oid
= *(rand_choose(context
.oid_not_in_use
));
394 cout
<< "append oid " << oid
<< " current snap is "
395 << context
.current_snap
<< std::endl
;
396 return new WriteOp(m_op
, &context
, oid
, true, false, m_stats
);
398 case TEST_OP_APPEND_EXCL
:
399 oid
= *(rand_choose(context
.oid_not_in_use
));
400 cout
<< "append oid (excl) " << oid
<< " current snap is "
401 << context
.current_snap
<< std::endl
;
402 return new WriteOp(m_op
, &context
, oid
, true, true, m_stats
);
404 case TEST_OP_CHUNK_READ
:
405 oid
= *(rand_choose(context
.oid_not_in_use
));
406 cout
<< m_op
<< ": " << "chunk read oid " << oid
<< " target oid " << oid2
<< std::endl
;
407 return new ChunkReadOp(m_op
, &context
, oid
, context
.pool_name
, false, m_stats
);
409 case TEST_OP_TIER_PROMOTE
:
410 oid
= *(rand_choose(context
.oid_not_in_use
));
411 cout
<< m_op
<< ": " << "tier_promote oid " << oid
<< std::endl
;
412 return new TierPromoteOp(m_op
, &context
, oid
, m_stats
);
414 case TEST_OP_SET_REDIRECT
:
415 oid
= *(rand_choose(context
.oid_not_in_use
));
416 oid2
= *(rand_choose(context
.oid_redirect_not_in_use
));
417 cout
<< m_op
<< ": " << "set_redirect oid " << oid
<< " target oid " << oid2
<< std::endl
;
418 return new SetRedirectOp(m_op
, &context
, oid
, oid2
, context
.pool_name
, m_stats
);
420 case TEST_OP_UNSET_REDIRECT
:
421 oid
= *(rand_choose(context
.oid_not_in_use
));
422 cout
<< m_op
<< ": " << "unset_redirect oid " << oid
<< std::endl
;
423 return new UnsetRedirectOp(m_op
, &context
, oid
, m_stats
);
426 cerr
<< m_op
<< ": Invalid op type " << type
<< std::endl
;
439 map
<TestOpType
, unsigned int> m_weight_sums
;
440 unsigned int m_total_weight
;
442 bool m_balance_reads
;
447 int main(int argc
, char **argv
)
451 int max_in_flight
= 16;
452 int64_t size
= 4000000; // 4 MB
453 int64_t min_stride_size
= -1, max_stride_size
= -1;
455 bool pool_snaps
= false;
456 bool write_fadvise_dontneed
= false;
463 { TEST_OP_READ
, "read", true },
464 { TEST_OP_WRITE
, "write", false },
465 { TEST_OP_WRITE_EXCL
, "write_excl", false },
466 { TEST_OP_WRITESAME
, "writesame", false },
467 { TEST_OP_DELETE
, "delete", true },
468 { TEST_OP_SNAP_CREATE
, "snap_create", true },
469 { TEST_OP_SNAP_REMOVE
, "snap_remove", true },
470 { TEST_OP_ROLLBACK
, "rollback", true },
471 { TEST_OP_SETATTR
, "setattr", true },
472 { TEST_OP_RMATTR
, "rmattr", true },
473 { TEST_OP_WATCH
, "watch", true },
474 { TEST_OP_COPY_FROM
, "copy_from", true },
475 { TEST_OP_HIT_SET_LIST
, "hit_set_list", true },
476 { TEST_OP_IS_DIRTY
, "is_dirty", true },
477 { TEST_OP_UNDIRTY
, "undirty", true },
478 { TEST_OP_CACHE_FLUSH
, "cache_flush", true },
479 { TEST_OP_CACHE_TRY_FLUSH
, "cache_try_flush", true },
480 { TEST_OP_CACHE_EVICT
, "cache_evict", true },
481 { TEST_OP_APPEND
, "append", true },
482 { TEST_OP_APPEND_EXCL
, "append_excl", true },
483 { TEST_OP_SET_REDIRECT
, "set_redirect", true },
484 { TEST_OP_UNSET_REDIRECT
, "unset_redirect", true },
485 { TEST_OP_CHUNK_READ
, "chunk_read", true },
486 { TEST_OP_TIER_PROMOTE
, "tier_promote", true },
487 { TEST_OP_READ
/* grr */, NULL
},
490 map
<TestOpType
, unsigned int> op_weights
;
491 string pool_name
= "rbd";
492 string low_tier_pool_name
= "";
493 bool ec_pool
= false;
494 bool no_omap
= false;
495 bool no_sparse
= false;
496 bool balance_reads
= false;
497 bool set_redirect
= false;
498 bool set_chunk
= false;
500 for (int i
= 1; i
< argc
; ++i
) {
501 if (strcmp(argv
[i
], "--max-ops") == 0)
502 ops
= atoi(argv
[++i
]);
503 else if (strcmp(argv
[i
], "--pool") == 0)
504 pool_name
= argv
[++i
];
505 else if (strcmp(argv
[i
], "--max-seconds") == 0)
506 max_seconds
= atoi(argv
[++i
]);
507 else if (strcmp(argv
[i
], "--objects") == 0)
508 objects
= atoi(argv
[++i
]);
509 else if (strcmp(argv
[i
], "--max-in-flight") == 0)
510 max_in_flight
= atoi(argv
[++i
]);
511 else if (strcmp(argv
[i
], "--size") == 0)
512 size
= atoi(argv
[++i
]);
513 else if (strcmp(argv
[i
], "--min-stride-size") == 0)
514 min_stride_size
= atoi(argv
[++i
]);
515 else if (strcmp(argv
[i
], "--max-stride-size") == 0)
516 max_stride_size
= atoi(argv
[++i
]);
517 else if (strcmp(argv
[i
], "--no-omap") == 0)
519 else if (strcmp(argv
[i
], "--no-sparse") == 0)
521 else if (strcmp(argv
[i
], "--balance_reads") == 0)
522 balance_reads
= true;
523 else if (strcmp(argv
[i
], "--pool-snaps") == 0)
525 else if (strcmp(argv
[i
], "--write-fadvise-dontneed") == 0)
526 write_fadvise_dontneed
= true;
527 else if (strcmp(argv
[i
], "--ec-pool") == 0) {
528 if (!op_weights
.empty()) {
529 cerr
<< "--ec-pool must be specified prior to any ops" << std::endl
;
535 } else if (strcmp(argv
[i
], "--op") == 0) {
538 cerr
<< "Missing op after --op" << std::endl
;
542 for (j
= 0; op_types
[j
].name
; ++j
) {
543 if (strcmp(op_types
[j
].name
, argv
[i
]) == 0) {
547 if (!op_types
[j
].name
) {
548 cerr
<< "unknown op " << argv
[i
] << std::endl
;
553 cerr
<< "Weight unspecified." << std::endl
;
556 int weight
= atoi(argv
[i
]);
558 cerr
<< "Weights must be nonnegative." << std::endl
;
560 } else if (weight
> 0) {
561 if (ec_pool
&& !op_types
[j
].ec_pool_valid
) {
562 cerr
<< "Error: cannot use op type " << op_types
[j
].name
563 << " with --ec-pool" << std::endl
;
566 cout
<< "adding op weight " << op_types
[j
].name
<< " -> " << weight
<< std::endl
;
567 op_weights
.insert(pair
<TestOpType
, unsigned int>(op_types
[j
].op
, weight
));
569 } else if (strcmp(argv
[i
], "--set_redirect") == 0) {
571 } else if (strcmp(argv
[i
], "--set_chunk") == 0) {
573 } else if (strcmp(argv
[i
], "--low_tier_pool") == 0) {
575 * disallow redirect or chunk object into the same pool
576 * to prevent the race. see https://github.com/ceph/ceph/pull/20096
578 low_tier_pool_name
= argv
[++i
];
580 cerr
<< "unknown arg " << argv
[i
] << std::endl
;
585 if (set_redirect
|| set_chunk
) {
586 if (low_tier_pool_name
== "") {
587 cerr
<< "low_tier_pool_name is needed" << std::endl
;
592 if (op_weights
.empty()) {
593 cerr
<< "No operations specified" << std::endl
;
597 if (min_stride_size
< 0)
598 min_stride_size
= size
/ 10;
599 if (max_stride_size
< 0)
600 max_stride_size
= size
/ 5;
602 cout
<< pretty_version_to_str() << std::endl
;
603 cout
<< "Configuration:" << std::endl
604 << "\tNumber of operations: " << ops
<< std::endl
605 << "\tNumber of objects: " << objects
<< std::endl
606 << "\tMax in flight operations: " << max_in_flight
<< std::endl
607 << "\tObject size (in bytes): " << size
<< std::endl
608 << "\tWrite stride min: " << min_stride_size
<< std::endl
609 << "\tWrite stride max: " << max_stride_size
<< std::endl
;
611 if (min_stride_size
>= max_stride_size
) {
612 cerr
<< "Error: max_stride_size must be more than min_stride_size"
617 if (min_stride_size
> size
|| max_stride_size
> size
) {
618 cerr
<< "Error: min_stride_size and max_stride_size must be "
619 << "smaller than object size" << std::endl
;
623 if (max_in_flight
* 2 > objects
) {
624 cerr
<< "Error: max_in_flight must be <= than the number of objects / 2"
629 char *id
= getenv("CEPH_CLIENT_ID");
630 RadosTestContext
context(
639 write_fadvise_dontneed
,
644 WeightedTestGenerator gen
= WeightedTestGenerator(
646 op_weights
, &stats
, max_seconds
,
647 ec_pool
, balance_reads
, set_redirect
, set_chunk
);
648 int r
= context
.init();
650 cerr
<< "Error initializing rados test context: "
651 << cpp_strerror(r
) << std::endl
;
657 cerr
<< context
.errors
<< " errors." << std::endl
;
658 cerr
<< stats
<< std::endl
;