root/modules/ts/src/ts_perf.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. setCurrentThreadAffinityMask
  2. TearDown
  3. randu
  4. instance
  5. add
  6. addMoments
  7. addKeypoints
  8. addMatches
  9. Init
  10. init
  11. write
  12. getCurrentTestNodeName
  13. isVector
  14. getElem
  15. write
  16. verify
  17. write
  18. countViolations
  19. verify
  20. clear
  21. loadPerfValidationResults
  22. savePerfValidationResult
  23. savePerfValidationResults
  24. SetUp
  25. TearDown
  26. Init
  27. Init
  28. RecordRunParameters
  29. getSelectedImpl
  30. setModulePerformanceStrategy
  31. getCurrentModulePerformanceStrategy
  32. _calibrate
  33. getMetrics
  34. TestBody
  35. PerfTestBody
  36. declare
  37. declareArray
  38. warmup
  39. getSizeInBytes
  40. getSize
  41. getCurrentPerformanceStrategy
  42. next
  43. warmup_impl
  44. getTotalInputSize
  45. getTotalOutputSize
  46. startTimer
  47. stopTimer
  48. calcMetrics
  49. validateMetrics
  50. reportMetrics
  51. SetUp
  52. TearDown
  53. getDataPath
  54. RunPerfTestBody
  55. iterations
  56. time
  57. tbb_threads
  58. runs
  59. in
  60. in
  61. in
  62. in
  63. out
  64. out
  65. out
  66. out
  67. strategy
  68. cmp
  69. sort
  70. targetDevice
  71. PrintTo
  72. PrintTo
  73. PrintTo

#include "precomp.hpp"

#include <map>
#include <iostream>
#include <fstream>

#if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif

#ifdef HAVE_CUDA
#include "opencv2/core/cuda.hpp"
#endif

#ifdef ANDROID
# include <sys/time.h>
#endif

using namespace perf;

int64 TestBase::timeLimitDefault = 0;
unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1);
int64 TestBase::_timeadjustment = 0;

// Item [0] will be considered the default implementation.
static std::vector<std::string> available_impls;

static std::string  param_impl;

static enum PERF_STRATEGY strategyForce = PERF_STRATEGY_DEFAULT;
static enum PERF_STRATEGY strategyModule = PERF_STRATEGY_SIMPLE;

static double       param_max_outliers;
static double       param_max_deviation;
static unsigned int param_min_samples;
static unsigned int param_force_samples;
static uint64       param_seed;
static double       param_time_limit;
static int          param_threads;
static bool         param_write_sanity;
static bool         param_verify_sanity;
#ifdef CV_COLLECT_IMPL_DATA
static bool         param_collect_impl;
#endif
extern bool         test_ipp_check;

#ifdef HAVE_CUDA
static int          param_cuda_device;
#endif

#ifdef ANDROID
static int          param_affinity_mask;
static bool         log_power_checkpoints;

#include <sys/syscall.h>
#include <pthread.h>
static void setCurrentThreadAffinityMask(int mask)
{
    pid_t pid=gettid();
    int syscallres=syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
    if (syscallres)
    {
        int err=errno;
        err=err;//to avoid warnings about unused variables
        LOGE("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
    }
}
#endif

static double perf_stability_criteria = 0.03; // 3%

namespace {

class PerfEnvironment: public ::testing::Environment
{
public:
    void TearDown()
    {
        cv::setNumThreads(-1);
    }
};

} // namespace

static void randu(cv::Mat& m)
{
    const int bigValue = 0x00000FFF;
    if (m.depth() < CV_32F)
    {
        int minmax[] = {0, 256};
        cv::Mat mr = cv::Mat(m.rows, (int)(m.cols * m.elemSize()), CV_8U, m.ptr(), m.step[0]);
        cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1));
    }
    else if (m.depth() == CV_32F)
    {
        //float minmax[] = {-FLT_MAX, FLT_MAX};
        float minmax[] = {-bigValue, bigValue};
        cv::Mat mr = m.reshape(1);
        cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1));
    }
    else
    {
        //double minmax[] = {-DBL_MAX, DBL_MAX};
        double minmax[] = {-bigValue, bigValue};
        cv::Mat mr = m.reshape(1);
        cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1));
    }
}

/*****************************************************************************************\
*                       inner exception class for early termination
\*****************************************************************************************/

class PerfEarlyExitException: public cv::Exception {};

/*****************************************************************************************\
*                                   ::perf::Regression
\*****************************************************************************************/

Regression& Regression::instance()
{
    static Regression single;
    return single;
}

Regression& Regression::add(TestBase* test, const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
{
    if(test) test->setVerified();
    return instance()(name, array, eps, err);
}

Regression& Regression::addMoments(TestBase* test, const std::string& name, const cv::Moments& array, double eps, ERROR_TYPE err)
{
    int len = (int)sizeof(cv::Moments) / sizeof(double);
    cv::Mat m(1, len, CV_64F, (void*)&array);

    return Regression::add(test, name, m, eps, err);
}

Regression& Regression::addKeypoints(TestBase* test, const std::string& name, const std::vector<cv::KeyPoint>& array, double eps, ERROR_TYPE err)
{
    int len = (int)array.size();
    cv::Mat pt      (len, 1, CV_32FC2, len ? (void*)&array[0].pt : 0,       sizeof(cv::KeyPoint));
    cv::Mat size    (len, 1, CV_32FC1, len ? (void*)&array[0].size : 0,     sizeof(cv::KeyPoint));
    cv::Mat angle   (len, 1, CV_32FC1, len ? (void*)&array[0].angle : 0,    sizeof(cv::KeyPoint));
    cv::Mat response(len, 1, CV_32FC1, len ? (void*)&array[0].response : 0, sizeof(cv::KeyPoint));
    cv::Mat octave  (len, 1, CV_32SC1, len ? (void*)&array[0].octave : 0,   sizeof(cv::KeyPoint));
    cv::Mat class_id(len, 1, CV_32SC1, len ? (void*)&array[0].class_id : 0, sizeof(cv::KeyPoint));

    return Regression::add(test, name + "-pt",       pt,       eps, ERROR_ABSOLUTE)
                                (name + "-size",     size,     eps, ERROR_ABSOLUTE)
                                (name + "-angle",    angle,    eps, ERROR_ABSOLUTE)
                                (name + "-response", response, eps, err)
                                (name + "-octave",   octave,   eps, ERROR_ABSOLUTE)
                                (name + "-class_id", class_id, eps, ERROR_ABSOLUTE);
}

Regression& Regression::addMatches(TestBase* test, const std::string& name, const std::vector<cv::DMatch>& array, double eps, ERROR_TYPE err)
{
    int len = (int)array.size();
    cv::Mat queryIdx(len, 1, CV_32SC1, len ? (void*)&array[0].queryIdx : 0, sizeof(cv::DMatch));
    cv::Mat trainIdx(len, 1, CV_32SC1, len ? (void*)&array[0].trainIdx : 0, sizeof(cv::DMatch));
    cv::Mat imgIdx  (len, 1, CV_32SC1, len ? (void*)&array[0].imgIdx : 0,   sizeof(cv::DMatch));
    cv::Mat distance(len, 1, CV_32FC1, len ? (void*)&array[0].distance : 0, sizeof(cv::DMatch));

    return Regression::add(test, name + "-queryIdx", queryIdx, DBL_EPSILON, ERROR_ABSOLUTE)
                                (name + "-trainIdx", trainIdx, DBL_EPSILON, ERROR_ABSOLUTE)
                                (name + "-imgIdx",   imgIdx,   DBL_EPSILON, ERROR_ABSOLUTE)
                                (name + "-distance", distance, eps, err);
}

void Regression::Init(const std::string& testSuitName, const std::string& ext)
{
    instance().init(testSuitName, ext);
}

void Regression::init(const std::string& testSuitName, const std::string& ext)
{
    if (!storageInPath.empty())
    {
        LOGE("Subsequent initialization of Regression utility is not allowed.");
        return;
    }

    const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
    const char *path_separator = "/";

    if (data_path_dir)
    {
        int len = (int)strlen(data_path_dir)-1;
        if (len < 0) len = 0;
        std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
                + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator)
                + "perf"
                + path_separator;

        storageInPath = path_base + testSuitName + ext;
        storageOutPath = path_base + testSuitName;
    }
    else
    {
        storageInPath = testSuitName + ext;
        storageOutPath = testSuitName;
    }

    suiteName = testSuitName;

    try
    {
        if (storageIn.open(storageInPath, cv::FileStorage::READ))
        {
            rootIn = storageIn.root();
            if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz")
                storageOutPath += "_new";
            storageOutPath += ext;
        }
    }
    catch(cv::Exception&)
    {
        LOGE("Failed to open sanity data for reading: %s", storageInPath.c_str());
    }

    if(!storageIn.isOpened())
        storageOutPath = storageInPath;
}

Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random
{
}

Regression::~Regression()
{
    if (storageIn.isOpened())
        storageIn.release();
    if (storageOut.isOpened())
    {
        if (!currentTestNodeName.empty())
            storageOut << "}";
        storageOut.release();
    }
}

cv::FileStorage& Regression::write()
{
    if (!storageOut.isOpened() && !storageOutPath.empty())
    {
        int mode = (storageIn.isOpened() && storageInPath == storageOutPath)
                ? cv::FileStorage::APPEND : cv::FileStorage::WRITE;
        storageOut.open(storageOutPath, mode);
        if (!storageOut.isOpened())
        {
            LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str());
            storageOutPath.clear();
        }
        else if (mode == cv::FileStorage::WRITE && !rootIn.empty())
        {
            //TODO: write content of rootIn node into the storageOut
        }
    }
    return storageOut;
}

std::string Regression::getCurrentTestNodeName()
{
    const ::testing::TestInfo* const test_info =
      ::testing::UnitTest::GetInstance()->current_test_info();

    if (test_info == 0)
        return "undefined";

    std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name();
    size_t idx = nodename.find_first_of('/');
    if (idx != std::string::npos)
        nodename.erase(idx);

    const char* type_param = test_info->type_param();
    if (type_param != 0)
        (nodename += "--") += type_param;

    const char* value_param = test_info->value_param();
    if (value_param != 0)
        (nodename += "--") += value_param;

    for(size_t i = 0; i < nodename.length(); ++i)
        if (!isalnum(nodename[i]) && '_' != nodename[i])
            nodename[i] = '-';

    return nodename;
}

bool Regression::isVector(cv::InputArray a)
{
    return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR ||
           a.kind() == cv::_InputArray::STD_VECTOR_UMAT;
}

double Regression::getElem(cv::Mat& m, int y, int x, int cn)
{
    switch (m.depth())
    {
    case CV_8U: return *(m.ptr<unsigned char>(y, x) + cn);
    case CV_8S: return *(m.ptr<signed char>(y, x) + cn);
    case CV_16U: return *(m.ptr<unsigned short>(y, x) + cn);
    case CV_16S: return *(m.ptr<signed short>(y, x) + cn);
    case CV_32S: return *(m.ptr<signed int>(y, x) + cn);
    case CV_32F: return *(m.ptr<float>(y, x) + cn);
    case CV_64F: return *(m.ptr<double>(y, x) + cn);
    default: return 0;
    }
}

void Regression::write(cv::Mat m)
{
    if (!m.empty() && m.dims < 2) return;

    double min, max;
    cv::minMaxIdx(m, &min, &max);
    write() << "min" << min << "max" << max;

    write() << "last" << "{" << "x" << m.size.p[1] - 1 << "y" << m.size.p[0] - 1
        << "val" << getElem(m, m.size.p[0] - 1, m.size.p[1] - 1, m.channels() - 1) << "}";

    int x, y, cn;
    x = regRNG.uniform(0, m.size.p[1]);
    y = regRNG.uniform(0, m.size.p[0]);
    cn = regRNG.uniform(0, m.channels());
    write() << "rng1" << "{" << "x" << x << "y" << y;
    if(cn > 0) write() << "cn" << cn;
    write() << "val" << getElem(m, y, x, cn) << "}";

    x = regRNG.uniform(0, m.size.p[1]);
    y = regRNG.uniform(0, m.size.p[0]);
    cn = regRNG.uniform(0, m.channels());
    write() << "rng2" << "{" << "x" << x << "y" << y;
    if (cn > 0) write() << "cn" << cn;
    write() << "val" << getElem(m, y, x, cn) << "}";
}

void Regression::verify(cv::FileNode node, cv::Mat actual, double eps, std::string argname, ERROR_TYPE err)
{
    if (!actual.empty() && actual.dims < 2) return;

    double expect_min = (double)node["min"];
    double expect_max = (double)node["max"];

    if (err == ERROR_RELATIVE)
        eps *= std::max(std::abs(expect_min), std::abs(expect_max));

    double actual_min, actual_max;
    cv::minMaxIdx(actual, &actual_min, &actual_max);

    ASSERT_NEAR(expect_min, actual_min, eps)
            << argname << " has unexpected minimal value" << std::endl;
    ASSERT_NEAR(expect_max, actual_max, eps)
            << argname << " has unexpected maximal value" << std::endl;

    cv::FileNode last = node["last"];
    double actual_last = getElem(actual, actual.size.p[0] - 1, actual.size.p[1] - 1, actual.channels() - 1);
    int expect_cols = (int)last["x"] + 1;
    int expect_rows = (int)last["y"] + 1;
    ASSERT_EQ(expect_cols, actual.size.p[1])
            << argname << " has unexpected number of columns" << std::endl;
    ASSERT_EQ(expect_rows, actual.size.p[0])
            << argname << " has unexpected number of rows" << std::endl;

    double expect_last = (double)last["val"];
    ASSERT_NEAR(expect_last, actual_last, eps)
            << argname << " has unexpected value of the last element" << std::endl;

    cv::FileNode rng1 = node["rng1"];
    int x1 = rng1["x"];
    int y1 = rng1["y"];
    int cn1 = rng1["cn"];

    double expect_rng1 = (double)rng1["val"];
    // it is safe to use x1 and y1 without checks here because we have already
    // verified that mat size is the same as recorded
    double actual_rng1 = getElem(actual, y1, x1, cn1);

    ASSERT_NEAR(expect_rng1, actual_rng1, eps)
            << argname << " has unexpected value of the ["<< x1 << ":" << y1 << ":" << cn1 <<"] element" << std::endl;

    cv::FileNode rng2 = node["rng2"];
    int x2 = rng2["x"];
    int y2 = rng2["y"];
    int cn2 = rng2["cn"];

    double expect_rng2 = (double)rng2["val"];
    double actual_rng2 = getElem(actual, y2, x2, cn2);

    ASSERT_NEAR(expect_rng2, actual_rng2, eps)
            << argname << " has unexpected value of the ["<< x2 << ":" << y2 << ":" << cn2 <<"] element" << std::endl;
}

void Regression::write(cv::InputArray array)
{
    write() << "kind" << array.kind();
    write() << "type" << array.type();
    if (isVector(array))
    {
        int total = (int)array.total();
        int idx = regRNG.uniform(0, total);
        write() << "len" << total;
        write() << "idx" << idx;

        cv::Mat m = array.getMat(idx);

        if (m.total() * m.channels() < 26) //5x5 or smaller
            write() << "val" << m;
        else
            write(m);
    }
    else
    {
        if (array.total() * array.channels() < 26) //5x5 or smaller
            write() << "val" << array.getMat();
        else
            write(array.getMat());
    }
}

static int countViolations(const cv::Mat& expected, const cv::Mat& actual, const cv::Mat& diff, double eps, double* max_violation = 0, double* max_allowed = 0)
{
    cv::Mat diff64f;
    diff.reshape(1).convertTo(diff64f, CV_64F);

    cv::Mat expected_abs = cv::abs(expected.reshape(1));
    cv::Mat actual_abs = cv::abs(actual.reshape(1));
    cv::Mat maximum, mask;
    cv::max(expected_abs, actual_abs, maximum);
    cv::multiply(maximum, cv::Vec<double, 1>(eps), maximum, CV_64F);
    cv::compare(diff64f, maximum, mask, cv::CMP_GT);

    int v = cv::countNonZero(mask);

    if (v > 0 && max_violation != 0 && max_allowed != 0)
    {
        int loc[10] = {0};
        cv::minMaxIdx(maximum, 0, max_allowed, 0, loc, mask);
        *max_violation = diff64f.at<double>(loc[0], loc[1]);
    }

    return v;
}

void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERROR_TYPE err)
{
    int expected_kind = (int)node["kind"];
    int expected_type = (int)node["type"];
    ASSERT_EQ(expected_kind, array.kind()) << "  Argument \"" << node.name() << "\" has unexpected kind";
    ASSERT_EQ(expected_type, array.type()) << "  Argument \"" << node.name() << "\" has unexpected type";

    cv::FileNode valnode = node["val"];
    if (isVector(array))
    {
        int expected_length = (int)node["len"];
        ASSERT_EQ(expected_length, (int)array.total()) << "  Vector \"" << node.name() << "\" has unexpected length";
        int idx = node["idx"];

        cv::Mat actual = array.getMat(idx);

        if (valnode.isNone())
        {
            ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels())
                    << "  \"" << node.name() << "[" <<  idx << "]\" has unexpected number of elements";
            verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx), err);
        }
        else
        {
            cv::Mat expected;
            valnode >> expected;

            if(expected.empty())
            {
                ASSERT_TRUE(actual.empty())
                    << "  expected empty " << node.name() << "[" <<  idx<< "]";
            }
            else
            {
                ASSERT_EQ(expected.size(), actual.size())
                        << "  " << node.name() << "[" <<  idx<< "] has unexpected size";

                cv::Mat diff;
                cv::absdiff(expected, actual, diff);

                if (err == ERROR_ABSOLUTE)
                {
                    if (!cv::checkRange(diff, true, 0, 0, eps))
                    {
                        if(expected.total() * expected.channels() < 12)
                            std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;

                        double max;
                        cv::minMaxIdx(diff.reshape(1), 0, &max);

                        FAIL() << "  Absolute difference (=" << max << ") between argument \""
                               << node.name() << "[" <<  idx << "]\" and expected value is greater than " << eps;
                    }
                }
                else if (err == ERROR_RELATIVE)
                {
                    double maxv, maxa;
                    int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
                    if (violations > 0)
                    {
                        if(expected.total() * expected.channels() < 12)
                            std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;

                        FAIL() << "  Relative difference (" << maxv << " of " << maxa << " allowed) between argument \""
                               << node.name() << "[" <<  idx << "]\" and expected value is greater than " << eps << " in " << violations << " points";
                    }
                }
            }
        }
    }
    else
    {
        if (valnode.isNone())
        {
            ASSERT_LE((size_t)26, array.total() * (size_t)array.channels())
                    << "  Argument \"" << node.name() << "\" has unexpected number of elements";
            verify(node, array.getMat(), eps, "Argument \"" + node.name() + "\"", err);
        }
        else
        {
            cv::Mat expected;
            valnode >> expected;
            cv::Mat actual = array.getMat();

            if(expected.empty())
            {
                ASSERT_TRUE(actual.empty())
                    << "  expected empty " << node.name();
            }
            else
            {
                ASSERT_EQ(expected.size(), actual.size())
                        << "  Argument \"" << node.name() << "\" has unexpected size";

                cv::Mat diff;
                cv::absdiff(expected, actual, diff);

                if (err == ERROR_ABSOLUTE)
                {
                    if (!cv::checkRange(diff, true, 0, 0, eps))
                    {
                        if(expected.total() * expected.channels() < 12)
                            std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;

                        double max;
                        cv::minMaxIdx(diff.reshape(1), 0, &max);

                        FAIL() << "  Difference (=" << max << ") between argument1 \"" << node.name()
                               << "\" and expected value is greater than " << eps;
                    }
                }
                else if (err == ERROR_RELATIVE)
                {
                    double maxv, maxa;
                    int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
                    if (violations > 0)
                    {
                        if(expected.total() * expected.channels() < 12)
                            std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;

                        FAIL() << "  Relative difference (" << maxv << " of " << maxa << " allowed) between argument \"" << node.name()
                               << "\" and expected value is greater than " << eps << " in " << violations << " points";
                    }
                }
            }
        }
    }
}

Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
{
    // exit if current test is already failed
    if(::testing::UnitTest::GetInstance()->current_test_info()->result()->Failed()) return *this;

    if(!array.empty() && array.depth() == CV_USRTYPE1)
    {
        ADD_FAILURE() << "  Can not check regression for CV_USRTYPE1 data type for " << name;
        return *this;
    }

    std::string nodename = getCurrentTestNodeName();

    cv::FileNode n = rootIn[nodename];
    if(n.isNone())
    {
        if(param_write_sanity)
        {
            if (nodename != currentTestNodeName)
            {
                if (!currentTestNodeName.empty())
                    write() << "}";
                currentTestNodeName = nodename;

                write() << nodename << "{";
            }
            // TODO: verify that name is alphanumeric, current error message is useless
            write() << name << "{";
            write(array);
            write() << "}";
        }
        else if(param_verify_sanity)
        {
            ADD_FAILURE() << "  No regression data for " << name << " argument";
        }
    }
    else
    {
        cv::FileNode this_arg = n[name];
        if (!this_arg.isMap())
            ADD_FAILURE() << "  No regression data for " << name << " argument";
        else
            verify(this_arg, array, eps, err);
    }

    return *this;
}


/*****************************************************************************************\
*                                ::perf::performance_metrics
\*****************************************************************************************/
performance_metrics::performance_metrics()
{
    clear();
}

void performance_metrics::clear()
{
    bytesIn = 0;
    bytesOut = 0;
    samples = 0;
    outliers = 0;
    gmean = 0;
    gstddev = 0;
    mean = 0;
    stddev = 0;
    median = 0;
    min = 0;
    frequency = 0;
    terminationReason = TERM_UNKNOWN;
}

/*****************************************************************************************\
*                                   Performance validation results
\*****************************************************************************************/

static bool perf_validation_enabled = false;

static std::string perf_validation_results_directory;
static std::map<std::string, float> perf_validation_results;
static std::string perf_validation_results_outfile;

static double perf_validation_criteria = 0.03; // 3 %
static double perf_validation_time_threshold_ms = 0.1;
static int perf_validation_idle_delay_ms = 3000; // 3 sec

static void loadPerfValidationResults(const std::string& fileName)
{
    perf_validation_results.clear();
    std::ifstream infile(fileName.c_str());
    while (!infile.eof())
    {
        std::string name;
        float value = 0;
        if (!(infile >> value))
        {
            if (infile.eof())
                break; // it is OK
            std::cout << "ERROR: Can't load performance validation results from " << fileName << "!" << std::endl;
            return;
        }
        infile.ignore(1);
        if (!(std::getline(infile, name)))
        {
            std::cout << "ERROR: Can't load performance validation results from " << fileName << "!" << std::endl;
            return;
        }
        if (!name.empty() && name[name.size() - 1] == '\r') // CRLF processing on Linux
            name.resize(name.size() - 1);
        perf_validation_results[name] = value;
    }
    std::cout << "Performance validation results loaded from " << fileName << " (" << perf_validation_results.size() << " entries)" << std::endl;
}

static void savePerfValidationResult(const std::string& name, float value)
{
    perf_validation_results[name] = value;
}

static void savePerfValidationResults()
{
    if (!perf_validation_results_outfile.empty())
    {
        std::ofstream outfile((perf_validation_results_directory + perf_validation_results_outfile).c_str());
        std::map<std::string, float>::const_iterator i;
        for (i = perf_validation_results.begin(); i != perf_validation_results.end(); ++i)
        {
            outfile << i->second << ';';
            outfile << i->first << std::endl;
        }
        outfile.close();
        std::cout << "Performance validation results saved (" << perf_validation_results.size() << " entries)" << std::endl;
    }
}

class PerfValidationEnvironment : public ::testing::Environment
{
public:
    virtual ~PerfValidationEnvironment() {}
    virtual void SetUp() {}

    virtual void TearDown()
    {
        savePerfValidationResults();
    }
};



/*****************************************************************************************\
*                                   ::perf::TestBase
\*****************************************************************************************/


void TestBase::Init(int argc, const char* const argv[])
{
    std::vector<std::string> plain_only;
    plain_only.push_back("plain");
    TestBase::Init(plain_only, argc, argv);
}

void TestBase::Init(const std::vector<std::string> & availableImpls,
                 int argc, const char* const argv[])
{
    available_impls = availableImpls;

    const std::string command_line_keys =
        "{   perf_max_outliers           |8        |percent of allowed outliers}"
        "{   perf_min_samples            |10       |minimal required numer of samples}"
        "{   perf_force_samples          |100      |force set maximum number of samples for all tests}"
        "{   perf_seed                   |809564   |seed for random numbers generator}"
        "{   perf_threads                |-1       |the number of worker threads, if parallel execution is enabled}"
        "{   perf_write_sanity           |false    |create new records for sanity checks}"
        "{   perf_verify_sanity          |false    |fail tests having no regression data for sanity checks}"
        "{   perf_impl                   |" + available_impls[0] +
                                                  "|the implementation variant of functions under test}"
        "{   perf_list_impls             |false    |list available implementation variants and exit}"
        "{   perf_run_cpu                |false    |deprecated, equivalent to --perf_impl=plain}"
        "{   perf_strategy               |default  |specifies performance measuring strategy: default, base or simple (weak restrictions)}"
        "{   perf_read_validation_results |        |specifies file name with performance results from previous run}"
        "{   perf_write_validation_results |       |specifies file name to write performance validation results}"
#ifdef ANDROID
        "{   perf_time_limit             |6.0      |default time limit for a single test (in seconds)}"
        "{   perf_affinity_mask          |0        |set affinity mask for the main thread}"
        "{   perf_log_power_checkpoints  |         |additional xml logging for power measurement}"
#else
        "{   perf_time_limit             |3.0      |default time limit for a single test (in seconds)}"
#endif
        "{   perf_max_deviation          |1.0      |}"
#ifdef HAVE_IPP
        "{   perf_ipp_check              |false    |check whether IPP works without failures}"
#endif
#ifdef CV_COLLECT_IMPL_DATA
        "{   perf_collect_impl           |false    |collect info about executed implementations}"
#endif
        "{   help h                      |false    |print help info}"
#ifdef HAVE_CUDA
        "{   perf_cuda_device            |0        |run CUDA test suite onto specific CUDA capable device}"
        "{   perf_cuda_info_only         |false    |print an information about system and an available CUDA devices and then exit.}"
#endif
    ;

    cv::CommandLineParser args(argc, argv, command_line_keys);
    if (args.has("help"))
    {
        args.printMessage();
        return;
    }

    ::testing::AddGlobalTestEnvironment(new PerfEnvironment);

    param_impl          = args.has("perf_run_cpu") ? "plain" : args.get<std::string>("perf_impl");
    std::string perf_strategy = args.get<std::string>("perf_strategy");
    if (perf_strategy == "default")
    {
        // nothing
    }
    else if (perf_strategy == "base")
    {
        strategyForce = PERF_STRATEGY_BASE;
    }
    else if (perf_strategy == "simple")
    {
        strategyForce = PERF_STRATEGY_SIMPLE;
    }
    else
    {
        printf("No such strategy: %s\n", perf_strategy.c_str());
        exit(1);
    }
    param_max_outliers  = std::min(100., std::max(0., args.get<double>("perf_max_outliers")));
    param_min_samples   = std::max(1u, args.get<unsigned int>("perf_min_samples"));
    param_max_deviation = std::max(0., args.get<double>("perf_max_deviation"));
    param_seed          = args.get<unsigned int>("perf_seed");
    param_time_limit    = std::max(0., args.get<double>("perf_time_limit"));
    param_force_samples = args.get<unsigned int>("perf_force_samples");
    param_write_sanity  = args.has("perf_write_sanity");
    param_verify_sanity = args.has("perf_verify_sanity");
    test_ipp_check      = !args.has("perf_ipp_check") ? getenv("OPENCV_IPP_CHECK") != NULL : true;
    param_threads       = args.get<int>("perf_threads");
#ifdef CV_COLLECT_IMPL_DATA
    param_collect_impl  = args.has("perf_collect_impl");
#endif
#ifdef ANDROID
    param_affinity_mask   = args.get<int>("perf_affinity_mask");
    log_power_checkpoints = args.has("perf_log_power_checkpoints");
#endif

    bool param_list_impls = args.has("perf_list_impls");

    if (param_list_impls)
    {
        fputs("Available implementation variants:", stdout);
        for (size_t i = 0; i < available_impls.size(); ++i) {
            putchar(' ');
            fputs(available_impls[i].c_str(), stdout);
        }
        putchar('\n');
        exit(0);
    }

    if (std::find(available_impls.begin(), available_impls.end(), param_impl) == available_impls.end())
    {
        printf("No such implementation: %s\n", param_impl.c_str());
        exit(1);
    }

#ifdef CV_COLLECT_IMPL_DATA
    if(param_collect_impl)
        cv::setUseCollection(1);
    else
        cv::setUseCollection(0);
#endif

#ifdef HAVE_CUDA

    bool printOnly        = args.has("perf_cuda_info_only");

    if (printOnly)
        exit(0);
#endif

    if (available_impls.size() > 1)
        printf("[----------]\n[   INFO   ] \tImplementation variant: %s.\n[----------]\n", param_impl.c_str()), fflush(stdout);

#ifdef HAVE_CUDA

    param_cuda_device      = std::max(0, std::min(cv::cuda::getCudaEnabledDeviceCount(), args.get<int>("perf_cuda_device")));

    if (param_impl == "cuda")
    {
        cv::cuda::DeviceInfo info(param_cuda_device);
        if (!info.isCompatible())
        {
            printf("[----------]\n[ FAILURE  ] \tDevice %s is NOT compatible with current CUDA module build.\n[----------]\n", info.name()), fflush(stdout);
            exit(-1);
        }

        cv::cuda::setDevice(param_cuda_device);

        printf("[----------]\n[ GPU INFO ] \tRun test suite on %s GPU.\n[----------]\n", info.name()), fflush(stdout);
    }
#endif

    {
        const char* path = getenv("OPENCV_PERF_VALIDATION_DIR");
        if (path)
            perf_validation_results_directory = path;
    }

    std::string fileName_perf_validation_results_src = args.get<std::string>("perf_read_validation_results");
    if (!fileName_perf_validation_results_src.empty())
    {
        perf_validation_enabled = true;
        loadPerfValidationResults(perf_validation_results_directory + fileName_perf_validation_results_src);
    }

    perf_validation_results_outfile = args.get<std::string>("perf_write_validation_results");
    if (!perf_validation_results_outfile.empty())
    {
        perf_validation_enabled = true;
        ::testing::AddGlobalTestEnvironment(new PerfValidationEnvironment());
    }

    if (!args.check())
    {
        args.printErrors();
        return;
    }

    timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency());
    iterationsLimitDefault = param_force_samples == 0 ? (unsigned)(-1) : param_force_samples;
    _timeadjustment = _calibrate();
}

void TestBase::RecordRunParameters()
{
    ::testing::Test::RecordProperty("cv_implementation", param_impl);
    ::testing::Test::RecordProperty("cv_num_threads", param_threads);

#ifdef HAVE_CUDA
    if (param_impl == "cuda")
    {
        cv::cuda::DeviceInfo info(param_cuda_device);
        ::testing::Test::RecordProperty("cv_cuda_gpu", info.name());
    }
#endif
}

std::string TestBase::getSelectedImpl()
{
    return param_impl;
}

enum PERF_STRATEGY TestBase::setModulePerformanceStrategy(enum PERF_STRATEGY strategy)
{
    enum PERF_STRATEGY ret = strategyModule;
    strategyModule = strategy;
    return ret;
}

enum PERF_STRATEGY TestBase::getCurrentModulePerformanceStrategy()
{
    return strategyForce == PERF_STRATEGY_DEFAULT ? strategyModule : strategyForce;
}


int64 TestBase::_calibrate()
{
    class _helper : public ::perf::TestBase
    {
        public:
        performance_metrics& getMetrics() { return calcMetrics(); }
        virtual void TestBody() {}
        virtual void PerfTestBody()
        {
            //the whole system warmup
            SetUp();
            cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1));
            cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2));
            declare.time(30);
            double s = 0;
            for(declare.iterations(20); startTimer(), next(); stopTimer())
                s+=a.dot(b);
            declare.time(s);

            //self calibration
            SetUp();
            for(declare.iterations(1000); startTimer(), next(); stopTimer()){}
        }
    };

    _timeadjustment = 0;
    _helper h;
    h.PerfTestBody();
    double compensation = h.getMetrics().min;
    if (getCurrentModulePerformanceStrategy() == PERF_STRATEGY_SIMPLE)
    {
        CV_Assert(compensation < 0.01 * cv::getTickFrequency());
        compensation = 0.0f; // simple strategy doesn't require any compensation
    }
    LOGD("Time compensation is %.0f", compensation);
    return (int64)compensation;
}

#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable:4355)  // 'this' : used in base member initializer list
#endif
TestBase::TestBase(): testStrategy(PERF_STRATEGY_DEFAULT), declare(this)
{
    lastTime = totalTime = timeLimit = 0;
    nIters = currentIter = runsPerIteration = 0;
    minIters = param_min_samples;
    verified = false;
    perfValidationStage = 0;
}
#ifdef _MSC_VER
# pragma warning(pop)
#endif


void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, WarmUpType wtype)
{
    if (!a.empty())
    {
        sizes.push_back(std::pair<int, cv::Size>(getSizeInBytes(a), getSize(a)));
        warmup(a, wtype);
    }
    else if (a.kind() != cv::_InputArray::NONE)
        ADD_FAILURE() << "  Uninitialized input/output parameters are not allowed for performance tests";
}

void TestBase::warmup(cv::InputOutputArray a, WarmUpType wtype)
{
    if (a.empty())
        return;
    else if (a.isUMat())
    {
        if (wtype == WARMUP_RNG || wtype == WARMUP_WRITE)
        {
            int depth = a.depth();
            if (depth == CV_8U)
                cv::randu(a, 0, 256);
            else if (depth == CV_8S)
                cv::randu(a, -128, 128);
            else if (depth == CV_16U)
                cv::randu(a, 0, 1024);
            else if (depth == CV_32F || depth == CV_64F)
                cv::randu(a, -1.0, 1.0);
            else if (depth == CV_16S || depth == CV_32S)
                cv::randu(a, -4096, 4096);
            else
                CV_Error(cv::Error::StsUnsupportedFormat, "Unsupported format");
        }
        return;
    }
    else if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
        warmup_impl(a.getMat(), wtype);
    else
    {
        size_t total = a.total();
        for (size_t i = 0; i < total; ++i)
            warmup_impl(a.getMat((int)i), wtype);
    }
}

int TestBase::getSizeInBytes(cv::InputArray a)
{
    if (a.empty()) return 0;
    int total = (int)a.total();
    if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
        return total * CV_ELEM_SIZE(a.type());

    int size = 0;
    for (int i = 0; i < total; ++i)
        size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i));

    return size;
}

cv::Size TestBase::getSize(cv::InputArray a)
{
    if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
        return a.size();
    return cv::Size();
}

PERF_STRATEGY TestBase::getCurrentPerformanceStrategy() const
{
    if (strategyForce == PERF_STRATEGY_DEFAULT)
        return (testStrategy == PERF_STRATEGY_DEFAULT) ? strategyModule : testStrategy;
    else
        return strategyForce;
}

bool TestBase::next()
{
    static int64 lastActivityPrintTime = 0;

    if (currentIter != (unsigned int)-1)
    {
        if (currentIter + 1 != times.size())
            ADD_FAILURE() << "  next() is called before stopTimer()";
    }
    else
    {
        lastActivityPrintTime = 0;
        metrics.clear();
    }

    cv::theRNG().state = param_seed; //this rng should generate same numbers for each run
    ++currentIter;

    bool has_next = false;

    do {
        assert(currentIter == times.size());
        if (currentIter == 0)
        {
            has_next = true;
            break;
        }

        if (getCurrentPerformanceStrategy() == PERF_STRATEGY_BASE)
        {
            has_next = currentIter < nIters && totalTime < timeLimit;
        }
        else
        {
            assert(getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE);
            if (totalTime - lastActivityPrintTime >= cv::getTickFrequency() * 10)
            {
                std::cout << '.' << std::endl;
                lastActivityPrintTime = totalTime;
            }
            if (currentIter >= nIters)
            {
                has_next = false;
                break;
            }
            if (currentIter < minIters)
            {
                has_next = true;
                break;
            }

            calcMetrics();

            if (fabs(metrics.mean) > 1e-6)
                has_next = metrics.stddev > perf_stability_criteria * fabs(metrics.mean);
            else
                has_next = true;
        }
    } while (false);

    if (perf_validation_enabled && !has_next)
    {
        calcMetrics();
        double median_ms = metrics.median * 1000.0f / metrics.frequency;

        const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
        std::string name = (test_info == 0) ? "" :
                std::string(test_info->test_case_name()) + "--" + test_info->name();

        if (!perf_validation_results.empty() && !name.empty())
        {
            std::map<std::string, float>::iterator i = perf_validation_results.find(name);
            bool isSame = false;
            bool found = false;
            bool grow = false;
            if (i != perf_validation_results.end())
            {
                found = true;
                double prev_result = i->second;
                grow = median_ms > prev_result;
                isSame = fabs(median_ms - prev_result) <= perf_validation_criteria * fabs(median_ms);
                if (!isSame)
                {
                    if (perfValidationStage == 0)
                    {
                        printf("Performance is changed (samples = %d, median):\n    %.2f ms (current)\n    %.2f ms (previous)\n", (int)times.size(), median_ms, prev_result);
                    }
                }
            }
            else
            {
                if (perfValidationStage == 0)
                    printf("New performance result is detected\n");
            }
            if (!isSame)
            {
                if (perfValidationStage < 2)
                {
                    if (perfValidationStage == 0 && currentIter <= minIters * 3 && currentIter < nIters)
                    {
                        unsigned int new_minIters = std::max(minIters * 5, currentIter * 3);
                        printf("Increase minIters from %u to %u\n", minIters, new_minIters);
                        minIters = new_minIters;
                        has_next = true;
                        perfValidationStage++;
                    }
                    else if (found && currentIter >= nIters &&
                            median_ms > perf_validation_time_threshold_ms &&
                            (grow || metrics.stddev > perf_stability_criteria * fabs(metrics.mean)))
                    {
                        printf("Performance is unstable, it may be a result of overheat problems\n");
                        printf("Idle delay for %d ms... \n", perf_validation_idle_delay_ms);
#if defined WIN32 || defined _WIN32 || defined WIN64 || defined _WIN64
                        Sleep(perf_validation_idle_delay_ms);
#else
                        usleep(perf_validation_idle_delay_ms * 1000);
#endif
                        has_next = true;
                        minIters = std::min(minIters * 5, nIters);
                        // reset collected samples
                        currentIter = 0;
                        times.clear();
                        metrics.clear();
                        perfValidationStage += 2;
                    }
                    if (!has_next)
                    {
                        printf("Assume that current result is valid\n");
                    }
                }
                else
                {
                    printf("Re-measured performance result: %.2f ms\n", median_ms);
                }
            }
        }

        if (!has_next && !name.empty())
        {
            savePerfValidationResult(name, (float)median_ms);
        }
    }

#ifdef ANDROID
    if (log_power_checkpoints)
    {
        timeval tim;
        gettimeofday(&tim, NULL);
        unsigned long long t1 = tim.tv_sec * 1000LLU + (unsigned long long)(tim.tv_usec / 1000.f);

        if (currentIter == 1) RecordProperty("test_start", cv::format("%llu",t1).c_str());
        if (!has_next) RecordProperty("test_complete", cv::format("%llu",t1).c_str());
    }
#endif

    if (has_next)
        startTimer(); // really we should measure activity from this moment, so reset start time
    return has_next;
}

void TestBase::warmup_impl(cv::Mat m, WarmUpType wtype)
{
    switch(wtype)
    {
    case WARMUP_READ:
        cv::sum(m.reshape(1));
        return;
    case WARMUP_WRITE:
        m.reshape(1).setTo(cv::Scalar::all(0));
        return;
    case WARMUP_RNG:
        randu(m);
        return;
    default:
        return;
    }
}

unsigned int TestBase::getTotalInputSize() const
{
    unsigned int res = 0;
    for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i)
        res += i->first;
    return res;
}

unsigned int TestBase::getTotalOutputSize() const
{
    unsigned int res = 0;
    for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i)
        res += i->first;
    return res;
}

void TestBase::startTimer()
{
    lastTime = cv::getTickCount();
}

void TestBase::stopTimer()
{
    int64 time = cv::getTickCount();
    if (lastTime == 0)
        ADD_FAILURE() << "  stopTimer() is called before startTimer()/next()";
    lastTime = time - lastTime;
    totalTime += lastTime;
    lastTime -= _timeadjustment;
    if (lastTime < 0) lastTime = 0;
    times.push_back(lastTime);
    lastTime = 0;
}

performance_metrics& TestBase::calcMetrics()
{
    CV_Assert(metrics.samples <= (unsigned int)currentIter);
    if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0)
        return metrics;

    metrics.bytesIn = getTotalInputSize();
    metrics.bytesOut = getTotalOutputSize();
    metrics.frequency = cv::getTickFrequency();
    metrics.samples = (unsigned int)times.size();
    metrics.outliers = 0;

    if (metrics.terminationReason != performance_metrics::TERM_INTERRUPT && metrics.terminationReason != performance_metrics::TERM_EXCEPTION)
    {
        if (currentIter == nIters)
            metrics.terminationReason = performance_metrics::TERM_ITERATIONS;
        else if (totalTime >= timeLimit)
            metrics.terminationReason = performance_metrics::TERM_TIME;
        else
            metrics.terminationReason = performance_metrics::TERM_UNKNOWN;
    }

    std::sort(times.begin(), times.end());

    TimeVector::const_iterator start = times.begin();
    TimeVector::const_iterator end = times.end();

    if (getCurrentPerformanceStrategy() == PERF_STRATEGY_BASE)
    {
        //estimate mean and stddev for log(time)
        double gmean = 0;
        double gstddev = 0;
        int n = 0;
        for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i)
        {
            double x = static_cast<double>(*i)/runsPerIteration;
            if (x < DBL_EPSILON) continue;
            double lx = log(x);

            ++n;
            double delta = lx - gmean;
            gmean += delta / n;
            gstddev += delta * (lx - gmean);
        }

        gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0;

        //filter outliers assuming log-normal distribution
        //http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements
        if (gstddev > DBL_EPSILON)
        {
            double minout = exp(gmean - 3 * gstddev) * runsPerIteration;
            double maxout = exp(gmean + 3 * gstddev) * runsPerIteration;
            while(*start < minout) ++start, ++metrics.outliers;
            do --end, ++metrics.outliers; while(*end > maxout);
            ++end, --metrics.outliers;
        }
    }
    else if (getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE)
    {
        metrics.outliers = static_cast<int>(times.size() * param_max_outliers / 100);
        for (unsigned int i = 0; i < metrics.outliers; i++)
            --end;
    }
    else
    {
        assert(false);
    }

    int offset = static_cast<int>(start - times.begin());

    metrics.min = static_cast<double>(*start)/runsPerIteration;
    //calc final metrics
    unsigned int n = 0;
    double gmean = 0;
    double gstddev = 0;
    double mean = 0;
    double stddev = 0;
    unsigned int m = 0;
    for(; start != end; ++start)
    {
        double x = static_cast<double>(*start)/runsPerIteration;
        if (x > DBL_EPSILON)
        {
            double lx = log(x);
            ++m;
            double gdelta = lx - gmean;
            gmean += gdelta / m;
            gstddev += gdelta * (lx - gmean);
        }
        ++n;
        double delta = x - mean;
        mean += delta / n;
        stddev += delta * (x - mean);
    }

    metrics.mean = mean;
    metrics.gmean = exp(gmean);
    metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0;
    metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0;
    metrics.median = (n % 2
            ? (double)times[offset + n / 2]
            : 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1])
            ) / runsPerIteration;

    return metrics;
}

void TestBase::validateMetrics()
{
    performance_metrics& m = calcMetrics();

    if (HasFailure()) return;

    ASSERT_GE(m.samples, 1u)
      << "  No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests.";

    if (getCurrentPerformanceStrategy() == PERF_STRATEGY_BASE)
    {
        EXPECT_GE(m.samples, param_min_samples)
          << "  Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements.";

        if (m.gstddev > DBL_EPSILON)
        {
            EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation))
              << "  Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is greater than measured time interval).";
        }

        EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u))
          << "  Test results are not reliable (too many outliers).";
    }
    else if (getCurrentPerformanceStrategy() == PERF_STRATEGY_SIMPLE)
    {
        double mean = metrics.mean * 1000.0f / metrics.frequency;
        double median = metrics.median * 1000.0f / metrics.frequency;
        double stddev = metrics.stddev * 1000.0f / metrics.frequency;
        double percents = stddev / mean * 100.f;
        printf("[ PERFSTAT ]    (samples = %d, mean = %.2f, median = %.2f, stddev = %.2f (%.1f%%))\n", (int)metrics.samples, mean, median, stddev, percents);
    }
    else
    {
        assert(false);
    }
}

void TestBase::reportMetrics(bool toJUnitXML)
{
    performance_metrics& m = calcMetrics();

    if (m.terminationReason == performance_metrics::TERM_SKIP_TEST)
    {
        if (toJUnitXML)
        {
            RecordProperty("custom_status", "skipped");
        }
    }
    else if (toJUnitXML)
    {
        RecordProperty("bytesIn", (int)m.bytesIn);
        RecordProperty("bytesOut", (int)m.bytesOut);
        RecordProperty("term", m.terminationReason);
        RecordProperty("samples", (int)m.samples);
        RecordProperty("outliers", (int)m.outliers);
        RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str());
        RecordProperty("min", cv::format("%.0f", m.min).c_str());
        RecordProperty("median", cv::format("%.0f", m.median).c_str());
        RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str());
        RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str());
        RecordProperty("mean", cv::format("%.0f", m.mean).c_str());
        RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str());
#ifdef CV_COLLECT_IMPL_DATA
        if(param_collect_impl)
        {
            RecordProperty("impl_ipp", (int)(implConf.ipp || implConf.icv));
            RecordProperty("impl_ocl", (int)implConf.ocl);
            RecordProperty("impl_plain", (int)implConf.plain);

            std::string rec_line;
            std::vector<cv::String> rec;
            rec_line.clear();
            rec = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT);
            for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
            rec = implConf.GetCallsForImpl(CV_IMPL_IPP);
            for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
            RecordProperty("impl_rec_ipp", rec_line.c_str());

            rec_line.clear();
            rec = implConf.GetCallsForImpl(CV_IMPL_OCL);
            for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
            RecordProperty("impl_rec_ocl", rec_line.c_str());
        }
#endif
    }
    else
    {
        const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
        const char* type_param = test_info->type_param();
        const char* value_param = test_info->value_param();

#if defined(ANDROID) && defined(USE_ANDROID_LOGGING)
        LOGD("[ FAILED   ] %s.%s", test_info->test_case_name(), test_info->name());
#endif

        if (type_param)  LOGD("type      = %11s", type_param);
        if (value_param) LOGD("params    = %11s", value_param);

        switch (m.terminationReason)
        {
        case performance_metrics::TERM_ITERATIONS:
            LOGD("termination reason:  reached maximum number of iterations");
            break;
        case performance_metrics::TERM_TIME:
            LOGD("termination reason:  reached time limit");
            break;
        case performance_metrics::TERM_INTERRUPT:
            LOGD("termination reason:  aborted by the performance testing framework");
            break;
        case performance_metrics::TERM_EXCEPTION:
            LOGD("termination reason:  unhandled exception");
            break;
        case performance_metrics::TERM_UNKNOWN:
        default:
            LOGD("termination reason:  unknown");
            break;
        };

#ifdef CV_COLLECT_IMPL_DATA
        if(param_collect_impl)
        {
            LOGD("impl_ipp =%11d", (int)(implConf.ipp || implConf.icv));
            LOGD("impl_ocl =%11d", (int)implConf.ocl);
            LOGD("impl_plain =%11d", (int)implConf.plain);

            std::string rec_line;
            std::vector<cv::String> rec;
            rec_line.clear();
            rec = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT);
            for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
            rec = implConf.GetCallsForImpl(CV_IMPL_IPP);
            for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
            LOGD("impl_rec_ipp =%s", rec_line.c_str());

            rec_line.clear();
            rec = implConf.GetCallsForImpl(CV_IMPL_OCL);
            for(int i=0; i<rec.size();i++ ){rec_line += rec[i].c_str(); rec_line += " ";}
            LOGD("impl_rec_ocl =%s", rec_line.c_str());
        }
#endif

        LOGD("bytesIn   =%11lu", (unsigned long)m.bytesIn);
        LOGD("bytesOut  =%11lu", (unsigned long)m.bytesOut);
        if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS)
            LOGD("samples   =%11u",  m.samples);
        else
            LOGD("samples   =%11u of %u", m.samples, nIters);
        LOGD("outliers  =%11u", m.outliers);
        LOGD("frequency =%11.0f", m.frequency);
        if (m.samples > 0)
        {
            LOGD("min       =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency);
            LOGD("median    =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency);
            LOGD("gmean     =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency);
            LOGD("gstddev   =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency);
            LOGD("mean      =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency);
            LOGD("stddev    =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency);
        }
    }
}

void TestBase::SetUp()
{
    cv::theRNG().state = param_seed; // this rng should generate same numbers for each run

    if (param_threads >= 0)
        cv::setNumThreads(param_threads);

#ifdef ANDROID
    if (param_affinity_mask)
        setCurrentThreadAffinityMask(param_affinity_mask);
#endif

    verified = false;
    lastTime = 0;
    totalTime = 0;
    runsPerIteration = 1;
    nIters = iterationsLimitDefault;
    currentIter = (unsigned int)-1;
    timeLimit = timeLimitDefault;
    times.clear();
}

void TestBase::TearDown()
{
    if (metrics.terminationReason == performance_metrics::TERM_SKIP_TEST)
    {
        LOGI("\tTest was skipped");
        GTEST_SUCCEED() << "Test was skipped";
    }
    else
    {
        if (!HasFailure() && !verified)
            ADD_FAILURE() << "The test has no sanity checks. There should be at least one check at the end of performance test.";

        validateMetrics();
        if (HasFailure())
        {
            reportMetrics(false);
            return;
        }
    }

    const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
    const char* type_param = test_info->type_param();
    const char* value_param = test_info->value_param();
    if (value_param) printf("[ VALUE    ] \t%s\n", value_param), fflush(stdout);
    if (type_param)  printf("[ TYPE     ] \t%s\n", type_param), fflush(stdout);

#ifdef CV_COLLECT_IMPL_DATA
    if(param_collect_impl)
    {
        implConf.ShapeUp();
        printf("[ I. FLAGS ] \t");
        if(implConf.ipp_mt)
        {
            if(implConf.icv) {printf("ICV_MT "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
            if(implConf.ipp) {printf("IPP_MT "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP|CV_IMPL_MT); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
        }
        else
        {
            if(implConf.icv) {printf("ICV "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
            if(implConf.ipp) {printf("IPP "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_IPP); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
        }
        if(implConf.ocl) {printf("OCL "); std::vector<cv::String> fun = implConf.GetCallsForImpl(CV_IMPL_OCL); printf("("); for(int i=0; i<fun.size();i++ ){printf("%s ", fun[i].c_str());} printf(") "); }
        if(implConf.plain) printf("PLAIN ");
        if(!(implConf.ipp_mt || implConf.icv || implConf.ipp || implConf.ocl || implConf.plain))
            printf("ERROR ");
        printf("\n");
        fflush(stdout);
    }
#endif
    reportMetrics(true);
}

std::string TestBase::getDataPath(const std::string& relativePath)
{
    if (relativePath.empty())
    {
        ADD_FAILURE() << "  Bad path to test resource";
        throw PerfEarlyExitException();
    }

    const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
    const char *path_separator = "/";

    std::string path;
    if (data_path_dir)
    {
        int len = (int)strlen(data_path_dir) - 1;
        if (len < 0) len = 0;
        path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
                + (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator);
    }
    else
    {
        path = ".";
        path += path_separator;
    }

    if (relativePath[0] == '/' || relativePath[0] == '\\')
        path += relativePath.substr(1);
    else
        path += relativePath;

    FILE* fp = fopen(path.c_str(), "r");
    if (fp)
        fclose(fp);
    else
    {
        ADD_FAILURE() << "  Requested file \"" << path << "\" does not exist.";
        throw PerfEarlyExitException();
    }
    return path;
}

void TestBase::RunPerfTestBody()
{
    try
    {
#ifdef CV_COLLECT_IMPL_DATA
        if(param_collect_impl)
            implConf.Reset();
#endif
        this->PerfTestBody();
#ifdef CV_COLLECT_IMPL_DATA
        if(param_collect_impl)
            implConf.GetImpl();
#endif
    }
    catch(PerfSkipTestException&)
    {
        metrics.terminationReason = performance_metrics::TERM_SKIP_TEST;
        return;
    }
    catch(PerfEarlyExitException&)
    {
        metrics.terminationReason = performance_metrics::TERM_INTERRUPT;
        return;//no additional failure logging
    }
    catch(cv::Exception& e)
    {
        metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
        #ifdef HAVE_CUDA
            if (e.code == cv::Error::GpuApiCallError)
                cv::cuda::resetDevice();
        #endif
        FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n  Actual: it throws cv::Exception:\n  " << e.what();
    }
    catch(std::exception& e)
    {
        metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
        FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n  Actual: it throws std::exception:\n  " << e.what();
    }
    catch(...)
    {
        metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
        FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n  Actual: it throws...";
    }
}

/*****************************************************************************************\
*                          ::perf::TestBase::_declareHelper
\*****************************************************************************************/
TestBase::_declareHelper& TestBase::_declareHelper::iterations(unsigned int n)
{
    test->times.clear();
    test->times.reserve(n);
    test->nIters = std::min(n, TestBase::iterationsLimitDefault);
    test->currentIter = (unsigned int)-1;
    test->metrics.clear();
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs)
{
    test->times.clear();
    test->currentIter = (unsigned int)-1;
    test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency());
    test->metrics.clear();
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::tbb_threads(int n)
{
    cv::setNumThreads(n);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::runs(unsigned int runsNumber)
{
    test->runsPerIteration = runsNumber;
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->inputData, a1, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->inputData, a1, wtype);
    TestBase::declareArray(test->inputData, a2, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->inputData, a1, wtype);
    TestBase::declareArray(test->inputData, a2, wtype);
    TestBase::declareArray(test->inputData, a3, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->inputData, a1, wtype);
    TestBase::declareArray(test->inputData, a2, wtype);
    TestBase::declareArray(test->inputData, a3, wtype);
    TestBase::declareArray(test->inputData, a4, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->outputData, a1, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->outputData, a1, wtype);
    TestBase::declareArray(test->outputData, a2, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->outputData, a1, wtype);
    TestBase::declareArray(test->outputData, a2, wtype);
    TestBase::declareArray(test->outputData, a3, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, WarmUpType wtype)
{
    if (!test->times.empty()) return *this;
    TestBase::declareArray(test->outputData, a1, wtype);
    TestBase::declareArray(test->outputData, a2, wtype);
    TestBase::declareArray(test->outputData, a3, wtype);
    TestBase::declareArray(test->outputData, a4, wtype);
    return *this;
}

TestBase::_declareHelper& TestBase::_declareHelper::strategy(enum PERF_STRATEGY s)
{
    test->testStrategy = s;
    return *this;
}

TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t)
{
}

/*****************************************************************************************\
*                                  miscellaneous
\*****************************************************************************************/

namespace {
struct KeypointComparator
{
    std::vector<cv::KeyPoint>& pts_;
    comparators::KeypointGreater cmp;

    KeypointComparator(std::vector<cv::KeyPoint>& pts) : pts_(pts), cmp() {}

    bool operator()(int idx1, int idx2) const
    {
        return cmp(pts_[idx1], pts_[idx2]);
    }
private:
    const KeypointComparator& operator=(const KeypointComparator&); // quiet MSVC
};
}//namespace

void perf::sort(std::vector<cv::KeyPoint>& pts, cv::InputOutputArray descriptors)
{
    cv::Mat desc = descriptors.getMat();

    CV_Assert(pts.size() == (size_t)desc.rows);
    cv::AutoBuffer<int> idxs(desc.rows);

    for (int i = 0; i < desc.rows; ++i)
        idxs[i] = i;

    std::sort((int*)idxs, (int*)idxs + desc.rows, KeypointComparator(pts));

    std::vector<cv::KeyPoint> spts(pts.size());
    cv::Mat sdesc(desc.size(), desc.type());

    for(int j = 0; j < desc.rows; ++j)
    {
        spts[j] = pts[idxs[j]];
        cv::Mat row = sdesc.row(j);
        desc.row(idxs[j]).copyTo(row);
    }

    spts.swap(pts);
    sdesc.copyTo(desc);
}

/*****************************************************************************************\
*                                  ::perf::GpuPerf
\*****************************************************************************************/
bool perf::GpuPerf::targetDevice()
{
    return param_impl == "cuda";
}

/*****************************************************************************************\
*                                  ::perf::PrintTo
\*****************************************************************************************/
namespace perf
{

void PrintTo(const MatType& t, ::std::ostream* os)
{
    switch( CV_MAT_DEPTH((int)t) )
    {
        case CV_8U:  *os << "8U";  break;
        case CV_8S:  *os << "8S";  break;
        case CV_16U: *os << "16U"; break;
        case CV_16S: *os << "16S"; break;
        case CV_32S: *os << "32S"; break;
        case CV_32F: *os << "32F"; break;
        case CV_64F: *os << "64F"; break;
        case CV_USRTYPE1: *os << "USRTYPE1"; break;
        default: *os << "INVALID_TYPE"; break;
    }
    *os << 'C' << CV_MAT_CN((int)t);
}

} //namespace perf

/*****************************************************************************************\
*                                  ::cv::PrintTo
\*****************************************************************************************/
namespace cv {

void PrintTo(const String& str, ::std::ostream* os)
{
    *os << "\"" << str << "\"";
}

void PrintTo(const Size& sz, ::std::ostream* os)
{
    *os << /*"Size:" << */sz.width << "x" << sz.height;
}

}  // namespace cv

/* [<][>][^][v][top][bottom][index][help] */