1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 #include "common/Cond.h"
4 #include "common/errno.h"
5 #include "common/version.h"
16 #include "test/osd/RadosModel.h"
20 class WeightedTestGenerator
: public TestOpGenerator
24 WeightedTestGenerator(int ops
,
26 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_localize_reads(localize_reads
),
41 m_set_redirect(set_redirect
),
42 m_set_chunk(set_chunk
),
43 m_enable_dedup(enable_dedup
)
46 for (map
<TestOpType
, unsigned int>::const_iterator it
= op_weights
.begin();
47 it
!= op_weights
.end();
49 m_total_weight
+= it
->second
;
50 m_weight_sums
.insert(pair
<TestOpType
, unsigned int>(it
->first
,
53 if (m_set_redirect
|| m_set_chunk
) {
55 m_ops
= ops
+m_objects
+m_objects
;
57 /* create 10 chunks per an object*/
58 m_ops
= ops
+m_objects
+m_objects
*10;
63 TestOp
*next(RadosTestContext
&context
) override
65 TestOp
*retval
= NULL
;
68 if (m_op
<= m_objects
&& !m_set_redirect
&& !m_set_chunk
) {
72 // make it a long name
73 oid << " " << string(300, 'o');
75 cout
<< m_op
<< ": write initial oid " << oid
.str() << std::endl
;
76 context
.oid_not_flushing
.insert(oid
.str());
78 return new WriteOp(m_op
, &context
, oid
.str(), true, true);
80 return new WriteOp(m_op
, &context
, oid
.str(), false, true);
82 } else if (m_op
>= m_ops
) {
86 if (m_set_redirect
|| m_set_chunk
) {
87 if (init_extensible_tier(context
, retval
)) {
98 while (retval
== NULL
) {
99 unsigned int rand_val
= rand() % m_total_weight
;
101 time_t now
= time(0);
102 if (m_seconds
&& now
- m_start
> m_seconds
)
105 for (map
<TestOpType
, unsigned int>::const_iterator it
= m_weight_sums
.begin();
106 it
!= m_weight_sums
.end();
108 if (rand_val
< it
->second
) {
109 retval
= gen_op(context
, it
->first
);
117 bool init_extensible_tier(RadosTestContext
&context
, TestOp
*& op
) {
119 * set-redirect or set-chunk test (manifest test)
120 * 0. make default objects (using create op)
121 * 1. set-redirect or set-chunk
122 * 2. initialize target objects (using write op)
123 * 3. wait for set-* completion
125 int copy_manifest_end
= 0;
127 copy_manifest_end
= m_objects
*2;
129 copy_manifest_end
= m_objects
*3;
131 int make_manifest_end
= copy_manifest_end
;
133 /* make 10 chunks per an object*/
134 make_manifest_end
= make_manifest_end
+ m_objects
* 10;
137 make_manifest_end
= make_manifest_end
+ m_objects
;
140 if (m_op
<= m_objects
) {
144 oid << " " << string(300, 'o');
146 cout
<< m_op
<< ": write initial oid " << oid
.str() << std::endl
;
147 context
.oid_not_flushing
.insert(oid
.str());
149 op
= new WriteOp(m_op
, &context
, oid
.str(), true, true);
151 op
= new WriteOp(m_op
, &context
, oid
.str(), false, true);
154 } else if (m_op
<= copy_manifest_end
) {
155 stringstream oid
, oid2
;
156 //int _oid = m_op-m_objects;
157 int _oid
= m_op
% m_objects
+ 1;
160 oid << " " << string(300, 'o');
163 if (context
.oid_in_use
.count(oid
.str())) {
164 /* previous write is not finished */
167 cout
<< m_op
<< " wait for completion of write op! " << std::endl
;
171 int _oid2
= m_op
- m_objects
+ 1;
172 if (_oid2
> copy_manifest_end
- m_objects
) {
173 _oid2
-= (copy_manifest_end
- m_objects
);
175 oid2
<< _oid2
<< " " << context
.low_tier_pool_name
;
177 oid2
<< " " << string(300, 'm');
179 cout
<< m_op
<< ": " << "copy oid " << oid
.str() << " target oid "
180 << oid2
.str() << std::endl
;
181 op
= new CopyOp(m_op
, &context
, oid
.str(), oid2
.str(), context
.low_tier_pool_name
);
183 } else if (m_op
<= make_manifest_end
) {
184 if (m_set_redirect
) {
185 stringstream oid
, oid2
;
186 int _oid
= m_op
-copy_manifest_end
;
189 oid << " " << string(300, 'o');
191 oid2
<< _oid
<< " " << context
.low_tier_pool_name
;
193 oid2
<< " " << string(300, 'm');
195 if (context
.oid_in_use
.count(oid
.str())) {
196 /* previous copy is not finished */
199 cout
<< m_op
<< " retry set_redirect !" << std::endl
;
202 cout
<< m_op
<< ": " << "set_redirect oid " << oid
.str() << " target oid "
203 << oid2
.str() << std::endl
;
204 op
= new SetRedirectOp(m_op
, &context
, oid
.str(), oid2
.str(), context
.pool_name
);
206 } else if (m_set_chunk
) {
208 int _oid
= m_op
% m_objects
+1;
211 oid << " " << string(300, 'o');
213 if (context
.oid_in_use
.count(oid
.str())) {
214 /* previous set-chunk is not finished */
217 cout
<< m_op
<< " retry set_chunk !" << std::endl
;
221 oid2
<< _oid
<< " " << context
.low_tier_pool_name
;
223 oid2
<< " " << string(300, 'm');
226 cout
<< m_op
<< ": " << "set_chunk oid " << oid
.str()
227 << " target oid " << oid2
.str() << std::endl
;
228 op
= new SetChunkOp(m_op
, &context
, oid
.str(), oid2
.str(), m_stats
);
231 } else if (m_op
== make_manifest_end
+ 1) {
232 int set_size
= context
.oid_not_in_use
.size();
233 int set_manifest_size
= context
.oid_redirect_not_in_use
.size();
234 cout
<< m_op
<< " oid_not_in_use " << set_size
<< " oid_redirect_not_in_use " << set_manifest_size
<< std::endl
;
235 /* wait for redirect or set_chunk initialization */
236 if (set_size
!= m_objects
|| set_manifest_size
!= 0) {
239 cout
<< m_op
<< " wait for manifest initialization " << std::endl
;
242 for (int t_op
= m_objects
+1; t_op
<= m_objects
*2; t_op
++) {
244 oid
<< t_op
<< " " << context
.low_tier_pool_name
;
246 oid
<< " " << string(300, 'm');
248 cout
<< " redirect_not_in_use: " << oid
.str() << std::endl
;
249 context
.oid_redirect_not_in_use
.insert(oid
.str());
258 TestOp
*gen_op(RadosTestContext
&context
, TestOpType type
)
261 ceph_assert(context
.oid_not_in_use
.size());
265 oid
= *(rand_choose(context
.oid_not_in_use
));
266 return new ReadOp(m_op
, &context
, oid
, m_balance_reads
, m_localize_reads
,
270 oid
= *(rand_choose(context
.oid_not_in_use
));
271 cout
<< m_op
<< ": " << "write oid " << oid
<< " current snap is "
272 << context
.current_snap
<< std::endl
;
273 return new WriteOp(m_op
, &context
, oid
, false, false, m_stats
);
275 case TEST_OP_WRITE_EXCL
:
276 oid
= *(rand_choose(context
.oid_not_in_use
));
277 cout
<< m_op
<< ": " << "write (excl) oid "
278 << oid
<< " current snap is "
279 << context
.current_snap
<< std::endl
;
280 return new WriteOp(m_op
, &context
, oid
, false, true, m_stats
);
282 case TEST_OP_WRITESAME
:
283 oid
= *(rand_choose(context
.oid_not_in_use
));
284 cout
<< m_op
<< ": " << "writesame oid "
285 << oid
<< " current snap is "
286 << context
.current_snap
<< std::endl
;
287 return new WriteSameOp(m_op
, &context
, oid
, m_stats
);
290 oid
= *(rand_choose(context
.oid_not_in_use
));
291 cout
<< m_op
<< ": " << "delete oid " << oid
<< " current snap is "
292 << context
.current_snap
<< std::endl
;
293 return new DeleteOp(m_op
, &context
, oid
, m_stats
);
295 case TEST_OP_SNAP_CREATE
:
296 cout
<< m_op
<< ": " << "snap_create" << std::endl
;
297 return new SnapCreateOp(m_op
, &context
, m_stats
);
299 case TEST_OP_SNAP_REMOVE
:
300 if (context
.snaps
.size() <= context
.snaps_in_use
.size()) {
304 int snap
= rand_choose(context
.snaps
)->first
;
305 if (context
.snaps_in_use
.lookup(snap
))
306 continue; // in use; try again!
307 cout
<< m_op
<< ": " << "snap_remove snap " << snap
<< std::endl
;
308 return new SnapRemoveOp(m_op
, &context
, snap
, m_stats
);
311 case TEST_OP_ROLLBACK
:
313 string oid
= *(rand_choose(context
.oid_not_in_use
));
314 cout
<< m_op
<< ": " << "rollback oid " << oid
<< " current snap is "
315 << context
.current_snap
<< std::endl
;
316 return new RollbackOp(m_op
, &context
, oid
);
319 case TEST_OP_SETATTR
:
320 oid
= *(rand_choose(context
.oid_not_in_use
));
321 cout
<< m_op
<< ": " << "setattr oid " << oid
322 << " current snap is " << context
.current_snap
<< std::endl
;
323 return new SetAttrsOp(m_op
, &context
, oid
, m_stats
);
326 oid
= *(rand_choose(context
.oid_not_in_use
));
327 cout
<< m_op
<< ": " << "rmattr oid " << oid
328 << " current snap is " << context
.current_snap
<< std::endl
;
329 return new RemoveAttrsOp(m_op
, &context
, oid
, m_stats
);
332 oid
= *(rand_choose(context
.oid_not_in_use
));
333 cout
<< m_op
<< ": " << "watch oid " << oid
334 << " current snap is " << context
.current_snap
<< std::endl
;
335 return new WatchOp(m_op
, &context
, oid
, m_stats
);
337 case TEST_OP_COPY_FROM
:
338 oid
= *(rand_choose(context
.oid_not_in_use
));
340 oid2
= *(rand_choose(context
.oid_not_in_use
));
341 } while (oid
== oid2
);
342 cout
<< m_op
<< ": " << "copy_from oid " << oid
<< " from oid " << oid2
343 << " current snap is " << context
.current_snap
<< std::endl
;
344 return new CopyFromOp(m_op
, &context
, oid
, oid2
, m_stats
);
346 case TEST_OP_HIT_SET_LIST
:
348 uint32_t hash
= rjhash32(rand());
349 cout
<< m_op
<< ": " << "hit_set_list " << hash
<< std::endl
;
350 return new HitSetListOp(m_op
, &context
, hash
, m_stats
);
353 case TEST_OP_UNDIRTY
:
355 oid
= *(rand_choose(context
.oid_not_in_use
));
356 cout
<< m_op
<< ": " << "undirty oid " << oid
<< std::endl
;
357 return new UndirtyOp(m_op
, &context
, oid
, m_stats
);
360 case TEST_OP_IS_DIRTY
:
362 oid
= *(rand_choose(context
.oid_not_flushing
));
363 return new IsDirtyOp(m_op
, &context
, oid
, m_stats
);
366 case TEST_OP_CACHE_FLUSH
:
368 oid
= *(rand_choose(context
.oid_not_in_use
));
369 return new CacheFlushOp(m_op
, &context
, oid
, m_stats
, true);
372 case TEST_OP_CACHE_TRY_FLUSH
:
374 oid
= *(rand_choose(context
.oid_not_in_use
));
375 return new CacheFlushOp(m_op
, &context
, oid
, m_stats
, false);
378 case TEST_OP_CACHE_EVICT
:
380 oid
= *(rand_choose(context
.oid_not_in_use
));
381 return new CacheEvictOp(m_op
, &context
, oid
, m_stats
);
385 oid
= *(rand_choose(context
.oid_not_in_use
));
386 cout
<< "append oid " << oid
<< " current snap is "
387 << context
.current_snap
<< std::endl
;
388 return new WriteOp(m_op
, &context
, oid
, true, false, m_stats
);
390 case TEST_OP_APPEND_EXCL
:
391 oid
= *(rand_choose(context
.oid_not_in_use
));
392 cout
<< "append oid (excl) " << oid
<< " current snap is "
393 << context
.current_snap
<< std::endl
;
394 return new WriteOp(m_op
, &context
, oid
, true, true, m_stats
);
396 case TEST_OP_CHUNK_READ
:
397 oid
= *(rand_choose(context
.oid_not_in_use
));
398 cout
<< m_op
<< ": " << "chunk read oid " << oid
<< " target oid " << oid2
<< std::endl
;
399 return new ChunkReadOp(m_op
, &context
, oid
, context
.pool_name
, false, m_stats
);
401 case TEST_OP_TIER_PROMOTE
:
402 oid
= *(rand_choose(context
.oid_not_in_use
));
403 cout
<< m_op
<< ": " << "tier_promote oid " << oid
<< std::endl
;
404 return new TierPromoteOp(m_op
, &context
, oid
, m_stats
);
406 case TEST_OP_TIER_FLUSH
:
407 oid
= *(rand_choose(context
.oid_not_in_use
));
408 cout
<< m_op
<< ": " << "tier_flush oid " << oid
<< std::endl
;
409 return new TierFlushOp(m_op
, &context
, oid
, m_stats
);
411 case TEST_OP_SET_REDIRECT
:
412 oid
= *(rand_choose(context
.oid_not_in_use
));
413 oid2
= *(rand_choose(context
.oid_redirect_not_in_use
));
414 cout
<< m_op
<< ": " << "set_redirect oid " << oid
<< " target oid " << oid2
<< std::endl
;
415 return new SetRedirectOp(m_op
, &context
, oid
, oid2
, context
.pool_name
, m_stats
);
417 case TEST_OP_UNSET_REDIRECT
:
418 oid
= *(rand_choose(context
.oid_not_in_use
));
419 cout
<< m_op
<< ": " << "unset_redirect oid " << oid
<< std::endl
;
420 return new UnsetRedirectOp(m_op
, &context
, oid
, m_stats
);
422 case TEST_OP_SET_CHUNK
:
424 ceph_assert(m_enable_dedup
);
425 oid
= *(rand_choose(context
.oid_not_in_use
));
426 cout
<< m_op
<< ": " << "set_chunk oid " << oid
427 << " target oid " << std::endl
;
428 return new SetChunkOp(m_op
, &context
, oid
, "", m_stats
);
431 case TEST_OP_TIER_EVICT
:
432 oid
= *(rand_choose(context
.oid_not_in_use
));
433 cout
<< m_op
<< ": " << "tier_evict oid " << oid
<< std::endl
;
434 return new TierEvictOp(m_op
, &context
, oid
, m_stats
);
437 cerr
<< m_op
<< ": Invalid op type " << type
<< std::endl
;
450 map
<TestOpType
, unsigned int> m_weight_sums
;
451 unsigned int m_total_weight
;
453 bool m_balance_reads
;
454 bool m_localize_reads
;
460 int main(int argc
, char **argv
)
464 int max_in_flight
= 16;
465 int64_t size
= 4000000; // 4 MB
466 int64_t min_stride_size
= -1, max_stride_size
= -1;
468 bool pool_snaps
= false;
469 bool write_fadvise_dontneed
= false;
476 { TEST_OP_READ
, "read", true },
477 { TEST_OP_WRITE
, "write", false },
478 { TEST_OP_WRITE_EXCL
, "write_excl", false },
479 { TEST_OP_WRITESAME
, "writesame", false },
480 { TEST_OP_DELETE
, "delete", true },
481 { TEST_OP_SNAP_CREATE
, "snap_create", true },
482 { TEST_OP_SNAP_REMOVE
, "snap_remove", true },
483 { TEST_OP_ROLLBACK
, "rollback", true },
484 { TEST_OP_SETATTR
, "setattr", true },
485 { TEST_OP_RMATTR
, "rmattr", true },
486 { TEST_OP_WATCH
, "watch", true },
487 { TEST_OP_COPY_FROM
, "copy_from", true },
488 { TEST_OP_HIT_SET_LIST
, "hit_set_list", true },
489 { TEST_OP_IS_DIRTY
, "is_dirty", true },
490 { TEST_OP_UNDIRTY
, "undirty", true },
491 { TEST_OP_CACHE_FLUSH
, "cache_flush", true },
492 { TEST_OP_CACHE_TRY_FLUSH
, "cache_try_flush", true },
493 { TEST_OP_CACHE_EVICT
, "cache_evict", true },
494 { TEST_OP_APPEND
, "append", true },
495 { TEST_OP_APPEND_EXCL
, "append_excl", true },
496 { TEST_OP_SET_REDIRECT
, "set_redirect", true },
497 { TEST_OP_UNSET_REDIRECT
, "unset_redirect", true },
498 { TEST_OP_CHUNK_READ
, "chunk_read", true },
499 { TEST_OP_TIER_PROMOTE
, "tier_promote", true },
500 { TEST_OP_TIER_FLUSH
, "tier_flush", true },
501 { TEST_OP_SET_CHUNK
, "set_chunk", true },
502 { TEST_OP_TIER_EVICT
, "tier_evict", true },
503 { TEST_OP_READ
/* grr */, NULL
},
508 } chunk_algo_types
[] = {
513 map
<TestOpType
, unsigned int> op_weights
;
514 string pool_name
= "rbd";
515 string low_tier_pool_name
= "";
516 bool ec_pool
= false;
517 bool no_omap
= false;
518 bool no_sparse
= false;
519 bool balance_reads
= false;
520 bool localize_reads
= false;
521 bool set_redirect
= false;
522 bool set_chunk
= false;
523 bool enable_dedup
= false;
524 string chunk_algo
= "";
525 string chunk_size
= "";
528 for (int i
= 1; i
< argc
; ++i
) {
529 if (strcmp(argv
[i
], "--max-ops") == 0)
530 ops
= atoi(argv
[++i
]);
531 else if (strcmp(argv
[i
], "--pool") == 0)
532 pool_name
= argv
[++i
];
533 else if (strcmp(argv
[i
], "--max-seconds") == 0)
534 max_seconds
= atoi(argv
[++i
]);
535 else if (strcmp(argv
[i
], "--objects") == 0)
536 objects
= atoi(argv
[++i
]);
537 else if (strcmp(argv
[i
], "--max-in-flight") == 0)
538 max_in_flight
= atoi(argv
[++i
]);
539 else if (strcmp(argv
[i
], "--size") == 0)
540 size
= atoi(argv
[++i
]);
541 else if (strcmp(argv
[i
], "--min-stride-size") == 0)
542 min_stride_size
= atoi(argv
[++i
]);
543 else if (strcmp(argv
[i
], "--max-stride-size") == 0)
544 max_stride_size
= atoi(argv
[++i
]);
545 else if (strcmp(argv
[i
], "--no-omap") == 0)
547 else if (strcmp(argv
[i
], "--no-sparse") == 0)
549 else if (strcmp(argv
[i
], "--balance-reads") == 0)
550 balance_reads
= true;
551 else if (strcmp(argv
[i
], "--localize-reads") == 0)
552 localize_reads
= true;
553 else if (strcmp(argv
[i
], "--pool-snaps") == 0)
555 else if (strcmp(argv
[i
], "--write-fadvise-dontneed") == 0)
556 write_fadvise_dontneed
= true;
557 else if (strcmp(argv
[i
], "--ec-pool") == 0) {
558 if (!op_weights
.empty()) {
559 cerr
<< "--ec-pool must be specified prior to any ops" << std::endl
;
565 } else if (strcmp(argv
[i
], "--op") == 0) {
568 cerr
<< "Missing op after --op" << std::endl
;
572 for (j
= 0; op_types
[j
].name
; ++j
) {
573 if (strcmp(op_types
[j
].name
, argv
[i
]) == 0) {
577 if (!op_types
[j
].name
) {
578 cerr
<< "unknown op " << argv
[i
] << std::endl
;
583 cerr
<< "Weight unspecified." << std::endl
;
586 int weight
= atoi(argv
[i
]);
588 cerr
<< "Weights must be nonnegative." << std::endl
;
590 } else if (weight
> 0) {
591 if (ec_pool
&& !op_types
[j
].ec_pool_valid
) {
592 cerr
<< "Error: cannot use op type " << op_types
[j
].name
593 << " with --ec-pool" << std::endl
;
596 cout
<< "adding op weight " << op_types
[j
].name
<< " -> " << weight
<< std::endl
;
597 op_weights
.insert(pair
<TestOpType
, unsigned int>(op_types
[j
].op
, weight
));
599 } else if (strcmp(argv
[i
], "--set_redirect") == 0) {
601 } else if (strcmp(argv
[i
], "--set_chunk") == 0) {
603 } else if (strcmp(argv
[i
], "--low_tier_pool") == 0) {
605 * disallow redirect or chunk object into the same pool
606 * to prevent the race. see https://github.com/ceph/ceph/pull/20096
608 low_tier_pool_name
= argv
[++i
];
609 } else if (strcmp(argv
[i
], "--enable_dedup") == 0) {
611 } else if (strcmp(argv
[i
], "--dedup_chunk_algo") == 0) {
614 cerr
<< "Missing chunking algorithm after --dedup_chunk_algo" << std::endl
;
618 for (j
= 0; chunk_algo_types
[j
].name
; ++j
) {
619 if (strcmp(chunk_algo_types
[j
].name
, argv
[i
]) == 0) {
623 if (!chunk_algo_types
[j
].name
) {
624 cerr
<< "unknown op " << argv
[i
] << std::endl
;
627 chunk_algo
= chunk_algo_types
[j
].name
;
628 } else if (strcmp(argv
[i
], "--dedup_chunk_size") == 0) {
629 chunk_size
= argv
[++i
];
631 cerr
<< "unknown arg " << argv
[i
] << std::endl
;
636 if (set_redirect
|| set_chunk
) {
637 if (low_tier_pool_name
== "") {
638 cerr
<< "low_tier_pool is needed" << std::endl
;
644 if (chunk_algo
== "" || chunk_size
== "") {
645 cerr
<< "Missing chunking algorithm: " << chunk_algo
646 << " or chunking size: " << chunk_size
<< std::endl
;
651 if (op_weights
.empty()) {
652 cerr
<< "No operations specified" << std::endl
;
656 if (min_stride_size
< 0)
657 min_stride_size
= size
/ 10;
658 if (max_stride_size
< 0)
659 max_stride_size
= size
/ 5;
661 cout
<< pretty_version_to_str() << std::endl
;
662 cout
<< "Configuration:" << std::endl
663 << "\tNumber of operations: " << ops
<< std::endl
664 << "\tNumber of objects: " << objects
<< std::endl
665 << "\tMax in flight operations: " << max_in_flight
<< std::endl
666 << "\tObject size (in bytes): " << size
<< std::endl
667 << "\tWrite stride min: " << min_stride_size
<< std::endl
668 << "\tWrite stride max: " << max_stride_size
<< std::endl
;
670 if (min_stride_size
>= max_stride_size
) {
671 cerr
<< "Error: max_stride_size must be more than min_stride_size"
676 if (min_stride_size
> size
|| max_stride_size
> size
) {
677 cerr
<< "Error: min_stride_size and max_stride_size must be "
678 << "smaller than object size" << std::endl
;
682 if (max_in_flight
* 2 > objects
) {
683 cerr
<< "Error: max_in_flight must be <= than the number of objects / 2"
688 char *id
= getenv("CEPH_CLIENT_ID");
689 RadosTestContext
context(
698 write_fadvise_dontneed
,
706 WeightedTestGenerator gen
= WeightedTestGenerator(
708 op_weights
, &stats
, max_seconds
,
709 ec_pool
, balance_reads
, localize_reads
,
710 set_redirect
, set_chunk
, enable_dedup
);
711 int r
= context
.init();
713 cerr
<< "Error initializing rados test context: "
714 << cpp_strerror(r
) << std::endl
;
719 if (!context
.check_chunks_refcount(context
.low_tier_io_ctx
, context
.io_ctx
)) {
720 cerr
<< " Invalid refcount " << std::endl
;
726 cerr
<< context
.errors
<< " errors." << std::endl
;
727 cerr
<< stats
<< std::endl
;