diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/test/omap_bench.cc | 413 | ||||
-rw-r--r-- | src/test/omap_bench.hpp | 192 |
4 files changed, 610 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index 6e045696fcc..595364b60cb 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ core *.generated.dot src/ocf/ceph src/ocf/rbd +src/omapbench # temporary directory used by e.g. "make distcheck", e.g. ceph-0.42 /ceph-[0-9]*/ diff --git a/src/Makefile.am b/src/Makefile.am index 9ac4f2f406e..ccddb7f88f1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -214,6 +214,10 @@ testrados_SOURCES = test/osd/TestRados.cc test/osd/TestOpStat.cc test/osd/Object testrados_LDADD = librados.la $(LIBGLOBAL_LDA) bin_DEBUGPROGRAMS += testrados +omapbench_SOURCES = test/omap_bench.cc +omapbench_LDADD = librados.la $(LIBGLOBAL_LDA) +bin_DEBUGPROGRAMS += omapbench + multi_stress_watch_SOURCES = test/multi_stress_watch.cc test/rados-api/test.cc multi_stress_watch_LDADD = librados.la $(LIBGLOBAL_LDA) bin_DEBUGPROGRAMS += multi_stress_watch diff --git a/src/test/omap_bench.cc b/src/test/omap_bench.cc new file mode 100644 index 00000000000..0bc7d860927 --- /dev/null +++ b/src/test/omap_bench.cc @@ -0,0 +1,413 @@ +/* + * Generate latency statistics for a configurable number of object map write + * operations of configurable size. + * + * Created on: May 21, 2012 + * Author: Eleanor Cawthon + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + */ + +#include "include/rados/librados.hpp" +#include "include/Context.h" +#include "common/ceph_context.h" +#include "common/Mutex.h" +#include "common/Cond.h" +#include "include/utime.h" +#include "global/global_context.h" +#include "common/ceph_argparse.h" +#include "omap_bench.hpp" + +#include<string> +#include<iostream> +#include<cassert> +#include<climits> + +using namespace std; +using ceph::bufferlist; + +int OmapBench::setup(int argc, const char** argv) { + int r = rados.init(rados_id.c_str()); + if (r < 0) { + return r; + } + r = rados.conf_read_file(NULL); + if (r < 0) + return r; + r = rados.conf_parse_argv(argc, argv); + if (r < 0) + return r; + r = rados.conf_parse_env(NULL); + if (r < 0) + return r; + r = rados.connect(); + if (r < 0) + return r; + r = rados.ioctx_create(pool_name.c_str(), io_ctx); + if (r < 0) { + rados.shutdown(); + return r; + } + + + //parse omap_bench args + vector<const char*> args; + argv_to_vec(argc,argv,args); + for (int i = 0;i < args.size(); i++) { + if (strcmp(args[i], "-t") == 0){ + threads = atoi(args[i+1]); + } else if (strcmp(args[i],"-o") == 0){ + objects = atoi(args[i+1]); + } else if (strcmp(args[i],"--entries") == 0){ + omap_entries = atoi(args[i+1]); + } else if (strcmp(args[i],"--keysize") == 0){ + omap_key_size = atoi(args[i+1]); + } else if (strcmp(args[i],"--valsize") == 0){ + omap_value_size = atoi(args[i+1]); + } else if (strcmp(args[i],"--inc") == 0){ + increment = atoi(args[i+1]); + } else if (strcmp(args[i],"--omaptype") == 0){ + if(strcmp("rand",args[i+1]) == 0){ + omap_generator = OmapBench::generate_non_uniform_omap; + } + else if (strcmp("uniform",args[i+1]) == 0){ + omap_generator = OmapBench::generate_uniform_omap; + } + } else if (strcmp(args[i],"--help") == 0){ + cout<< "\nUsage: omapbench [options]\n" + << "Generate latency statistics for a configurable number of " + << "object map write operations of\n" + << "configurable size.\n\n" + << "OPTIONS\n" + << " -t number of threads to use (default "<<threads; + cout<<")\n" + << " -o number of objects to write (default "<<objects; + cout<< ")\n" + << " --entries number of entries per object map (default " + <<omap_entries; + cout<<")\n" + << " --keysize number of characters per object map key " + << "(default "<<omap_key_size; + cout<<")\n" + << " --valsize number of characters per object map value " + << "(default "<<omap_value_size; + cout<<")\n" + << " --inc specify the increment to use in the displayed " + << "histogram (default "<<increment; + cout<<")\n" + << " --omaptype specify how omaps should be generated - " + << "rand for random sizes between\n" + << " 0 and the max size, uniform for all sizes" + << " to be specified size.\n" + << " (default "<<omap_value_size; + cout<<")\n"; + exit(1); + } + } + + return 0; +} + +//Writer functions +Writer::Writer(OmapBench *omap_bench) : ob(omap_bench){ + stringstream name; + ob->data_lock.Lock(); + name << "object number " << ++(ob->data.started_ops); + ob->data_lock.Unlock(); + oid = name.str(); +} +void Writer::start_time(){ + begin_time = ceph_clock_now(g_ceph_context); +} +void Writer::stop_time(){ + end_time = ceph_clock_now(g_ceph_context); +} +double Writer::get_time(){ + return (end_time - begin_time) * 1000; +} +string Writer::get_oid(){ + return oid; +} +std::map<std::string, bufferlist> & Writer::get_omap(){ + return omap; +} + +//AioWriter functions +AioWriter::AioWriter(OmapBench *ob) : Writer(ob){ + aioc = NULL; +} +AioWriter::~AioWriter(){ + if(aioc) aioc->release(); +} +librados::AioCompletion * AioWriter::get_aioc(){ + return aioc; +} +void AioWriter::set_aioc(librados::callback_t complete, + librados::callback_t safe){ + aioc = ob->rados.aio_create_completion(this, complete, safe); +} + +void OmapBench::aio_is_safe(rados_completion_t c, void *arg) { + AioWriter *aiow = reinterpret_cast<AioWriter *>(arg); + aiow->stop_time(); + Mutex * data_lock = &aiow->ob->data_lock; + Mutex * thread_is_free_lock = &aiow->ob->thread_is_free_lock; + Cond * thread_is_free = &aiow->ob->thread_is_free; + int &busythreads_count = aiow->ob->busythreads_count; + omap_bench_data &data = aiow->ob->data; + int INCREMENT = aiow->ob->increment; + int err = aiow->get_aioc()->get_return_value(); + if (err < 0) { + cout << "error writing AioCompletion"; + return; + } + double time = aiow->get_time(); + delete aiow; + data_lock->Lock(); + data.avg_latency = (data.avg_latency * + data.completed_ops + time) / ++(data.completed_ops); + if (time < data.min_latency){ + data.min_latency = time; + } + if (time > data.max_latency){ + data.max_latency = time; + } + data.total_latency += time; + ++(data.freq_map[time / INCREMENT]); + if(data.freq_map[time/INCREMENT] > data.mode.second){ + data.mode.first = time/INCREMENT; + data.mode.second = data.freq_map[time/INCREMENT]; + } + data_lock->Unlock(); + + thread_is_free_lock->Lock(); + busythreads_count--; + thread_is_free->Signal(); + thread_is_free_lock->Unlock(); +} + +string OmapBench::random_string(int len) { + string ret; + string alphanum = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + for (int i = 0; i < len; ++i) { + ret.push_back(alphanum[rand() % (alphanum.size()) - 1]); + } + + return ret; +} + +int OmapBench::generate_uniform_omap(const int omap_entries, const int key_size, + const int value_size, std::map<std::string,bufferlist> * out_omap){ + bufferlist bl; + stringstream data; + int err = 0; + + //setup omap + for (int i = 0; i < omap_entries; i++) { + bufferlist omap_val; + omap_val.append(random_string(value_size)); + string key = random_string(key_size); + (*out_omap)[key]= omap_val; + } + if (err < 0){ + cout << "generating uniform omap failed - " + << "appending random string to omap failed" << std::endl; + } + return err; +} + +int OmapBench::generate_non_uniform_omap(const int omap_entries, + const int key_size, const int value_size, + std::map<std::string,bufferlist> * out_omap){ + bufferlist bl; + stringstream data; + int err = 0; + + int num_entries = rand() % omap_entries + 1; + int key_len = rand() % key_size +1; + int val_len = rand() % value_size +1; + + //setup omap + for (int i = 0; i < num_entries; i++) { + bufferlist omap_val; + omap_val.append(random_string(val_len)); + string key = random_string(key_len); + (*out_omap)[key] = omap_val; + } + if (err < 0){ + cout << "generating non-uniform omap failed - " + "appending random string to omap failed" << std::endl; + } + return err; +} + + +int OmapBench::write_omap_asynchronously(AioWriter *aiow, + const std::map<std::string,bufferlist> &omap) { + librados::ObjectWriteOperation owo; + owo.create(false); + owo.omap_clear(); + owo.omap_set(omap); + aiow->start_time(); + int err = io_ctx.aio_operate(aiow->get_oid(), aiow->get_aioc(), &owo); + if (err < 0){ + cout << "writing omap failed with code "<<err; + cout << std::endl; + return err; + } + return 0; +} + +int OmapBench::write_objects_in_parallel(omap_generator_t omap_gen) { + comp = NULL; + AioWriter *this_aio_writer; + + Mutex::Locker l(thread_is_free_lock); + for (int i = 0; i < objects; i++) { + assert(busythreads_count <= threads); + //wait for a writer to be free + if (busythreads_count == threads) { + int err = thread_is_free.Wait(thread_is_free_lock); + assert(busythreads_count < threads); + if (err < 0) { + return err; + } + } + + //set up the write + this_aio_writer = new AioWriter(this); + this_aio_writer->set_aioc(NULL,safe); + + //perform the write + busythreads_count++; + int err = omap_gen(omap_entries, omap_key_size, omap_value_size, + & this_aio_writer->get_omap()); + if (err < 0) { + return err; + } + err = OmapBench::write_omap_asynchronously(this_aio_writer, + (this_aio_writer->get_omap())); + + + if (err < 0) { + return err; + } + } + while(busythreads_count > 0){ + thread_is_free.Wait(thread_is_free_lock); + } + + return 0; +} + +int OmapBench::run(){ + return (((OmapBench *)this)->*OmapBench::test)(omap_generator); +} + +int OmapBench::print_written_omap(){ + for (int i = 1; i <= objects; i++) { + int err = 0; + librados::ObjectReadOperation key_read; + set<string> out_keys; + map<string, bufferlist> out_vals; + std::stringstream objstrm; + objstrm << "object number "; + objstrm << i; + cout << "\nPrinting omap for "<<objstrm.str() << std::endl; + key_read.omap_get_keys("", LONG_MAX, &out_keys, &err); + io_ctx.operate(objstrm.str(), &key_read, NULL); + if (err < 0) { + cout << "error " << err; + cout << " getting omap key set " << std::endl; + return err; + } + + librados::ObjectReadOperation val_read; + val_read.omap_get_vals_by_keys(out_keys, &out_vals, &err); + if (err < 0) { + cout << "error " << err; + cout << " getting omap value set " << std::endl; + return err; + } + io_ctx.operate(objstrm.str(), &val_read, NULL); + + for (set<string>::iterator iter = out_keys.begin(); + iter != out_keys.end(); ++iter) { + cout << *iter << "\t" << (out_vals)[*iter] << std::endl; + } + } + return 0; +} + +void OmapBench::print_results(){ + cout << "===============================================================" + << std::endl; + cout << "\nNumber of object maps written:\t" << objects; + cout << "\nNumber of threads used:\t\t" << threads; + cout << "\nEntries per object map:\t\t" << omap_entries; + cout << "\nCharacters per object map key:\t" <<omap_key_size; + cout << "\nCharacters per object map val:\t" << omap_value_size; + cout << std::endl; + cout << std::endl; + cout << "Average latency:\t" << data.avg_latency; + cout << "ms\nMinimum latency:\t" << data.min_latency; + cout << "ms\nMaximum latency:\t" << data.max_latency; + cout << "ms\nMode latency:\t\t"<<"between "<<data.mode.first * increment; + cout << " and " <<data.mode.first * increment + increment; + cout << "ms\nTotal latency:\t\t" << data.total_latency; + cout << "ms"<<std::endl; + cout << std::endl; + cout << "Histogram:" << std::endl; + for(int i = floor(data.min_latency / increment); i < + ceil(data.max_latency / increment); i++){ + cout << ">= "<< i * increment; + cout << "ms"; + if (i * increment < 100) cout << "\t"; + cout << "\t["; + for(int j = 0; j < ((data.freq_map)[i])*45/(data.mode.second); j++){ + cout << "*"; + } + cout << std::endl; + } + cout << "\n===============================================================" + << std::endl; +} + +/** + * runs the specified test with the specified parameters and generates + * a histogram of latencies + */ +int main(int argc, const char** argv) { + OmapBench ob; + int err = ob.setup(argc, argv); + if (err<0){ + cout << "error during setup: "<<err; + cout << std::endl; + exit(1); + } + err = ob.run(); + if (err < 0) { + cout << "writing objects failed with code " << err; + cout << std::endl; + return err; + } + + ob.print_results(); + + //uncomment to show omaps + /*err = ob.return print_written_omap(); + if (err < 0) { + cout << "printing omaps failed with code " << err; + cout << std::endl; + return err; + } + */ + return 0; + +} diff --git a/src/test/omap_bench.hpp b/src/test/omap_bench.hpp new file mode 100644 index 00000000000..2a40557d5de --- /dev/null +++ b/src/test/omap_bench.hpp @@ -0,0 +1,192 @@ +/* + * Generate latency statistics for a configurable number of object map write + * operations of configurable size. + * + * Created on: May 21, 2012 + * Author: Eleanor Cawthon + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + */ + +#ifndef OMAP_BENCH_HPP_ +#define OMAP_BENCH_HPP_ + +#include "common/Mutex.h" +#include "common/Cond.h" +#include "include/rados/librados.hpp" +#include <string> +#include<map> +#include<cfloat>; + +using ceph::bufferlist; + +struct omap_bench_data{ + double avg_latency; + double min_latency; + double max_latency; + double total_latency; + int started_ops; + int completed_ops; + std::map<int,int> freq_map; + pair<int,int> mode; + omap_bench_data(): avg_latency(0.0), min_latency(DBL_MAX), max_latency(0.0), + total_latency(0.0),started_ops(0),completed_ops(0),freq_map(),mode(){} + +}; + +class OmapBench; + +typedef int (*omap_generator_t)(const int omap_entries, const int key_size, + const int value_size, std::map<std::string,bufferlist> * out_omap); +typedef int (OmapBench::*test_t)(omap_generator_t omap_gen); + + +class Writer{ +protected: + string oid; + utime_t begin_time; + utime_t end_time; + std::map<std::string,bufferlist> omap; + OmapBench *ob; + friend class OmapBench; +public: + Writer(OmapBench *omap_bench); + virtual ~Writer(){}; + virtual void start_time(); + virtual void stop_time(); + virtual double get_time(); + virtual string get_oid(); + virtual std::map<std::string,bufferlist> & get_omap(); +}; + +class AioWriter : public Writer{ +protected: + librados::AioCompletion * aioc; + friend class OmapBench; + +public: + AioWriter(OmapBench *omap_bench); + ~AioWriter(); + virtual librados::AioCompletion * get_aioc(); + virtual void set_aioc(librados::callback_t complete, + librados::callback_t safe); +}; + +class OmapBench{ +protected: + librados::IoCtx io_ctx; + librados::Rados rados; + struct omap_bench_data data; + test_t test; + omap_generator_t omap_generator; + + //aio things + Cond thread_is_free; + Mutex thread_is_free_lock; + Mutex data_lock; + int busythreads_count; + librados::callback_t comp; + librados::callback_t safe; + + string pool_name; + string rados_id; + int threads; + int objects; + int omap_entries; + int omap_key_size; + int omap_value_size; + double increment; + + friend class Writer; + friend class AioWriter; + +public: + OmapBench() : data(),busythreads_count(0),pool_name("data"),rados_id("admin"), + safe(aio_is_safe),omap_generator(generate_uniform_omap), + thread_is_free_lock("thread is free lock"), data_lock("data lock"), + threads(3),objects(100),omap_entries(10),omap_key_size(10), + omap_value_size(100),increment(10),test(&OmapBench::write_objects_in_parallel) + {}; + + /** + * Parses command line args, initializes rados and ioctx + */ + int setup(int argc, const char** argv); + + /** + * Callback for when an AioCompletion (called from an AioWriter) + * is safe. deletes the AioWriter that called it, + * Updates data, updates busythreads, and signals thread_is_free. + * + * @param c provided by aio_write - not used + * @param arg the AioWriter that contains this AioCompletion + */ + static void aio_is_safe(rados_completion_t c, void *arg); + + /** + * Generates a random string len characters long + */ + static string random_string(int len); + + /** + * Generates an omap with omap_entries entries, each with keys key_size + * characters long and with string values value_size characters long. + * + * @param out_map pointer to the map to be created + * @return error code + */ + static int generate_uniform_omap(const int omap_entries, const int key_size, + const int value_size, std::map<std::string,bufferlist> * out_omap); + + /** + * The same as generate_uniform_omap except that string lengths are picked + * randomly between 1 and the int arguments + */ + static int generate_non_uniform_omap(const int omap_entries, + const int key_size, + const int value_size, std::map<std::string,bufferlist> * out_omap); + + /** + * Writes an object with the specified AioWriter. + * + * @param aiow the AioWriter to write with + * @param omap the omap to write + * @post: an asynchronous omap_set is launched + */ + int write_omap_asynchronously(AioWriter *aiow, + const std::map<std::string,bufferlist> &omap); + + /* + * Uses aio_write to write omaps generated by omap_gen to OBJECTS objects + * using THREADS AioWriters at a time. + * + * @param omap_gen the method used to generate the omaps. + */ + int write_objects_in_parallel(omap_generator_t omap_gen); + + /* + * runs the test specified by test using the omap generator specified by + * omap_generator + * + * @return error code + */ + int run(); + + /* + * Prints all keys and values for all omap entries for all objects + */ + int print_written_omap(); + + /* + * Displays relevant constants and the histogram generated through a test + */ + void print_results(); +}; + + + +#endif /* OMAP_BENCH_HPP_ */ + |