//===----------------------------------------------------------------------===//
//
//                         DuckDB
//
// duckdb/main/profiling_utils.hpp
//
// This file is automatically generated by scripts/generate_metric_enums.py
// Do not edit this file manually, your changes will be overwritten
//===----------------------------------------------------------------------===//

#pragma once

#include "duckdb/common/enums/metric_type.hpp"
#include "duckdb/main/profiling_node.hpp"
#include "duckdb/main/profiling_info.hpp"
#include "duckdb/common/profiler.hpp"

namespace duckdb_yyjson {
struct yyjson_mut_doc;
struct yyjson_mut_val;
} // namespace duckdb_yyjson

namespace duckdb {

struct ActiveTimer;

// Top level query metrics
struct QueryMetrics {
public:
    QueryMetrics() {
        Reset();
    }

	ProfilingInfo query_global_info;

	std::string query_name;
	unique_ptr<ActiveTimer> latency_timer;

public:
    void UpdateMetric(const MetricType metric, idx_t addition) {
        active_metrics[GetMetricsIndex(metric)] += addition;
    }

    idx_t GetMetricValue(const MetricType metric) const {
        return active_metrics[GetMetricsIndex(metric)];
    }

    double GetMetricInSeconds(const MetricType metric) const {
        return static_cast<double>(active_metrics[GetMetricsIndex(metric)]) / 1e9;
    }

    void Reset() {
        for(idx_t i = 0; i < ACTIVELY_TRACKED_METRICS; i++) {
            active_metrics[i] = 0;
        }

        latency_timer.reset();
        query_name = "";
    }

    void Merge(const QueryMetrics &other) {
        for(idx_t i = 0; i < ACTIVELY_TRACKED_METRICS; i++) {
            active_metrics[i] += other.active_metrics[i];
        }
    }

	static idx_t GetMetricsIndex(MetricType type) {
		switch(type) {
		case MetricType::ATTACH_LOAD_STORAGE_LATENCY: return 0;
		case MetricType::ATTACH_REPLAY_WAL_LATENCY: return 1;
		case MetricType::CHECKPOINT_LATENCY: return 2;
		case MetricType::COMMIT_LOCAL_STORAGE_LATENCY: return 3;
		case MetricType::LATENCY: return 4;
		case MetricType::WAITING_TO_ATTACH_LATENCY: return 5;
		case MetricType::WRITE_TO_WAL_LATENCY: return 6;
		case MetricType::TOTAL_BYTES_READ: return 7;
		case MetricType::TOTAL_BYTES_WRITTEN: return 8;
		case MetricType::TOTAL_MEMORY_ALLOCATED: return 9;
		case MetricType::WAL_REPLAY_ENTRY_COUNT: return 10;
		default:
			throw InternalException("MetricType %s is not actively tracked.", EnumUtil::ToString(type));
		}
	}

private:
	static constexpr const idx_t ACTIVELY_TRACKED_METRICS = 11;

	atomic<idx_t> active_metrics[ACTIVELY_TRACKED_METRICS];
};

class ProfilingUtils {
public:
	static void SetMetricToDefault(profiler_metrics_t &metrics, const MetricType &type);
	static void MetricToJson(duckdb_yyjson::yyjson_mut_doc *doc, duckdb_yyjson::yyjson_mut_val *dest, const char *key_ptr,  profiler_metrics_t &metrics, const MetricType &type);
	static void CollectMetrics(const MetricType &type, QueryMetrics &query_metrics, Value &metric, ProfilingNode &node, ProfilingInfo &child_info);
};

struct ActiveTimer {
public:
	ActiveTimer() : metric(MetricType::EXTRA_INFO), is_active(false) {
	}
	ActiveTimer(QueryMetrics &query_metrics, const MetricType metric, const bool is_active = true) : query_metrics(query_metrics), metric(metric), is_active(is_active) {
		// start on constructor
		if (!is_active) {
			return;
		}
		profiler.Start();
	}
	~ActiveTimer() {
		if (is_active) {
			// automatically end in destructor
			EndTimer();
		}
	}
	// disable copy constructors
	ActiveTimer(const ActiveTimer &other) = delete;
	ActiveTimer &operator=(const ActiveTimer &) = delete;
	//! enable move constructors
	ActiveTimer(ActiveTimer &&other) noexcept : is_active(false) {
		std::swap(query_metrics, other.query_metrics);
		std::swap(metric, other.metric);
		std::swap(profiler, other.profiler);
		std::swap(is_active, other.is_active);
	}
	ActiveTimer &operator=(ActiveTimer &&other) noexcept {
		std::swap(query_metrics, other.query_metrics);
		std::swap(metric, other.metric);
		std::swap(profiler, other.profiler);
		std::swap(is_active, other.is_active);
		return *this;
	}

	// Automatically called in the destructor.
	void EndTimer() {
		if (!is_active) {
			return;
		}
		// stop profiling and report
		is_active = false;
		profiler.End();
		query_metrics->UpdateMetric(metric, profiler.ElapsedNanos());
	}

	void Reset() {
	    if (!is_active) {
			return;
		}
		profiler.Reset();
		is_active = false;
	}

private:
	optional_ptr<QueryMetrics> query_metrics;
	MetricType metric;
	Profiler profiler;
	bool is_active;
};

}
