// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 <gtest/gtest.h>

#include <iostream>
#include <thread>

#include "common/config.h"
#include "util/logging.h"
#include "util/metrics.h"
#include "util/stopwatch.hpp"

namespace doris {

class MetricsTest : public testing::Test {
public:
    MetricsTest() { }
    virtual ~MetricsTest() {
    }
};

TEST_F(MetricsTest, Counter) {
    {
        IntCounter counter;
        ASSERT_EQ(0, counter.value());
        counter.increment(100);
        ASSERT_EQ(100, counter.value());

        ASSERT_STREQ("100", counter.to_string().c_str());
    }
    {
        DoubleCounter counter;
        ASSERT_EQ(0.0, counter.value());
        counter.increment(1.23);
        ASSERT_EQ(1.23, counter.value());

        ASSERT_STREQ("1.23", counter.to_string().c_str());
    }
}

void mt_updater(IntCounter* counter, std::atomic<uint64_t>* used_time) {
    sleep(1);
    MonotonicStopWatch watch;
    watch.start();
    for (int i = 0; i < 1000000L; ++i) {
        counter->increment(1);
    }
    uint64_t elapsed = watch.elapsed_time();
    used_time->fetch_add(elapsed);
}

TEST_F(MetricsTest, CounterPerf) {
    IntCounter counter;
    volatile int64_t sum = 0;

    {
        MonotonicStopWatch watch;
        watch.start();
        for (int i = 0; i < 100000000; ++i) {
            counter.increment(1);
        }
        uint64_t elapsed = watch.elapsed_time();
        LOG(INFO) << "counter elapsed: " << elapsed
                  << "ns, ns/iter:" << elapsed / 100000000;
    }
    {
        MonotonicStopWatch watch;
        watch.start();
        for (int i = 0; i < 100000000; ++i) {
            sum += 1;
        }
        uint64_t elapsed = watch.elapsed_time();
        LOG(INFO) << "value elapsed: " << elapsed
                  << "ns, ns/iter:" << elapsed / 100000000;
    }
    ASSERT_EQ(100000000, counter.value());
    ASSERT_EQ(100000000, sum);
    {
        IntCounter mt_counter;
        std::vector<std::thread> updaters;
        std::atomic<uint64_t> used_time(0);
        for (int i = 0; i < 8; ++i) {
            updaters.emplace_back(&mt_updater, &mt_counter, &used_time);
        }
        for (int i = 0; i < 8; ++i) {
            updaters[i].join();
        }
        LOG(INFO) << "mt_counter elapsed: " << used_time.load()
                  << "ns, ns/iter:" << used_time.load() / (8 * 1000000L);
        ASSERT_EQ(8 * 1000000L, mt_counter.value());
    }
}

TEST_F(MetricsTest, Gauge) {
    {
        IntGauge gauge;
        ASSERT_EQ(0, gauge.value());
        gauge.set_value(100);
        ASSERT_EQ(100, gauge.value());

        ASSERT_STREQ("100", gauge.to_string().c_str());
    }
    {
        DoubleGauge gauge;
        ASSERT_EQ(0.0, gauge.value());
        gauge.set_value(1.23);
        ASSERT_EQ(1.23, gauge.value());

        ASSERT_STREQ("1.23", gauge.to_string().c_str());
    }
}

TEST_F(MetricsTest, MetricLabel) {
    std::string put("put");
    MetricLabel label("type", put);

    ASSERT_TRUE(label == MetricLabel("type", "put"));
    ASSERT_TRUE(label != MetricLabel("type", "get"));
    ASSERT_TRUE(label < MetricLabel("type", "quit"));
    ASSERT_TRUE(label < MetricLabel("typee", "put"));
    ASSERT_TRUE(label.compare(MetricLabel("type", "put")) == 0);
    ASSERT_TRUE(label.compare(MetricLabel("typee", "put")) < 0);

    ASSERT_STREQ("type=put", label.to_string().c_str());
}

TEST_F(MetricsTest, MetricLabels) {
    MetricLabels empty_labels;

    ASSERT_TRUE(empty_labels == MetricLabels());
    ASSERT_TRUE(empty_labels < MetricLabels().add("type", "put"));
    ASSERT_TRUE(empty_labels.empty());

    ASSERT_STREQ("", empty_labels.to_string().c_str());

    MetricLabels labels;
    labels.add("path", "/home").add("type", "put");

    ASSERT_TRUE(labels == MetricLabels().add("path", "/home").add("type", "put"));
    ASSERT_FALSE(labels == MetricLabels().add("path", "/home").add("type", "get"));
    ASSERT_FALSE(labels == MetricLabels().add("path", "/home"));
    ASSERT_TRUE(labels < MetricLabels().add("path", "/sports"));
    ASSERT_TRUE(labels < MetricLabels().add("path", "/home").add("type", "put").add("xstatus", "404"));
    ASSERT_FALSE(labels < MetricLabels().add("path", "/abc"));
    ASSERT_FALSE(labels < MetricLabels().add("path", "/home").add("type", "put"));

    ASSERT_STREQ("path=/home,type=put", labels.to_string().c_str());
}

class TestMetricsVisitor : public MetricsVisitor {
public:
    virtual ~TestMetricsVisitor() { }
    void visit(const std::string& prefix, const std::string& name,
               MetricCollector* collector) {
        for (auto& it : collector->metrics()) {
            Metric* metric = it.second;
            auto& labels = it.first;
            switch (metric->type()) {
            case MetricType::COUNTER: {
                bool has_prev = false;
                if (!prefix.empty()) {
                    _ss << prefix;
                    has_prev = true;
                }
                if (!name.empty()) {
                    if (has_prev) {
                        _ss << "_";
                    }
                    _ss << name;
                }
                if (!labels.empty()) {
                    if (has_prev) {
                        _ss << "_";
                    }
                    _ss << labels.to_string();
                }
                _ss << " " << ((SimpleMetric*)metric)->to_string() << std::endl;
                break;
            }
            default:
                break;
            }
        }
    }
    std::string to_string() {
        return _ss.str();
    }
private:
    std::stringstream _ss;
};

TEST_F(MetricsTest, MetricCollector) {
    IntCounter puts;
    puts.increment(101);
    IntCounter gets;
    gets.increment(201);
    MetricCollector collector;
    ASSERT_TRUE(collector.add_metic(MetricLabels().add("type", "put"), &puts));
    ASSERT_TRUE(collector.add_metic(MetricLabels().add("type", "get"), &gets));
    ASSERT_FALSE(collector.add_metic(MetricLabels().add("type", "get"), &gets));

    {
        // Can't add different type to one collector
        IntGauge post;
        ASSERT_FALSE(collector.add_metic(MetricLabels().add("type", "post"), &post));
    }

    {
        TestMetricsVisitor visitor;
        collector.collect("", "", &visitor);
        ASSERT_STREQ("type=get 201\ntype=put 101\n", visitor.to_string().c_str());
    }
    collector.remove_metric(&puts);
    {
        TestMetricsVisitor visitor;
        collector.collect("", "", &visitor);
        ASSERT_STREQ("type=get 201\n", visitor.to_string().c_str());
    }
    // test get_metric
    ASSERT_TRUE(collector.get_metric(MetricLabels()) == nullptr);
    ASSERT_TRUE(collector.get_metric(MetricLabels().add("type" ,"get")) != nullptr);
    std::vector<Metric*> metrics;
    collector.get_metrics(&metrics);
    ASSERT_EQ(1, metrics.size());
}

TEST_F(MetricsTest, MetricRegistry) {
    MetricRegistry registry("test");
    IntCounter cpu_idle;
    cpu_idle.increment(12);
    ASSERT_TRUE(registry.register_metric("cpu_idle", &cpu_idle));
    // registry failed
    IntCounter dummy;
    ASSERT_FALSE(registry.register_metric("cpu_idle", &dummy));
    IntCounter memory_usage;
    memory_usage.increment(24);
    ASSERT_TRUE(registry.register_metric("memory_usage", &memory_usage));
    {
        TestMetricsVisitor visitor;
        registry.collect(&visitor);
        ASSERT_STREQ("test_cpu_idle 12\ntest_memory_usage 24\n", visitor.to_string().c_str());
    }
    registry.deregister_metric(&memory_usage);
    {
        TestMetricsVisitor visitor;
        registry.collect(&visitor);
        ASSERT_STREQ("test_cpu_idle 12\n", visitor.to_string().c_str());
    }
    // test get_metric
    ASSERT_TRUE(registry.get_metric("cpu_idle") != nullptr);
    ASSERT_TRUE(registry.get_metric("memory_usage") == nullptr);
}

TEST_F(MetricsTest, MetricRegistry2) {
    MetricRegistry registry("test");
    IntCounter cpu_idle;
    cpu_idle.increment(12);
    ASSERT_TRUE(registry.register_metric("cpu_idle", &cpu_idle));

    {
        // memory_usage will deregister after this block
        IntCounter memory_usage;
        memory_usage.increment(24);
        ASSERT_TRUE(registry.register_metric("memory_usage", &memory_usage));
        TestMetricsVisitor visitor;
        registry.collect(&visitor);
        ASSERT_STREQ("test_cpu_idle 12\ntest_memory_usage 24\n", visitor.to_string().c_str());
    }

    {
        TestMetricsVisitor visitor;
        registry.collect(&visitor);
        ASSERT_STREQ("test_cpu_idle 12\n", visitor.to_string().c_str());
    }
}

}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
