// Copyright 2023 TiKV Project Authors.
//
// 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.

package controller

import (
	"strconv"

	rmpb "github.com/pingcap/kvproto/pkg/resource_manager"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	namespace             = "resource_manager_client"
	requestSubsystem      = "request"
	tokenRequestSubsystem = "token_request"
	tokenSubsystem        = "token"
	resourceUnitSubsystem = "resource_unit"
	resourceSubsystem     = "resource"

	// TODO: remove old label in 8.x
	resourceGroupNameLabel    = "name"
	newResourceGroupNameLabel = "resource_group"
	clientUniqueIDLabel       = "client_unique_id"
	directionLabel            = "direction"

	errType = "type"
)

var (
	resourceGroupStatusGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: "resource_group",
			Name:      "status",
			Help:      "Status of the resource group.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})

	successfulRequestDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: requestSubsystem,
			Name:      "success",
			Buckets:   []float64{0.0005, .005, .01, .05, .1, .5, 1, 5, 10, 20, 25, 30, 60, 600, 1800, 3600}, // 0.0005 ~ 1h
			Help:      "Bucketed histogram of wait duration of successful request.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})

	failedLimitReserveDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: requestSubsystem,
			Name:      "limit_reserve_time_failed",
			Buckets:   []float64{0.0005, .01, .05, .1, .5, 1, 5, 10, 20, 25, 30, 60, 600, 1800, 3600, 86400}, // 0.0005 ~ 24h
			Help:      "Bucketed histogram of wait duration of failed request.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})

	failedRequestCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: requestSubsystem,
			Name:      "fail",
			Help:      "Counter of failed request.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})

	groupRunningKVRequestCounter = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: requestSubsystem,
			Name:      "running_kv_request",
			Help:      "Counter of running kv request.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})

	requestRetryCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: requestSubsystem,
			Name:      "retry",
			Help:      "Counter of retry time for request.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})

	tokenRequestDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: tokenRequestSubsystem,
			Buckets:   prometheus.ExponentialBuckets(0.001, 2, 13), // 1ms ~ 8s
			Name:      "duration",
			Help:      "Bucketed histogram of latency(s) of token request.",
		}, []string{clientUniqueIDLabel, "type"})

	resourceGroupTokenRequestCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: tokenRequestSubsystem,
			Name:      "resource_group",
			Help:      "Counter of token request by every resource group.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})

	lowTokenRequestNotifyCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: tokenRequestSubsystem,
			Name:      "low_token_notified",
			Help:      "Counter of low token request.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})
	tokenConsumedHistogram = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: tokenRequestSubsystem,
			Name:      "consume",
			Buckets:   []float64{.5, 1, 2, 5, 10, 15, 20, 40, 64, 128, 256, 512, 1024, 2048}, // 0 ~ 2048
			Help:      "Bucketed histogram of token consume.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})

	tokenRequestCauseCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: tokenRequestSubsystem,
			Name:      "cause_total",
			Help:      "Counter of token bucket request causes by resource group.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, "cause"})
	clientInfoGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "info",
			Help:      "Resource manager client information.",
		}, []string{clientUniqueIDLabel, newResourceGroupNameLabel})
	clientTokenBalanceGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "balance",
			Help:      "Current local token balance observed by resource manager client.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})
	clientTokenFillRateGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "fill_rate",
			Help:      "Current local token fill rate observed by resource manager client.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})
	clientTokenBurstLimitGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "burst_limit",
			Help:      "Current local token burst limit observed by resource manager client.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})
	clientTokenAvgRUPerSecGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "avg_ru_per_sec",
			Help:      "Moving average RU per second estimated by resource manager client.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})
	clientTokenDemandGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "demand",
			Help:      "Moving average pre-throttling token demand per second estimated by resource manager client.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})
	clientTokenThrottledGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "throttled",
			Help:      "Whether the resource manager client is currently throttled.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})
	clientTokenGrantedCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "granted_total",
			Help:      "Counter of tokens granted to resource manager client.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})
	clientTokenTrickleDurationHistogram = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: tokenSubsystem,
			Name:      "trickle_duration_ms",
			Help:      "Bucketed histogram of token trickle duration in milliseconds observed by resource manager client.",
			Buckets:   []float64{0, 1, 10, 50, 100, 500, 1000, 5000, 10000, 30000, 60000},
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, errType})

	clientReadRequestUnitCost = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: resourceUnitSubsystem,
			Name:      "read_request_unit_sum",
			Help:      "Counter of client-side read request unit cost for all resource groups.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})
	clientWriteRequestUnitCost = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: resourceUnitSubsystem,
			Name:      "write_request_unit_sum",
			Help:      "Counter of client-side write request unit cost flow for all resource groups.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, directionLabel})
	clientReadByteCost = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: resourceSubsystem,
			Name:      "read_byte_sum",
			Help:      "Counter of client-side read byte cost for all resource groups.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})
	clientWriteByteCost = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: resourceSubsystem,
			Name:      "write_byte_sum",
			Help:      "Counter of client-side write byte cost flow for all resource groups.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel, directionLabel})
	clientKVCPUCost = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: resourceSubsystem,
			Name:      "kv_cpu_time_ms_sum",
			Help:      "Counter of client-side KV CPU time cost in milliseconds for all resource groups.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})
	clientSQLCPUCost = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: resourceSubsystem,
			Name:      "sql_cpu_time_ms_sum",
			Help:      "Counter of client-side SQL CPU time cost in milliseconds for all resource groups.",
		}, []string{resourceGroupNameLabel, newResourceGroupNameLabel, clientUniqueIDLabel})
)

func init() {
	prometheus.MustRegister(resourceGroupStatusGauge)
	prometheus.MustRegister(successfulRequestDuration)
	prometheus.MustRegister(failedRequestCounter)
	prometheus.MustRegister(failedLimitReserveDuration)
	prometheus.MustRegister(groupRunningKVRequestCounter)
	prometheus.MustRegister(requestRetryCounter)
	prometheus.MustRegister(tokenRequestDuration)
	prometheus.MustRegister(resourceGroupTokenRequestCounter)
	prometheus.MustRegister(lowTokenRequestNotifyCounter)
	prometheus.MustRegister(tokenConsumedHistogram)
	prometheus.MustRegister(tokenRequestCauseCounter)
	prometheus.MustRegister(clientInfoGauge)
	prometheus.MustRegister(clientTokenBalanceGauge)
	prometheus.MustRegister(clientTokenFillRateGauge)
	prometheus.MustRegister(clientTokenBurstLimitGauge)
	prometheus.MustRegister(clientTokenAvgRUPerSecGauge)
	prometheus.MustRegister(clientTokenDemandGauge)
	prometheus.MustRegister(clientTokenThrottledGauge)
	prometheus.MustRegister(clientTokenGrantedCounter)
	prometheus.MustRegister(clientTokenTrickleDurationHistogram)
	prometheus.MustRegister(clientReadRequestUnitCost)
	prometheus.MustRegister(clientWriteRequestUnitCost)
	prometheus.MustRegister(clientReadByteCost)
	prometheus.MustRegister(clientWriteByteCost)
	prometheus.MustRegister(clientKVCPUCost)
	prometheus.MustRegister(clientSQLCPUCost)
}

func formatClientUniqueID(clientUniqueID uint64) string {
	return strconv.FormatUint(clientUniqueID, 10)
}

func requestUnitMetricLabel(typ rmpb.RequestUnitType) string {
	if name, ok := rmpb.RequestUnitType_name[int32(typ)]; ok {
		return name
	}
	return strconv.Itoa(int(typ))
}

func rawResourceMetricLabel(typ rmpb.RawResourceType) string {
	if name, ok := rmpb.RawResourceType_name[int32(typ)]; ok {
		return name
	}
	return strconv.Itoa(int(typ))
}
