Skip to content

Commit

Permalink
add
Browse files Browse the repository at this point in the history
Signed-off-by: yaroslavborbat <[email protected]>
  • Loading branch information
yaroslavborbat committed Sep 23, 2024
1 parent 1d8fa75 commit 174ce01
Show file tree
Hide file tree
Showing 8 changed files with 402 additions and 69 deletions.
19 changes: 19 additions & 0 deletions images/virtualization-artifact/pkg/monitoring/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ limitations under the License.

package metrics

import "github.com/prometheus/client_golang/prometheus"

const (
MetricNamespace = "d8_virtualization"
)

type MetricInfo struct {
Desc *prometheus.Desc
Type prometheus.ValueType
}

// +nolint: unparam
func NewMetricInfo(metricName, help string, t prometheus.ValueType, labels []string, constLabels prometheus.Labels) MetricInfo {
return MetricInfo{
Desc: prometheus.NewDesc(
prometheus.BuildFQName(MetricNamespace, "", metricName),
help,
labels,
constLabels),
Type: t,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Copyright 2024 Flant JSC
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 util

import (
"fmt"

"github.com/gogo/protobuf/proto"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"google.golang.org/protobuf/types/known/timestamppb"
)

type dynamicMetric struct {
desc *prometheus.Desc
metric *dto.Metric
}

func (m dynamicMetric) Desc() *prometheus.Desc {
return m.desc
}

func (m dynamicMetric) Write(out *dto.Metric) error {
out.Label = m.metric.Label
out.Counter = m.metric.Counter
out.Gauge = m.metric.Gauge
out.Untyped = m.metric.Untyped
return nil
}

func NewDynamicMetric(desc *prometheus.Desc, valueType prometheus.ValueType, value float64, labelValues []string, extraLabels prometheus.Labels) (prometheus.Metric, error) {
metric := &dto.Metric{}
if err := populateMetric(valueType, value, makeLabelPairs(desc, labelValues, extraLabels), nil, metric, nil); err != nil {
return nil, err
}

return &dynamicMetric{
desc: desc,
metric: metric,
}, nil
}

func makeLabelPairs(desc *prometheus.Desc, labelValues []string, extraLabels prometheus.Labels) []*dto.LabelPair {
pairs := prometheus.MakeLabelPairs(desc, labelValues)
if extraLabels == nil {
return pairs
}
for k, v := range extraLabels {
pairs = append(pairs, &dto.LabelPair{
Name: proto.String(k),
Value: proto.String(v),
})
}
return pairs
}

func populateMetric(
t prometheus.ValueType,
v float64,
labelPairs []*dto.LabelPair,
e *dto.Exemplar,
m *dto.Metric,
ct *timestamppb.Timestamp,
) error {
m.Label = labelPairs
switch t {
case prometheus.CounterValue:
m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e, CreatedTimestamp: ct}
case prometheus.GaugeValue:
m.Gauge = &dto.Gauge{Value: proto.Float64(v)}
case prometheus.UntypedValue:
m.Untyped = &dto.Untyped{Value: proto.Float64(v)}
default:
return fmt.Errorf("encountered unknown type %v", t)
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2024 Flant JSC
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 util

import (
"fmt"
"regexp"
"strings"
)

var (
invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
)

type SkipLabel func(key, value string) bool

func WrapPrometheusLabels(labels map[string]string, prefix string, skip SkipLabel) map[string]string {
wrapLabels := make(map[string]string, len(labels))
conflicts := make(map[string]int, len(labels))

for k, v := range labels {
if skip != nil && skip(k, v) {
continue
}
labelKey := labelName(prefix, k)
if conflictCount, ok := conflicts[labelKey]; ok {
if conflictCount == 1 {
// this is the first conflict for the label,
// so we have to go back and rename the initial label that we've already added

value := wrapLabels[labelKey]
delete(wrapLabels, labelKey)
wrapLabels[labelConflictSuffix(labelKey, conflictCount)] = value
}
conflicts[labelKey]++
labelKey = labelConflictSuffix(labelKey, conflicts[labelKey])
} else {
conflicts[labelKey] = 1
}

wrapLabels[labelKey] = v
}
return wrapLabels
}

func labelName(prefix, labelName string) string {
return prefix + "_" + lintLabelName(SanitizeLabelName(labelName))
}

func SanitizeLabelName(s string) string {
return invalidLabelCharRE.ReplaceAllString(s, "_")
}

func lintLabelName(s string) string {
return toSnakeCase(s)
}

func toSnakeCase(s string) string {
snake := matchAllCap.ReplaceAllString(s, "${1}_${2}")
return strings.ToLower(snake)
}

func labelConflictSuffix(label string, count int) string {
return fmt.Sprintf("%s_conflict%d", label, count)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright 2024 Flant JSC
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 util

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestWrapPrometheusLabels(t *testing.T) {
tests := []struct {
name string
labels map[string]string
expected map[string]string
}{
{
name: "should resolve conflicted labels",
labels: map[string]string{
"key-1": "value1",
"key_1": "value1",
"key-2": "value2",
},
expected: map[string]string{
"label_key_1_conflict1": "value1",
"label_key_1_conflict2": "value1",
"label_key_2": "value2",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
wrapped := WrapPrometheusLabels(test.labels, "label", nil)
require.Equal(t, test.expected, wrapped)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,9 @@ const collectorName = "virtualmachine-collector"
func SetupCollector(reader client.Reader,
registerer prometheus.Registerer,
log *slog.Logger,
) *Collector {
c := &Collector{
iterator: newUnsafeIterator(reader),
log: log.With(logger.SlogCollector(collectorName)),
}

registerer.MustRegister(c)
return c
) {
c := NewCollector(reader, log)
c.Register(registerer)
}

type handler func(m *dataMetric) (stop bool)
Expand All @@ -48,14 +43,25 @@ type Iterator interface {
Iter(ctx context.Context, h handler) error
}

func NewCollector(reader client.Reader, log *slog.Logger) *Collector {
return &Collector{
iterator: newUnsafeIterator(reader),
log: log.With(logger.SlogCollector(collectorName)),
}
}

type Collector struct {
iterator Iterator
log *slog.Logger
}

func (c Collector) Register(reg prometheus.Registerer) {
reg.MustRegister(c)
}

func (c Collector) Describe(ch chan<- *prometheus.Desc) {
for _, v := range virtualMachineMetrics {
ch <- v
for _, m := range virtualMachineMetrics {
ch <- m.Desc
}
}

Expand All @@ -68,6 +74,5 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) {
return
}); err != nil {
c.log.Error("Failed to iterate of VirtualMachines", logger.SlogErr(err))
return
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"

"github.com/deckhouse/virtualization-controller/pkg/controller/service"
"github.com/deckhouse/virtualization-controller/pkg/monitoring/metrics/util"
virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2"
"github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition"
)
Expand All @@ -33,16 +34,19 @@ type dataMetric struct {
Node string
UID string
Phase virtv2.MachinePhase
CpuConfigurationCores float64
CpuConfigurationCoreFraction float64
CpuCores float64
CpuCoreFraction float64
CpuRequestedCores float64
CpuRuntimeOverhead float64
MemorySize float64
MemoryConfigurationSize float64
MemoryRuntimeOverhead float64
AwaitingRestartToApplyConfiguration bool
ConfigurationApplied bool
RunPolicy virtv2.RunPolicy
Pods []virtv2.VirtualMachinePod
Labels map[string]string
Annotations map[string]string
}

// DO NOT mutate VirtualMachine!
Expand All @@ -51,7 +55,9 @@ func newDataMetric(vm *virtv2.VirtualMachine) *dataMetric {
return nil
}
res := vm.Status.Resources
cf := intstr.FromString(strings.TrimSuffix(res.CPU.CoreFraction, "%"))
cf := getPercent(res.CPU.CoreFraction)
cfSpec := getPercent(vm.Spec.CPU.CoreFraction)

var (
awaitingRestartToApplyConfiguration bool
configurationApplied bool
Expand All @@ -68,21 +74,33 @@ func newDataMetric(vm *virtv2.VirtualMachine) *dataMetric {
for i, pod := range vm.Status.VirtualMachinePods {
pods[i] = *pod.DeepCopy()
}

return &dataMetric{
Name: vm.Name,
Namespace: vm.Namespace,
Node: vm.Status.Node,
UID: string(vm.UID),
Phase: vm.Status.Phase,
CpuConfigurationCores: float64(vm.Spec.CPU.Cores),
CpuConfigurationCoreFraction: float64(cfSpec.IntValue()),
CpuCores: float64(res.CPU.Cores),
CpuCoreFraction: float64(cf.IntValue()),
CpuRequestedCores: float64(res.CPU.RequestedCores.MilliValue()),
CpuRuntimeOverhead: float64(res.CPU.RuntimeOverhead.MilliValue()),
MemorySize: float64(res.Memory.Size.Value()),
MemoryConfigurationSize: float64(vm.Spec.Memory.Size.Value()),
MemoryRuntimeOverhead: float64(res.Memory.RuntimeOverhead.Value()),
AwaitingRestartToApplyConfiguration: awaitingRestartToApplyConfiguration,
ConfigurationApplied: configurationApplied,
RunPolicy: vm.Spec.RunPolicy,
Pods: pods,
Labels: util.WrapPrometheusLabels(vm.GetLabels(), "label", func(key, value string) bool {
return false
}),
Annotations: util.WrapPrometheusLabels(vm.GetAnnotations(), "annotation", func(key, _ string) bool {
return strings.HasPrefix(key, "kubectl.kubernetes.io")
}),
}
}

func getPercent(s string) intstr.IntOrString {
return intstr.FromString(strings.TrimSuffix(s, "%"))
}
Loading

0 comments on commit 174ce01

Please sign in to comment.