/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "PerfTest.h"
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>

#ifdef ASSERT
#undef ASSERT
#endif
#define ASSERT(cond)                                                                              \
    do {                                                                                          \
        if (!(cond)) {                                                                            \
            cerr << __func__ << ":" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \
            exit(EXIT_FAILURE);                                                                   \
        }                                                                                         \
    } while (0)

// the ratio that the service is synced on the same cpu beyond
// GOOD_SYNC_MIN is considered as good
#define GOOD_SYNC_MIN (0.6)

// the precision used for cout to dump float
#define DUMP_PRICISION 2

using std::cerr;
using std::cout;
using std::endl;
using std::left;
using std::ios;
using std::min;
using std::max;
using std::to_string;
using std::setprecision;
using std::setw;
using std::ofstream;
using std::make_tuple;

tuple<Pipe, Pipe> Pipe::createPipePair() {
    int a[2];
    int b[2];

    int error1 = pipe(a);
    int error2 = pipe(b);
    ASSERT(error1 >= 0);
    ASSERT(error2 >= 0);

    return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1]));
}

Pipe::Pipe(Pipe&& rval) noexcept {
    fd_read_ = rval.fd_read_;
    fd_write_ = rval.fd_write_;
    rval.fd_read_ = 0;
    rval.fd_write_ = 0;
}

Pipe::~Pipe() {
    if (fd_read_) {
        close(fd_read_);
    }
    if (fd_write_) {
        close(fd_write_);
    }
}

Results Results::combine(const Results& a, const Results& b) {
    Results ret;
    for (uint32_t i = 0; i < kNumBuckets; i++) {
        ret.buckets_[i] = a.buckets_[i] + b.buckets_[i];
    }
    ret.worst_ = max(a.worst_, b.worst_);
    ret.best_ = min(a.best_, b.best_);
    ret.transactions_ = a.transactions_ + b.transactions_;
    ret.miss_ = a.miss_ + b.miss_;
    ret.total_time_ = a.total_time_ + b.total_time_;
    return ret;
}

static void traceStop() {
    ofstream file;
    file.open(TRACE_PATH "/tracing_on", ios::out | ios::trunc);
    file << '0' << endl;
    file.close();
}

void Results::addTime(uint64_t nano) {
    buckets_[min(nano, kMaxTimeBucket - 1) / kTimePerBucket] += 1;
    best_ = min(nano, best_);
    worst_ = max(nano, worst_);
    if (raw_dump_) {
        raw_data_->push_back(nano);
    }
    transactions_ += 1;
    total_time_ += nano;
    if (missDeadline(nano)) {
        miss_++;
        if (tracing_) {
            // There might be multiple process pair running the test concurrently
            // each may execute following statements and only the first one actually
            // stop the trace and any traceStop() after then has no effect.
            traceStop();
            cerr << endl;
            cerr << "deadline triggered: halt & stop trace" << endl;
            cerr << "log:" TRACE_PATH "/trace" << endl;
            cerr << endl;
            exit(EXIT_FAILURE);
        }
    }
}

void Results::setupRawData() {
    raw_dump_ = true;
    if (raw_data_ == nullptr) {
        raw_data_ = new list<uint64_t>;
    } else {
        raw_data_->clear();
    }
}

void Results::flushRawData() {
    if (raw_dump_) {
        bool first = true;
        cout << "[";
        for (auto nano : *raw_data_) {
            cout << (first ? "" : ",") << to_string(nano);
            first = false;
        }
        cout << "]," << endl;
        delete raw_data_;
        raw_data_ = nullptr;
    }
}

void Results::dump() const {
    double best = (double)best_ / 1.0E6;
    double worst = (double)worst_ / 1.0E6;
    double average = (double)total_time_ / transactions_ / 1.0E6;
    int W = DUMP_PRICISION + 2;
    cout << std::setprecision(DUMP_PRICISION) << "{ \"avg\":" << setw(W) << left << average
         << ", \"wst\":" << setw(W) << left << worst << ", \"bst\":" << setw(W) << left << best
         << ", \"miss\":" << left << miss_ << ", \"meetR\":" << setprecision(DUMP_PRICISION + 3)
         << left << (1.0 - (double)miss_ / transactions_) << "}";
}

void Results::dumpDistribution() const {
    uint64_t cur_total = 0;
    cout << "{ ";
    cout << std::setprecision(DUMP_PRICISION + 3);
    for (uint32_t i = 0; i < kNumBuckets; i++) {
        float cur_time = kTimePerBucketMS * i + 0.5f * kTimePerBucketMS;
        float accumulation = cur_total + buckets_[i];
        if ((cur_total < 0.5f * transactions_) && (accumulation >= 0.5f * transactions_)) {
            cout << "\"p50\":" << cur_time << ", ";
        }
        if ((cur_total < 0.9f * transactions_) && (accumulation >= 0.9f * transactions_)) {
            cout << "\"p90\":" << cur_time << ", ";
        }
        if ((cur_total < 0.95f * transactions_) && (accumulation >= 0.95f * transactions_)) {
            cout << "\"p95\":" << cur_time << ", ";
        }
        if ((cur_total < 0.99f * transactions_) && (accumulation >= 0.99f * transactions_)) {
            cout << "\"p99\": " << cur_time;
        }
        cur_total += buckets_[i];
    }
    cout << "}";
}

PResults PResults::combine(const PResults& a, const PResults& b) {
    PResults ret;
    ret.nNotInherent = a.nNotInherent + b.nNotInherent;
    ret.nNotSync = a.nNotSync + b.nNotSync;
    ret.other = Results::combine(a.other, b.other);
    ret.fifo = Results::combine(a.fifo, b.fifo);
    return ret;
}

void PResults::dump() const {
    int no_trans = other.getTransactions() + fifo.getTransactions();
    double sync_ratio = (1.0 - (double)nNotSync / no_trans);
    cout << "{\"SYNC\":\"" << ((sync_ratio > GOOD_SYNC_MIN) ? "GOOD" : "POOR") << "\","
         << "\"S\":" << (no_trans - nNotSync) << ",\"I\":" << no_trans << ","
         << "\"R\":" << sync_ratio << "," << endl;
    cout << "  \"other_ms\":";
    other.dump();
    cout << "," << endl;
    cout << "  \"fifo_ms\": ";
    fifo.dump();
    cout << "," << endl;
    cout << "  \"otherdis\":";
    other.dumpDistribution();
    cout << "," << endl;
    cout << "  \"fifodis\": ";
    fifo.dumpDistribution();
    cout << endl;
    cout << "}," << endl;
}