package metrics

import (
	. "context"
	"fmt"
	"strings"
	"time"

	"github.com/onsi/gomega/format"
	"github.com/pingcap/test-infra/sdk/resource"
	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
)

// checker is a convenient wrapper of promQLEvaluatedToEmpty, for you to
// implement your own PromQL-oriented assertion by buildChecker.
//
// When you're writing a new checker, a rule of thumb that you should follow is:
// If the expected state is False (that means a normal state (i.e. expected state)
// leads to a False returned from the assertion, and when anomaly occurs, the
// checker returns True. Assertions as such including TiDBRestart, QPSDropMoreThan,
// etc.), then you should guarantee your PromQL expr returns NON-EMPTY when that
// anomaly occurs.
//
// If you're curious about why is that, please continue reading.
//
// A common confusion is under what circumstance could a checker returns false.
// you may suppose a checker returns false when its promQLEvaluatedToEmpty returns
// false, but, sadly, that's NOT TRUE. If your PromQL expression evaluated to empty,
// promQLEvaluatedToEmpty will returns true, and this truthy will be inverted by
// checker, thus a checker returns false when the corresponding PromQL expr evaluated
// to EMPTY.
//
// Another common misunderstanding is w.r.t naming. The name of these checker builder
// actually denotes the cases when the PromQL returns NON-EMPTY, as the reason behind
// this already described in the last paragraph (i.e. the inversion by checker).
// That is to say, the PromQL of QPSDropMoreThan actually needs to return NON-EMPTY
// when the QPS is really drop more than the threshold, so that `ShouldNot(QPSDropMoreThan)`
// will fail.
//
// Such situation could be further deteriorated by the introduction of For. The
// AtLeastFailedFor tolerate failure for promQLEvaluatedToEmpty, not the checker
// itself. That means when your non-empty-when-anomaly-occurs PromQL expr returns
// non-empty, causing promQLEvaluatedToEmpty should have returned False, this
// falsy will be tolerated and rectified to True. After that, inversion in checker
// will causes False be returned from checker, thus passes the outer ShouldNot.
//
// This discrepancy accounts for the breaking outcome of implementing For with
// TolerateByDuration, and why the counter-intuition nature of For with which
// only negated assertions works fine.
//
// Yes, Yes, WHAT A BIG MESS! Therefore we introduce TolerateByDuration which
// treats any GomegaMatcher *as a whole*, as we hereby *deprecated* For.
//
// TODO: by redesigning these thingy into a more natural, obvious one, we could remove these lengthy comments...
type checker struct {
	name     string
	subject  *PromQLSubject
	delegate *promQLEvaluatedToEmpty

	testingKnobs struct {
		mockApi v1.API
	}
}

func buildChecker() *checker {
	return &checker{
		subject:  &PromQLSubject{},
		delegate: PromQLEvaluatedToEmpty(),
	}
}

// Deprecated: check out the comments of checker. use TolerateByDuration instead.
func (q *checker) For(atLeastFailedFor time.Duration) *checker {
	q.subject.AtLeastFailedFor(atLeastFailedFor)
	return q
}

// WithCtx makes the assertion *always passed* after deadline of the ctx exceeded.
func (q *checker) WithCtx(ctx Context) *checker {
	q.subject.Ctx = ctx
	return q
}

func (q *checker) Match(actual interface{}) (success bool, err error) {
	if q.testingKnobs.mockApi != nil {
		q.subject.Api = q.testingKnobs.mockApi
	} else {
		resolver, ok := actual.(resource.Addressable)
		if !ok {
			return false, fmt.Errorf(q.name+" must be passed a resource.Addressable(such as resource.TiDBCluster). Got:\n%s", format.Object(actual, 1))
		}

		if q.subject.Api == nil {
			url, err := resolver.ServiceURL(resource.Prometheus)
			if err != nil {
				return false, err
			}
			api, err := NewPrometheusClient(*url)
			if err != nil {
				return false, err
			}
			q.subject.Api = api
		}
	}

	b, err := q.delegate.Match(*q.subject)
	return !b, err
}

func (q *checker) FailureMessage(_ interface{}) (message string) {
	return q.delegate.FailureMessage(q.subject)
}

func (q *checker) NegatedFailureMessage(_ interface{}) (message string) {
	return q.delegate.NegatedFailureMessage(q.subject)
}

func (q *checker) MatchMayChangeInTheFuture(_ interface{}) bool {
	return q.delegate.MatchMayChangeInTheFuture(q.subject)
}

// QPSDropMoreThan checks if (the current QPS) <= (1-percent)*(QPS 2 minutes ago).
//
// If you want to compare the current QPS with QPS 3 minutes ago, you should
// write your own query.
func QPSDropMoreThan(percentage float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("QPS drop more than (%.2f)", percentage)
	matcher.subject.Query = fmt.Sprintf(
		"sum(irate(tidb_executor_statement_total[1m])) < %.2f * sum(rate(tidb_executor_statement_total[1m] offset 2m))",
		1-percentage)
	matcher.subject.Ctx = TODO()
	return matcher
}

// RaftLogLaglessThan detects whether the Raft log lag are less than some value
func RaftLogLaglessThan(threshold int) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("Raft log lag less than (%d)", threshold)
	matcher.subject.Query = fmt.Sprintf(
		`sum(rate(tikv_raftstore_log_lag_sum[1m])) < %d`,
		threshold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// RaftEngineFileCountMoreThan detects whether the raft engine file count are more than some value
func RaftEngineAppendFileCountMoreThan(threshold int) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("Raft engine file count more than (%d)", threshold)
	matcher.subject.Query = fmt.Sprintf(
		`avg(raft_engine_log_file_count{type="append"}) > %d`,
		threshold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// RegionSizeMoreThan detects whether the region size exceed expectations, the unit of region size is byte, such as 134217728
// threshold can only be N * 1048576, and N can only take one of these value:
// [1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,1048576]
// TODO: verify the validity of threshold
func RegionSizeMoreThan(threshold int) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("Region Size more than (%d)", threshold)
	matcher.subject.Query = fmt.Sprintf(
		`sum(rate(tikv_raftstore_region_size_bucket{le="+Inf"}[1m])) - ignoring(le) sum(rate(tikv_raftstore_region_size_bucket{le="%d"}[1m])) > 0`,
		threshold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// Enhance for QPSDropMoreThan, define your own offset
func QPSDropMoreThanOffset(percentage float64, offset string) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("QPS drop more than (%.2f)", percentage)
	matcher.subject.Query = fmt.Sprintf(
		"sum(irate(tidb_executor_statement_total[1m])) < %.2f * sum(rate(tidb_executor_statement_total[1m] offset %s))",
		1-percentage, offset)
	matcher.subject.Ctx = TODO()
	return matcher
}

// QPSIncreaseLessThanOffset checks if (the current QPS) <= (1+percent)*(QPS 2 minutes ago).
func QPSIncreaseLessThanOffset(percentage float64, offset string) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("QPS drop more than (%.2f)", percentage)
	matcher.subject.Query = fmt.Sprintf(
		"sum(irate(tidb_executor_statement_total[1m])) < %.2f * sum(rate(tidb_executor_statement_total[1m] offset %s))",
		1+percentage, offset)
	matcher.subject.Ctx = TODO()
	return matcher
}

// DurationIncreasedMoreThanOffset checks if (the current Duration) >= (1+percent)*(Duration 2 minutes ago).
func DurationIncreasedMoreThanOffset(percentage float64, offset string) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("Duration increased more than (%.2f)", percentage)
	matcher.subject.Query = fmt.Sprintf(
		"histogram_quantile(0.99, sum(irate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le)) > %.2f * histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m] offset %s)) by (le))",
		1+percentage, offset)
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiDBConnectionPanic checks if TiDB connection is panic.
func TiDBConnectionPanic() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiDB connection panic"
	matcher.subject.Query = `increase(tidb_server_panic_total[1m]) != 0`
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiDBCriticalErrorCheck checks if TiDB server has critical error.
func TiDBCriticalErrorCheck() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiDB has critical error"
	matcher.subject.Query = `increase(tidb_server_critical_error_total[1m]) != 0`
	matcher.subject.Ctx = TODO()
	return matcher
}

// InstanceRestart checks if specified instances had been restarted.
func InstanceRestart(instance string) *checker {
	component := strings.Split(instance, "-")[1]
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("%s restart", instance)
	matcher.subject.Query = fmt.Sprintf("rate(process_start_time_seconds{component=\"%s\", instance=\"%s\"}[2m]) != 0", component, instance)
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiKVRestart checks if TiKV instances belongs to the specified TiDBCluster had been restarted.
func TiKVRestart() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiKV restart"
	matcher.subject.Query = `rate(process_start_time_seconds{component="tikv"}[2m]) != 0`
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiDBRestart checks if TiDB instances belongs to the specified TiDBCluster had been restarted.
func TiDBRestart() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiDB restart"
	matcher.subject.Query = `rate(process_start_time_seconds{component="tidb"}[2m]) != 0`
	matcher.subject.Ctx = TODO()
	return matcher
}

// PDRestart checks if PD instances belongs to the specified TiDBCluster had been restarted.
func PDRestart() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "PD restart"
	matcher.subject.Query = `rate(process_start_time_seconds{component="pd"}[2m]) != 0`
	matcher.subject.Ctx = TODO()
	return matcher
}

// AllComponentRestart checks if all component including tikv/tidb/pd/tiflash instances belongs to the specified TiDBCluster had been restarted.
func AllComponentRestart() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiKV/TiDB/PD restart"
	matcher.subject.Query = `rate(process_start_time_seconds{}[2m]) != 0`
	matcher.subject.Ctx = TODO()
	return matcher
}

// GCSafePointIncrease checks if Gc safe point increase by time.
func GCSafePointIncrease() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Check if Gc safe point increase by time."
	matcher.subject.Query = `max(tikv_gcworker_autogc_safe_point)/(2^18) <= max(tikv_gcworker_autogc_safe_point offset 20m)/(2^18)`
	matcher.subject.Ctx = TODO()
	return matcher
}

// DifferenceExceedsCheck checks if the different between all tikvs exceeds threshhold.
func DifferenceExceedsCheck(query string, filter string, describe string, threshhold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = describe
	balance_query := fmt.Sprintf("abs(%s-on(%s) group_left avg(%s) by (%s))/on(%s) group_left avg(%s) by (%s) > %f",
		query, filter, query, filter, filter, query, filter, threshhold)
	matcher.subject.Query = balance_query
	matcher.subject.Ctx = TODO()
	return matcher
}

// StoreReadQueryDifferentExceeds checks if the different in read query rate between all tikvs exceeds threshhold
func StoreReadQueryDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="store_read_query_rate",address=~".*-tikv-.*"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in read query rate between all tikvs exceeds threshhold", threshhold)
}

// StoreWriteQueryDifferentExceeds checks if the different in write query between all tikvs exceeds threshhold
func StoreWriteQueryDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="store_write_query_rate",address=~".*-tikv-.*"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in write query between all tikvs exceeds threshhold", threshhold)
}

// ReginLeaderDifferentExceeds checks if the different in region leader count between all tikvs exceeds threshhold
func RegionLeaderDifferentExceeds(threshhold float64) *checker {
	query := `tikv_raftstore_region_count{type="leader"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in region leader count between all tikvs exceeds threshhold", threshhold)
}

// ReginNumberDifferentExceeds checks if the different in region count between all tikvs exceeds threshhold
func RegionNumberDifferentExceeds(threshhold float64) *checker {
	query := `tikv_raftstore_region_count{type="region"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in region count between all tikvs exceeds threshhold", threshhold)
}

// StoreWriteByteDifferentExceeds checks if the different in write rate between all tikvs exceeds threshhold
func StoreWriteByteDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="store_write_rate_bytes"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in write rate between all tikvs exceeds threshhold", threshhold)
}

// StoreWriteKeyDifferentExceeds checks if the different in write rate keys between all tikvs exceeds threshhold
func StoreWriteKeyDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="store_regions_write_rate_keys"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in write rate keys between all tikvs exceeds threshhold", threshhold)
}

// StoreReadByteDifferentExceeds checks if the different in read rate bytes between all tikvs exceeds threshhold
func StoreReadByteDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="store_read_rate_bytes"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in read rate bytes between all tikvs exceeds threshhold", threshhold)
}

// StoreReadKeyDifferentExceeds checks if the different in read rate keys between all tikvs exceeds threshhold
func StoreReadKeyDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="store_read_rate_keys"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in read rate keys between all tikvs exceeds threshhold", threshhold)
}

// StoreScoreDifferentExceeds checks if the different in region score between all tikvs exceeds threshhold
func StoreScoreDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="region_score",address=~".*tikv.*"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in region score between all tikvs exceeds threshhold", threshhold)
}

// StoreRegionSizeDifferentExceeds checks if the different in region size between all tikvs exceeds threshhold
func StoreRegionSizeDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="region_size",address=~".*tikv.*"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in region size between all tikvs exceeds threshhold", threshhold)
}

// LeaderScoreDifferentExceeds checks if the different in region size between all tikvs exceeds threshhold
func LeaderScoreDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="leader_score",address=~".*tikv.*"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in region size between all tikvs exceeds threshhold", threshhold)
}

// TiKVQPSDifferentExceeds checks if the different in tikv qps between all tikvs exceeds threshhold
func TiKVQPSDifferentExceeds(threshhold float64) *checker {
	query := `sum(rate(tikv_grpc_msg_duration_seconds_count{type!="kv_gc"}[1m])) by (instance)`
	return DifferenceExceedsCheck(query, "type", "Check if the different in tikv qps between all tikvs exceeds threshhold", threshhold)
}

// TiKVCPUDifferentExceeds checks if the different in tikv cpu between all tikvs exceeds threshhold
func TiKVCPUDifferentExceeds(threshhold float64) *checker {
	// query := `sum(rate(tikv_thread_cpu_seconds_total{}[1m])) by (instance)`
	matcher := buildChecker()
	matcher.subject.Description = "Check if the different in tikv cpu between all tikvs exceeds threshhold"
	balance_query := fmt.Sprintf(`abs(sum(rate(tikv_thread_cpu_seconds_total[1m])) by (instance)-ignoring(instance) group_left avg(sum(rate(tikv_thread_cpu_seconds_total[1m])) by (instance)))
	/ignoring(instance) group_left avg(sum(rate(tikv_thread_cpu_seconds_total[1m])) by (instance)) > %f`, threshhold)
	matcher.subject.Query = balance_query
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiKVCopDurationDifferentExceeds checks if the different in tikv cop between all tikvs exceeds threshhold
func TiKVCopDurationDifferentExceeds(threshhold float64) *checker {
	query := `tikv_grpc_msg_duration_seconds_count{type="coprocessor"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in tikv cop between all tikvs exceeds threshhold", threshhold)
}

// DurationP99MoreThan checks if 99% duration large than threshhold
func DurationP99MoreThan(threshhold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Check if 99% duration more than threshhold(ms)"
	matcher.subject.Query = fmt.Sprintf(`histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{}[1m])) by (le)) > %f`, threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// applyDbMutexLockDurationMoreThan checks if apply-db_mutex_lock_nanos large than threshhold
func ApplyDbMutexLockDurationMoreThan(threshhold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Check if apply-db_mutex_lock_nanos more than threshhold(s)"
	matcher.subject.Query = fmt.Sprintf(`histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_perf_context_time_duration_secs_bucket{type="db_mutex_lock_nanos"}[1m])) by (le)) > %f`, threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// StoreRegionSizeDifferentExceeds checks if the different in region size between all tiflash exceeds threshhold
func TiFlashStoreRegionSizeDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="region_size",address=~".*tiflash.*"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in region size between all tiflash exceeds threshhold", threshhold)
}

// StoreRegionSizeDifferentExceeds checks if the different in region count between all tiflash exceeds threshhold
func TiFlashStoreRegionCountDifferentExceeds(threshhold float64) *checker {
	query := `pd_scheduler_store_status{type="region_count",address=~".*tiflash.*"}`
	return DifferenceExceedsCheck(query, "type", "Check if the different in region count between all tiflash exceeds threshhold", threshhold)
}

func TiFlashStoreStoreSizeDifferentExceeds(threshhold float64) *checker {
	query := `tiflash_system_current_metric_StoreSizeUsed`
	return DifferenceExceedsCheck(query, "", "Check if tiflash store size is balance", threshhold)
}

func TiFlashWriteFlowDifferentExceeds(threshhold float64) *checker {
	query := `sum(rate(tiflash_storage_throughput_bytes{type=~"write|ingest"}[1m])) by (instance)`
	return DifferenceExceedsCheck(query, "", "Check if tiflash write flow is balance", threshhold)
}

// TiFlashRestart checks if TiFlash instances belongs to the specified TiDBCluster had been restarted.
func TiFlashRestart() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiFlash restart"
	matcher.subject.Query = `rate(tiflash_proxy_process_start_time_seconds{}[2m]) != 0`
	matcher.subject.Ctx = TODO()
	return matcher
}

func TiFlashMvccSnapshotLifetimeMoreThan(threshold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = fmt.Sprintf("Tiflash mvcc snapshot max lifetime less than (%.2f)", threshold)
	matcher.subject.Query = fmt.Sprintf(
		`tiflash_system_asynchronous_metric_MaxDTDeltaOldestSnapshotLifetime > %f`,
		threshold)
	matcher.subject.Ctx = TODO()
	return matcher
}

func TiFlashActiveThreadPoolNoLeak() *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Tiflash active thread pool size equals 400"
	matcher.subject.Query = `tiflash_thread_count{type="thread_pool_active"} == 400`
	matcher.subject.Ctx = TODO()
	return matcher
}

// ResolvedTsGapMoreThan checks if resolvedTsGap more than threshold seconds, this resolvedTs use for stale read.
func ResolvedTsGapMoreThanSecond(threshhold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Check if resolved ts gap more than threshhold seconds"
	matcher.subject.Query = fmt.Sprintf(`max(tikv_resolved_ts_min_resolved_ts_gap_millis{}) > %f`, 1000*threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// LeaderResolvedTsGapMoreThan checks if resolvedTsGap in leader region more than threshold seconds, this resolvedTs use for stale read.
func LeaderResolvedTsGapMoreThanSecond(threshhold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Check if leader resolved ts gap more than threshhold seconds"
	matcher.subject.Query = fmt.Sprintf(`max(tikv_resolved_ts_min_leader_resolved_ts_gap_millis{}) > %f`, 1000*threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// PdTsoGapMoreThanMs checks if pd tso gap more than threshold millionseconds.
func PdTsoGapMoreThanMs(threshhold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Pd tso gap more than threshhold millionseconds"
	matcher.subject.Query = fmt.Sprintf(`sum(pd_cluster_tso_gap_millionseconds{}) by (dc) > %f`, threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// EmptyRegionsRatioMoreThan checks if empty regions ratio more than threshold .
func EmptyRegionsRatioMoreThan(threshhold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Empty regions more than threshhold"
	matcher.subject.Query = fmt.Sprintf(`sum(pd_regions_status{type="empty-region-count"})/sum(pd_cluster_status{type="leader_count"}) > %f`, threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// EmptyRegionsMoreThan checks if empty region more than threshold.
func EmptyRegionsMoreThan(threshhold int64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Empty region ratio more than threshhold"
	matcher.subject.Query = fmt.Sprintf(`sum(pd_regions_status{type="empty-region-count"}) > %d`, threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// UndersizedRegionsMoreThan checks if undersized region more than threshold.
func UndersizedRegionsMoreThan(threshhold int64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Undersized region more than threshhold"
	matcher.subject.Query = fmt.Sprintf(`sum(pd_regions_status{type="undersized-region-count"}) > %d`, threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// OversizedRegionsMoreThan checks if oversized region more than threshold.
func OversizedRegionsMoreThan(threshhold int64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Oversized region more than threshhold"
	matcher.subject.Query = fmt.Sprintf(`sum(pd_regions_status{type="oversized-region-count"}) > %d`, threshhold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiDBProfilingCPU checks if TiDB is profiling
func TiDBProfilingCPU(instance string) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiDB is profiling"
	matcher.subject.Query = fmt.Sprintf(`sum(rate(tidb_server_cpu_profile_total{instance="%s"}[1m])) > 0`, instance)
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiKVRsmeterStatingThreads checks if TiKV is stating threads for resource metering
func TiKVRsmeterStatingThreads(instance string) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiKV is stating threads for resource metering"
	matcher.subject.Query = fmt.Sprintf(`sum(rate(tikv_resource_metering_stat_task_count{instance="%s"}[1m])) > 0`, instance)
	matcher.subject.Ctx = TODO()
	return matcher
}

// RocksdbSnapshotNumMoreThan checks if number of snapshot of rocksdb more than threshold.
func RocksdbSnapshotNumMoreThan(threshold int64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "Rocksdb snapshot number more than threshold"
	matcher.subject.Query = fmt.Sprintf(`tikv_engine_num_snapshots > %d`, threshold)
	matcher.subject.Ctx = TODO()
	return matcher
}

// TiFlashProxyStoreSizeDiffLessThan check if tiflash's store_used obtained from pd and tiflash are consistent,
// if the difference exceeds `threshold`, it is judged to be inconsistent.
// If deploy the cluster through TiUP, only can match nodes by ip and port from Prometheus's label.
// So using TiFlash's default port to get data by regular expression.
// Add according to https://github.com/pingcap/tiflash/pull/4907
func TiFlashProxyStoreSizeDiffLessThan(threshold float64) *checker {
	matcher := buildChecker()
	matcher.subject.Description = "TiFlash proxy store size diff less than threshold"
	matcher.subject.Query = fmt.Sprintf(`abs(sum(tiflash_system_current_metric_StoreSizeUsed{job=~".*tiflash.*"}) - sum(pd_scheduler_store_status{type="store_used", address=~".*3930.*"})) / sum(pd_scheduler_store_status{type="store_used", address=~".*3930.*"}) < %f`, threshold)
	matcher.subject.Ctx = TODO()
	return matcher
}
