Skip to content

Commit

Permalink
Merge pull request #96 from deefreak/ossscraper
Browse files Browse the repository at this point in the history
Refactored p8s queries building implementation
  • Loading branch information
deefreak authored Dec 30, 2023
2 parents 334ba37 + 0c07dd6 commit 500e6ba
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 144 deletions.
149 changes: 149 additions & 0 deletions pkg/metrics/queries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package metrics

import "fmt"

type QueryComponent struct {
metric string
labelKeys []string
}

type CompositeQuery struct {
queries map[string]*QueryComponent
}

type CompositeQueryBuilder CompositeQuery

func NewCompositeQueryBuilder() *CompositeQueryBuilder {
return &CompositeQueryBuilder{}
}

func (qb *CompositeQueryBuilder) WithQuery(name string, query *QueryComponent) *CompositeQueryBuilder {
if qb.queries == nil {
qb.queries = make(map[string]*QueryComponent)
}
qb.queries[name] = query
return qb
}

func (qb *CompositeQueryBuilder) Build() *CompositeQuery {
return &CompositeQuery{
queries: qb.queries,
}
}

type CPUUtilizationQuery CompositeQuery
type CPUUtilizationBreachQuery CompositeQuery
type PodReadyLatencyQuery CompositeQuery

func (qb *CPUUtilizationQuery) Render(labels map[string]string) string {

return fmt.Sprintf("sum(%s * on (namespace,pod) group_left(workload, workload_type)"+
"%s) by(namespace, workload, workload_type)",
qb.queries["cpu_utilization_metric"].Render(labels),
qb.queries["pod_owner_metric"].Render(labels))
}

func (qb *CPUUtilizationBreachQuery) Render(redLineUtilization float64, labels map[string]string) string {

return fmt.Sprintf("(sum(%s * on(namespace,pod) group_left(workload, workload_type) "+
"%s) by (namespace, workload, workload_type)/ on (namespace, workload, workload_type) "+
"group_left sum(%s * on(namespace,pod) group_left(workload, workload_type)"+
"%s) by (namespace, workload, workload_type) > %.2f) and on(namespace, workload) "+
"label_replace(sum(%s * on(replicaset)"+
" group_left(namespace, owner_kind, owner_name) %s) by"+
" (namespace, owner_kind, owner_name) < on(namespace, owner_kind, owner_name) "+
"(%s * on(namespace, horizontalpodautoscaler) "+
"group_left(owner_kind, owner_name) label_replace(label_replace(%s,\"owner_kind\", \"$1\", "+
"\"scaletargetref_kind\", \"(.*)\"), \"owner_name\", \"$1\", \"scaletargetref_name\", \"(.*)\")),"+
"\"workload\", \"$1\", \"owner_name\", \"(.*)\")",
qb.queries["cpu_utilization_metric"].Render(labels),
qb.queries["pod_owner_metric"].Render(labels),
qb.queries["resource_limit_metric"].Render(labels),
qb.queries["pod_owner_metric"].Render(labels),
redLineUtilization,
qb.queries["ready_replicas_metric"].Render(labels),
qb.queries["replicaset_owner_metric"].Render(labels),
qb.queries["hpa_max_replicas_metric"].Render(labels),
qb.queries["hpa_owner_info_metric"].Render(labels))

}

func (qb *PodReadyLatencyQuery) Render(labels map[string]string) string {

return fmt.Sprintf("quantile(0.5,(%s - on (namespace,pod) (%s)) "+
"* on (namespace,pod) group_left(workload, workload_type)"+
"(%s))",
qb.queries["pod_ready_time_metric"].Render(labels),
qb.queries["pod_created_time_metric"].Render(labels),
qb.queries["pod_owner_metric"].Render(labels))
}

func ValidateQuery(query string) bool {
//validate if p8s query is syntactically correct
stack := make([]rune, 0)
for _, char := range query {
switch char {
case '(', '{':
stack = append(stack, char)
case ')':
if len(stack) == 0 || stack[len(stack)-1] != '(' {
return false
}
stack = stack[:len(stack)-1]
case '}':
if len(stack) == 0 || stack[len(stack)-1] != '{' {
return false
}
stack = stack[:len(stack)-1]
}
}
return len(stack) == 0

}

type QueryComponentBuilder QueryComponent

func NewQueryComponentBuilder() *QueryComponentBuilder {
return &QueryComponentBuilder{}
}

func (qb *QueryComponentBuilder) WithMetric(metric string) *QueryComponentBuilder {
qb.metric = metric
return qb
}

func (qb *QueryComponentBuilder) WithLabelKeys(labelKeys []string) *QueryComponentBuilder {
qb.labelKeys = labelKeys
return qb
}

func (qb *QueryComponentBuilder) Build() *QueryComponent {
return &QueryComponent{
metric: qb.metric,
labelKeys: qb.labelKeys,
}
}

func (qc *QueryComponent) Render(m map[string]string) string {

labels := make(map[string]string)
for _, labelKey := range qc.labelKeys {
if labelValue, ok := m[labelKey]; ok {
labels[labelKey] = labelValue
}
}
return fmt.Sprintf("%s%s", qc.metric, renderLabels(labels))
}

func renderLabels(labels map[string]string) string {
if len(labels) == 0 {
return ""
}
labelStr := "{"
for key, value := range labels {
labelStr += fmt.Sprintf("%s=\"%s\",", key, value)
}
labelStr = labelStr[:len(labelStr)-1]
labelStr += "}"
return labelStr
}
52 changes: 52 additions & 0 deletions pkg/metrics/queries_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package metrics

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Queries", func() {
var (
qc *QueryComponent
)

BeforeEach(func() {
qc = &QueryComponent{
metric: "test_metric",
labelKeys: []string{"label1", "label2"},
}
})

Describe("Render", func() {
Context("when labels are present", func() {
It("should render the metric with labels", func() {
labels := map[string]string{
"label1": "value1",
"label2": "value2",
}
Expect(qc.Render(labels)).To(Equal("test_metric{label1=\"value1\",label2=\"value2\"}"))
})
})

Context("when labels are not present", func() {
It("should render the metric without labels", func() {
labels := map[string]string{}
Expect(qc.Render(labels)).To(Equal("test_metric"))
})
})
})

Describe("ValidateQuery", func() {
Context("when the query is valid", func() {
It("should return true", func() {
Expect(ValidateQuery("(test_metric{label1=\"value1\",label2=\"value2\"})")).To(BeTrue())
})
})

Context("when the query is not valid", func() {
It("should return false", func() {
Expect(ValidateQuery("(test_metric{label1=\"value1\",label2=\"value2\"}")).To(BeFalse())
})
})
})
})
Loading

0 comments on commit 500e6ba

Please sign in to comment.