diff --git a/src/backends/tensorflow.c b/src/backends/tensorflow.c index 1e2f7837f..fa52b2691 100644 --- a/src/backends/tensorflow.c +++ b/src/backends/tensorflow.c @@ -371,7 +371,9 @@ RAI_Model *RAI_ModelCreateTF(RAI_Backend backend, const char* devicestr, RAI_Mod ret->backend = backend; ret->devicestr = RedisModule_Strdup(devicestr); ret->inputs = inputs_; + ret->ninputs = ninputs; ret->outputs = outputs_; + ret->noutputs = ninputs; ret->opts = opts; ret->refCount = 1; diff --git a/src/config.h b/src/config.h index 7c522da4c..3fb2d34ee 100644 --- a/src/config.h +++ b/src/config.h @@ -27,4 +27,7 @@ typedef enum { #define RAI_COPY_RUN_OUTPUT #define RAI_PRINT_BACKEND_ERRORS +#define MODELRUN_BATCH_INITIAL_CAPACITY 10 +#define MODELRUN_PARAM_INITIAL_CAPACITY 10 + #endif /* SRC_CONFIG_H_ */ diff --git a/src/model.c b/src/model.c index 9d8e3f77d..d9be4e52a 100644 --- a/src/model.c +++ b/src/model.c @@ -304,15 +304,14 @@ void RAI_ModelFree(RAI_Model* model, RAI_Error* err) { } RAI_ModelRunCtx* RAI_ModelRunCtxCreate(RAI_Model* model) { -#define BATCH_INITIAL_SIZE 10 RAI_ModelRunCtx* mctx = RedisModule_Calloc(1, sizeof(*mctx)); mctx->model = RAI_ModelGetShallowCopy(model); - mctx->batches = array_new(RAI_ModelCtxBatch, BATCH_INITIAL_SIZE); -#undef BATCH_INITIAL_SIZE + mctx->nbatches=0; + mctx->batches = array_new(RAI_ModelCtxBatch, MODELRUN_BATCH_INITIAL_CAPACITY); return mctx; } -static int Model_RunCtxAddParam(RAI_ModelRunCtx* mctx, RAI_ModelCtxParam** paramArr, +static int Model_RunCtxAddParam(RAI_ModelRunCtx* mctx, RAI_ModelCtxParam** paramArr, const char* name, RAI_Tensor* tensor) { RAI_ModelCtxParam param = { @@ -320,22 +319,24 @@ static int Model_RunCtxAddParam(RAI_ModelRunCtx* mctx, RAI_ModelCtxParam** param .tensor = tensor ? RAI_TensorGetShallowCopy(tensor): NULL, }; *paramArr = array_append(*paramArr, param); - return 1; + return REDISMODULE_OK; } int RAI_ModelRunCtxAddInput(RAI_ModelRunCtx* mctx, size_t id, const char* inputName, RAI_Tensor* inputTensor) { if (id >= RAI_ModelRunCtxNumBatches(mctx)) { // TODO error - return 0; + return REDISMODULE_ERR; } + mctx->batches[id].ninputs++; return Model_RunCtxAddParam(mctx, &mctx->batches[id].inputs, inputName, inputTensor); } int RAI_ModelRunCtxAddOutput(RAI_ModelRunCtx* mctx, size_t id, const char* outputName) { if (id >= RAI_ModelRunCtxNumBatches(mctx)) { // TODO error - return 0; + return REDISMODULE_ERR; } + mctx->batches[id].noutputs++; return Model_RunCtxAddParam(mctx, &mctx->batches[id].outputs, outputName, NULL); } @@ -344,40 +345,42 @@ size_t RAI_ModelRunCtxNumInputs(RAI_ModelRunCtx* mctx) { return 0; } // Here we assume batch is well-formed (i.e. number of outputs is equal in all batches) - return array_len(mctx->batches[0].inputs); + return mctx->batches[0].ninputs; } size_t RAI_ModelRunCtxNumOutputs(RAI_ModelRunCtx* mctx) { - if (RAI_ModelRunCtxNumBatches(mctx) == 0) { + if (RAI_ModelRunCtxNumBatches(mctx)) { return 0; } // Here we assume batch is well-formed (i.e. number of outputs is equal in all batches) - return array_len(mctx->batches[0].outputs); + return mctx->batches[0].noutputs; } int RAI_ModelRunCtxAddBatch(RAI_ModelRunCtx* mctx) { -#define PARAM_INITIAL_SIZE 10 RAI_ModelCtxBatch batch = { - .inputs = array_new(RAI_ModelCtxParam, PARAM_INITIAL_SIZE), - .outputs = array_new(RAI_ModelCtxParam, PARAM_INITIAL_SIZE) + .inputs = array_new(RAI_ModelCtxParam, MODELRUN_PARAM_INITIAL_CAPACITY), + .ninputs = 0, + .outputs = array_new(RAI_ModelCtxParam, MODELRUN_PARAM_INITIAL_CAPACITY), + .noutputs = 0 }; -#undef PARAM_INITIAL_SIZE array_append(mctx->batches, batch); + mctx->nbatches++; return array_len(mctx->batches)-1; } size_t RAI_ModelRunCtxNumBatches(RAI_ModelRunCtx* mctx) { - return array_len(mctx->batches); + return mctx->nbatches; } void RAI_ModelRunCtxCopyBatch(RAI_ModelRunCtx* dest, size_t id_dest, RAI_ModelRunCtx* src, size_t id_src) { - size_t ninputs = array_len(src->batches[id_src].inputs); + const size_t ninputs = src->batches[id_src].ninputs; + const size_t noutputs = src->batches[id_src].noutputs; + for (size_t i=0; ibatches[id_src].inputs[i]; RAI_ModelRunCtxAddInput(dest, id_dest, param.name, param.tensor); } - size_t noutputs = array_len(src->batches[id_src].outputs); for (size_t i=0; ibatches[id_src].outputs[i]; RAI_ModelRunCtxAddOutput(dest, id_dest, param.name); @@ -423,7 +426,7 @@ void RAI_ModelRunCtxFree(RAI_ModelRunCtx* mctx) { } int RAI_ModelRun(RAI_ModelRunCtx* mctx, RAI_Error* err) { - int ret; + int ret = REDISMODULE_ERR; switch (mctx->model->backend) { case RAI_BACKEND_TENSORFLOW: diff --git a/src/model_struct.h b/src/model_struct.h index 3a7a38abc..173eef7fc 100644 --- a/src/model_struct.h +++ b/src/model_struct.h @@ -35,13 +35,16 @@ typedef struct RAI_ModelCtxParam { typedef struct RAI_ModelCtxBatch { RAI_ModelCtxParam* inputs; + size_t ninputs; RAI_ModelCtxParam* outputs; + size_t noutputs; } RAI_ModelCtxBatch; typedef struct RAI_ModelRunCtx { size_t ctxtype; RAI_Model* model; RAI_ModelCtxBatch* batches; + size_t nbatches; } RAI_ModelRunCtx; #endif /* SRC_MODEL_STRUCT_H_ */ diff --git a/src/redisai.c b/src/redisai.c index b14c9c05c..ecb71a993 100644 --- a/src/redisai.c +++ b/src/redisai.c @@ -342,8 +342,8 @@ int RedisAI_TensorSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv if( RedisModule_ModuleTypeSetValue(key, RedisAI_TensorType, t) != REDISMODULE_OK ){ return RedisModule_ReplyWithError(ctx, "ERR could not save tensor"); } - RedisModule_CloseKey(key); RedisModule_ReplyWithSimpleString(ctx, "OK"); + RedisModule_CloseKey(key); RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } @@ -514,6 +514,7 @@ struct RedisAI_RunInfo { RedisModuleBlockedClient *client; RedisModuleString *runkey; RedisModuleString **outkeys; + size_t outkeys_argc; RAI_ModelRunCtx *mctx; RAI_ScriptRunCtx *sctx; int status; @@ -522,15 +523,18 @@ struct RedisAI_RunInfo { }; void RedisAI_FreeRunInfo(RedisModuleCtx *ctx, struct RedisAI_RunInfo *rinfo) { + if (rinfo->runkey) { + RedisModule_FreeString(ctx, rinfo->runkey); + } if (rinfo->mctx) { - for(int i = 0 ; i < RAI_ModelRunCtxNumOutputs(rinfo->mctx) ; ++i){ + for(int i = 0 ; i < rinfo->outkeys_argc; i++){ RedisModule_FreeString(ctx, rinfo->outkeys[i]); } RedisModule_Free(rinfo->outkeys); RAI_ModelRunCtxFree(rinfo->mctx); } else if (rinfo->sctx) { - for(int i = 0 ; i < RAI_ScriptRunCtxNumOutputs(rinfo->sctx) ; ++i){ + for(int i = 0 ; i < rinfo->outkeys_argc; i++){ RedisModule_FreeString(ctx, rinfo->outkeys[i]); } RedisModule_Free(rinfo->outkeys); @@ -1084,8 +1088,9 @@ int RedisAI_Run_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { return RedisModule_ReplyWithSimpleString(ctx, "OK"); } - -// model key, INPUTS, key1, key2 ... OUTPUTS key1 key2 ... +/* + * AI.MODELRUN key INPUTS key1 key2 ... OUTPUTS key1 key2 ... + */ int RedisAI_ModelRun_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { // 1. clone inputs as needed in the main thread (only the alternative is to lock) // 2. spawn the new thread for running the model @@ -1098,28 +1103,8 @@ int RedisAI_ModelRun_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, // The key is having a single thread looping for execution if (argc < 3) return RedisModule_WrongArity(ctx); - if (RedisModule_IsKeysPositionRequest(ctx)) { - RedisModule_KeyAtPos(ctx, 1); - for (int i=2; irunkey = argv[1]; + rinfo->mctx = RAI_ModelRunCtxCreate(mto); + rinfo->sctx = NULL; + rinfo->outkeys = RedisModule_Calloc(mto->noutputs, sizeof(RedisModuleString*)); + rinfo->err = NULL; + int is_input = 0; + size_t ninputs = 0; + size_t noutputs = 0; + int outputs_flag_count = 0; - size_t ninputs = inac.argc; - RedisModuleString *inputs[ninputs]; - for (size_t i=0; imctx); - size_t noutputs = outac.argc; - RedisModuleString *outputs[noutputs]; - for (size_t i=0; imctx, 0, arg_string, t) != REDISMODULE_OK){ + // RedisAI_FreeRunInfo(ctx, rinfo); + return RedisModule_ReplyWithError(ctx, "ERR Input key not found."); + } + ninputs++; + } + else { + if (RAI_ModelRunCtxAddOutput(rinfo->mctx, 0, arg_string)!= REDISMODULE_OK){ + // RedisAI_FreeRunInfo(ctx, rinfo); + return RedisModule_ReplyWithError(ctx, "ERR Output key not found."); + } + rinfo->outkeys[noutputs] = argv[argpos]; + noutputs++; + } + } } + rinfo->outkeys_argc = noutputs; if (mto->inputs && array_len(mto->inputs) != ninputs) { + RedisAI_FreeRunInfo(ctx, rinfo); return RedisModule_ReplyWithError(ctx, "Number of names given as INPUTS during MODELSET and keys given as INPUTS here do not match."); } if (mto->outputs && array_len(mto->outputs) != noutputs) { + RedisAI_FreeRunInfo(ctx, rinfo); return RedisModule_ReplyWithError(ctx, "Number of names given as OUTPUTS during MODELSET and keys given as OUTPUTS here do not match."); } - struct RedisAI_RunInfo *rinfo = RedisModule_Calloc(1, sizeof(struct RedisAI_RunInfo)); - RedisModule_RetainString(ctx, keystr); - rinfo->runkey = keystr; - rinfo->mctx = RAI_ModelRunCtxCreate(mto); - rinfo->sctx = NULL; - rinfo->outkeys = NULL; - rinfo->err = NULL; - - RAI_ModelRunCtxAddBatch(rinfo->mctx); - - for (size_t i=0; iinputs) { - opname = mto->inputs[i]; - } - if (!RAI_ModelRunCtxAddInput(rinfo->mctx, 0, opname, t)) { - // todo free rinfo - return RedisModule_ReplyWithError(ctx, "Input key not found."); - } - } - rinfo->outkeys = RedisModule_Calloc(noutputs, sizeof(RedisModuleString*)); - for (size_t i=0; ioutputs) { - opname = mto->outputs[i]; - } - if (!RAI_ModelRunCtxAddOutput(rinfo->mctx, 0, opname)) { - // todo free rinfo - return RedisModule_ReplyWithError(ctx, "Output key not found."); - } - RedisModule_RetainString(ctx, outputs[i]); - rinfo->outkeys[i] = outputs[i]; - } + // if (mto->ninputs != ninputs) { + // RedisAI_FreeRunInfo(ctx, rinfo); + // char *buffer = RedisModule_Alloc( 150* sizeof(*buffer)); + // sprintf(buffer, "ERR Number of names given as INPUTS (%d) during MODELSET and keys given as INPUTS (%d) here do not match.", mto->ninputs, ninputs ); + // return RedisModule_ReplyWithError(ctx, buffer); + // } - // RedisModule_AbortBlock(bc); - // return RedisModule_ReplyWithError(ctx, "-ERR Can't start thread"); + // if (mto->noutputs != noutputs) { + // RedisAI_FreeRunInfo(ctx, rinfo); + // char *buffer = RedisModule_Alloc( 150* sizeof(*buffer)); + // sprintf(buffer, "ERR Number of names given as OUTPUTS (%d) during MODELSET and keys given as OUTPUTS (%d) here do not match.", mto->noutputs, noutputs ); + // return RedisModule_ReplyWithError(ctx, buffer); + // } AI_dictEntry *entry = AI_dictFind(run_queues, mto->devicestr); RunQueueInfo *run_queue_info = NULL; if (!entry){ // If the queue does not exist, initialize it if(ensureRunQueue(mto->devicestr)==REDISMODULE_ERR) { + RedisAI_FreeRunInfo(ctx, rinfo); return RedisModule_ReplyWithError(ctx, "Queue not initialized for device."); } entry = AI_dictFind(run_queues, mto->devicestr); diff --git a/test/includes.py b/test/includes.py index 3fa31936f..5267f89c2 100755 --- a/test/includes.py +++ b/test/includes.py @@ -22,6 +22,7 @@ TEST_ONNX = os.environ.get("TEST_ONNX") != "0" and os.environ.get("WITH_ORT") != "0" DEVICE = os.environ.get('DEVICE', 'CPU').upper().encode('utf-8', 'ignore').decode('utf-8') VALGRIND = os.environ.get("VALGRIND") == "1" +PROFILER = os.environ.get("PROFILER") == "1" print(f"Running tests on {DEVICE}\n") diff --git a/test/tests_common.py b/test/tests_common.py index db9e761b3..306143e04 100644 --- a/test/tests_common.py +++ b/test/tests_common.py @@ -63,7 +63,7 @@ def test_common_tensorset_error_replies(env): env.assertEqual(type(exception), redis.exceptions.ResponseError) env.assertEqual("invalid or negative value found in tensor shape",exception.__str__()) - # ERR invalid argument found in tensor shape + # ERR invalid or negative value found in tensor shape try: con.execute_command('AI.TENSORSET', 'z', 'INT32', 2, 'unsupported', 2, 3) except Exception as e: @@ -85,43 +85,57 @@ def test_common_tensorset_error_replies(env): except Exception as e: exception = e env.assertEqual(type(exception), redis.exceptions.ResponseError) - env.assertEqual(exception.__str__(), "invalid value") + env.assertEqual("invalid value", exception.__str__()) try: con.execute_command('AI.TENSORSET', 1) except Exception as e: exception = e env.assertEqual(type(exception), redis.exceptions.ResponseError) + env.assertEqual("wrong number of arguments for 'AI.TENSORSET' command",exception.__str__()) try: con.execute_command('AI.TENSORSET', 'y', 'FLOAT') except Exception as e: exception = e env.assertEqual(type(exception), redis.exceptions.ResponseError) + env.assertEqual("wrong number of arguments for 'AI.TENSORSET' command",exception.__str__()) try: con.execute_command('AI.TENSORSET', 'y', 'FLOAT', '2') except Exception as e: exception = e env.assertEqual(type(exception), redis.exceptions.ResponseError) + env.assertEqual("wrong number of arguments for 'AI.TENSORSET' command",exception.__str__()) try: con.execute_command('AI.TENSORSET', 'y', 'FLOAT', 2, 'VALUES') except Exception as e: exception = e env.assertEqual(type(exception), redis.exceptions.ResponseError) + env.assertEqual("wrong number of arguments for 'AI.TENSORSET' command",exception.__str__()) try: con.execute_command('AI.TENSORSET', 'y', 'FLOAT', 2, 'VALUES', 1) except Exception as e: exception = e env.assertEqual(type(exception), redis.exceptions.ResponseError) + env.assertEqual("wrong number of arguments for 'AI.TENSORSET' command",exception.__str__()) try: con.execute_command('AI.TENSORSET', 'y', 'FLOAT', 2, 'VALUES', '1') except Exception as e: exception = e env.assertEqual(type(exception), redis.exceptions.ResponseError) + env.assertEqual("wrong number of arguments for 'AI.TENSORSET' command",exception.__str__()) + + # test for more arguments than the required + try: + con.execute_command('AI.TENSORSET', 'z', 'FLOAT', 2, 'VALUES', 2, 3, "extra1", "extra2") + except Exception as e: + exception = e + env.assertEqual(type(exception), redis.exceptions.ResponseError) + env.assertEqual("wrong number of arguments for 'AI.TENSORSET' command",exception.__str__()) def test_common_tensorget(env): @@ -250,7 +264,6 @@ def funcname(env, blob, repetitions, same_key): avg_ops_sec = 100000*10/elapsed_time env.debugPrint("AI.TENSORSET elapsed time(sec) {:6.2f}\tAvg. ops/sec {:10.2f}".format(elapsed_time, avg_ops_sec), True) - def test_tensorset_disconnect(env): red = env.getConnection() ret = send_and_disconnect(('AI.TENSORSET', 't_FLOAT', 'FLOAT', 2, 'VALUES', 2, 3), red) diff --git a/test/tests_profilers.py b/test/tests_profilers.py new file mode 100644 index 000000000..5a1ca47d8 --- /dev/null +++ b/test/tests_profilers.py @@ -0,0 +1,164 @@ +import sys +import time + +from RLTest.Profilers.stackUtil import fromFoldedStacksToDataframe +from includes import * + +''' +python -m RLTest --test tests_common.py --module path/to/redisai.so +''' + + +# +# def test_profile_small_tensorset(env): +# if not PROFILER: +# env.debugPrint("skipping {} since PROFILER!=1".format( +# sys._getframe().f_code.co_name), force=True) +# return +# con = env.getConnection() +# +# tested_datatypes = ["FLOAT", "DOUBLE", "INT8", "INT16", "INT32", "INT64", "UINT8", "UINT16"] +# tested_datatypes_blobs = {} +# +# for datatype in tested_datatypes: +# ret = con.execute_command('AI.TENSORSET', 'tensor_{0}'.format(datatype), datatype, 2, 'VALUES', 1, 1) +# env.assertEqual(ret, b'OK') +# +# # AI.TENSORGET in BLOB format and set in a new key +# for datatype in tested_datatypes: +# tensor_dtype, tensor_dim, tensor_blob = con.execute_command('AI.TENSORGET', 'tensor_{0}'.format(datatype), +# 'BLOB') +# tested_datatypes_blobs[datatype] = tensor_blob +# +# res = env.startProfiler(999) +# for datatype in tested_datatypes: +# for tensor_number in range(1, 10000): +# ret = con.execute_command('AI.TENSORSET', 'tensor_blob_{0}_{1}'.format(datatype, tensor_number), datatype, +# 2, 'BLOB', tested_datatypes_blobs[datatype]) +# env.assertEqual(ret, b'OK') +# stopandwrapUpProfiler(env, sys._getframe().f_code.co_name, "RedisAI_TensorSet_RedisCommand") +# +# # +# def test_profile_medium_tensorset(env): +# if not PROFILER: +# env.debugPrint("skipping {} since PROFILER!=1".format( +# sys._getframe().f_code.co_name), force=True) +# return +# con = env.getConnection() +# +# tested_datatypes = ["FLOAT", "DOUBLE", "INT8", "INT16", "INT32", "INT64", "UINT8", "UINT16"] +# tested_datatypes_blobs = {} +# +# for datatype in tested_datatypes: +# ret = con.execute_command('AI.TENSORSET', 'tensor_{0}'.format(datatype), datatype, 1, 256) +# env.assertEqual(ret, b'OK') +# +# # AI.TENSORGET in BLOB format and set in a new key +# for datatype in tested_datatypes: +# tensor_dtype, tensor_dim, tensor_blob = con.execute_command('AI.TENSORGET', 'tensor_{0}'.format(datatype), +# 'BLOB') +# tested_datatypes_blobs[datatype] = tensor_blob +# +# res = env.startProfiler(999) +# for datatype in tested_datatypes: +# for tensor_number in range(1, 10000): +# ret = con.execute_command('AI.TENSORSET', 'tensor_blob_{0}_{1}'.format(datatype, tensor_number), datatype, +# 1, 256, 'BLOB', tested_datatypes_blobs[datatype]) +# env.assertEqual(ret, b'OK') +# stopandwrapUpProfiler(env, sys._getframe().f_code.co_name, "RedisAI_TensorSet_RedisCommand") +# +# # +# # def test_profile_large_tensorset(env): +# # if not PROFILER: +# # env.debugPrint("skipping {} since PROFILER!=1".format( +# # sys._getframe().f_code.co_name), force=True) +# # return +# # con = env.getConnection() +# # +# # model_pb, labels, img = load_mobilenet_test_data() +# # res = env.startProfiler(999) +# # for tensor_number in range(1, 10000): +# # ret = con.execute_command('AI.TENSORSET', 'tensor_{0}'.format(tensor_number), +# # 'FLOAT', 1, img.shape[1], img.shape[0], img.shape[2], +# # 'BLOB', img.tobytes()) +# # env.assertEqual(ret, b'OK') +# # +# # stopandwrapUpProfiler(env, sys._getframe().f_code.co_name, "RedisAI_TensorSet_RedisCommand") + +def test_profile_modelrun(env): + if not PROFILER: + env.debugPrint("skipping {} since PROFILER!=1".format( + sys._getframe().f_code.co_name), force=True) + return + con = env.getConnection() + + model_pb, creditcard_transactions, creditcard_referencedata = load_creditcardfraud_data(env) + + tensor_number = 1 + for transaction_tensor in creditcard_transactions: + # env.debugPrint("at transactionTensor:{0} {1} {2}".format( + # tensor_number, transaction_tensor.shape, len(transaction_tensor.tobytes())), force=True) + ret = con.execute_command('AI.TENSORSET', 'transactionTensor:{0}'.format(tensor_number), + 'FLOAT', 1, 30, + 'BLOB', transaction_tensor.tobytes()) + env.assertEqual(ret, b'OK') + tensor_number = tensor_number + 1 + + tensor_number = 1 + for reference_tensor in creditcard_referencedata: + # env.debugPrint("at referenceTensor:{0} {1} {2}".format( + # tensor_number, reference_tensor.shape, len(reference_tensor.tobytes())), force=True) + ret = con.execute_command('AI.TENSORSET', 'referenceTensor:{0}'.format(tensor_number), + 'FLOAT', 1, 256, + 'BLOB', reference_tensor.tobytes()) + env.assertEqual(ret, b'OK') + tensor_number = tensor_number + 1 + + ret = con.execute_command('AI.MODELSET', 'financialNet', 'TF', "CPU", + 'INPUTS', 'transaction', 'reference', 'OUTPUTS', 'output', model_pb) + env.assertEqual(ret, b'OK') + + res = env.startProfiler(frequency=999, profileOnlyMasterThread=True) + t = time.time() + for tensor_number in range(1, 10001): + for repetition in range(0, 10): + ret = con.execute_command('AI.MODELRUN', 'financialNet', 'INPUTS', + 'transactionTensor:{}'.format(tensor_number), + 'referenceTensor:{}'.format(tensor_number), 'OUTPUTS', + 'classificationTensor:{}_{}'.format(tensor_number, repetition)) + env.assertEqual(ret, b'OK') + elapsed_time = time.time() - t + stopandwrapUpProfiler(env, sys._getframe().f_code.co_name, "RedisAI_ModelRun_RedisCommand") + avg_ops_sec = 100 * 10000 / elapsed_time + env.debugPrint( + "AI.TENSORSET elapsed time(sec) {:6.2f}\tAvg. ops/sec {:10.2f}".format(elapsed_time, avg_ops_sec), + True) + + +def stopandwrapUpProfiler(env, testname, startFunction): + res = env.stopProfiler() + env.assertEqual(res, True) + env.debugPrint("{0} perf.data file {1}".format(testname, env.getProfilerOutputs()), + force=True) + res = env.generatePerfEventsMap() + env.assertEqual(res, True) + res = env.generateTraceFiles() + env.assertEqual(res, True) + res = env.stackCollapse() + env.assertEqual(res, True) + stacks = env.getCollapsedStacksMap() + for filename, stacksMap in stacks.items(): + env.debugPrint( + "{0} collapsed stacks {1} len {2}".format(testname, filename, len(stacksMap)), + force=True) + df = fromFoldedStacksToDataframe(stacksMap, startFunction, threshold=1) + # render dataframe as html + html = df.to_html(index=False) + + # write html to file + text_file = open("{}.html".format(testname), "w") + env.debugPrint( + "{}.html".format(testname), + force=True) + text_file.write(html) + text_file.close()