Skip to content

Commit

Permalink
Update metrics documentation generator
Browse files Browse the repository at this point in the history
Signed-off-by: machadovilaca <machadovilaca@gmail.com>
  • Loading branch information
machadovilaca committed Mar 26, 2024
1 parent c1e6b09 commit 62268bb
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 341 deletions.
24 changes: 8 additions & 16 deletions docs/metrics.md
@@ -1,16 +1,4 @@
<!--
This is an auto-generated file.
PLEASE DO NOT EDIT THIS FILE.
See "Developing new metrics" below how to generate this file
-->

# KubeVirt metrics
This document aims to help users that are not familiar with all metrics exposed by different KubeVirt components.
All metrics documented here are auto-generated by the utility tool `tools/doc-generator` and reflects exactly what is being exposed.

## KubeVirt Metrics List
### kubevirt_info
Version information.

### kubevirt_allocatable_nodes
The number of allocatable nodes in the cluster. Type: Gauge.
Expand All @@ -24,6 +12,9 @@ Indicates whether the Software Emulation is enabled in the configuration. Type:
### kubevirt_console_active_connections
Amount of active Console connections, broken down by namespace and vmi name. Type: Gauge.

### kubevirt_info
Version information. Type: Gauge.

### kubevirt_nodes_with_kvm
The number of nodes in the cluster that have the devices.kubevirt.io/kvm resource available. Type: Gauge.

Expand Down Expand Up @@ -139,7 +130,7 @@ The total amount of memory written out to swap space of the guest in bytes. Type
The amount of memory left completely unused by the system. Memory that is available but used for reclaimable caches should NOT be reported as free. Type: Gauge.

### kubevirt_vmi_memory_usable_bytes
The amount of memory which can be reclaimed by balloon without pushing the guest system to swap, corresponds to 'Available' in /proc/meminfo Type: Gauge.
The amount of memory which can be reclaimed by balloon without pushing the guest system to swap, corresponds to 'Available' in /proc/meminfo. Type: Gauge.

### kubevirt_vmi_memory_used_bytes
Amount of `used` memory as seen by the domain. Type: Gauge.
Expand Down Expand Up @@ -187,7 +178,7 @@ The total number of rx packets dropped on vNIC interfaces. Type: Counter.
Total network traffic received packets. Type: Counter.

### kubevirt_vmi_network_traffic_bytes_total
Deprecated. Type: Counter.
[Deprecated] Total number of bytes sent and received. Type: Counter.

### kubevirt_vmi_network_transmit_bytes_total
Total network traffic transmitted in bytes. Type: Counter.
Expand Down Expand Up @@ -268,6 +259,7 @@ Returns the labels of the persistent volume claims that are used for restoring v
Amount of active VNC connections, broken down by namespace and vmi name. Type: Gauge.

## Developing new metrics
After developing new metrics or changing old ones, please run `make generate` to regenerate this document.

If you feel that the new metric doesn't follow these rules, please change `doc-generator` with your needs.
All metrics documented here are auto-generated and reflect exactly what is being
exposed. After developing new metrics or changing old ones please regenerate
this document.
3 changes: 1 addition & 2 deletions hack/generate.sh
Expand Up @@ -140,8 +140,7 @@ ${KUBEVIRT_DIR}/tools/openapispec/openapispec --dump-api-spec-path ${KUBEVIRT_DI
(cd ${KUBEVIRT_DIR}/tools/doc-generator/ && go_build)
(
cd ${KUBEVIRT_DIR}/docs
${KUBEVIRT_DIR}/tools/doc-generator/doc-generator
mv newmetrics.md metrics.md
${KUBEVIRT_DIR}/tools/doc-generator/doc-generator >metrics.md
)

rm -f ${KUBEVIRT_DIR}/manifests/generated/*
Expand Down
15 changes: 3 additions & 12 deletions tools/doc-generator/BUILD.bazel
Expand Up @@ -2,26 +2,17 @@ load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")

go_library(
name = "go_default_library",
srcs = [
"doc-generator.go",
"fakeDomainCollector.go",
],
srcs = ["doc-generator.go"],
importpath = "kubevirt.io/kubevirt/tools/doc-generator",
visibility = ["//visibility:private"],
deps = [
"//pkg/monitoring/domainstats/prometheus:go_default_library",
"//pkg/monitoring/metrics/virt-api:go_default_library",
"//pkg/monitoring/metrics/virt-controller:go_default_library",
"//pkg/monitoring/metrics/virt-handler:go_default_library",
"//pkg/monitoring/metrics/virt-operator:go_default_library",
"//pkg/monitoring/rules:go_default_library",
"//pkg/virt-controller/watch:go_default_library",
"//pkg/virt-launcher/virtwrap/stats:go_default_library",
"//pkg/virt-launcher/virtwrap/statsconv:go_default_library",
"//pkg/virt-launcher/virtwrap/statsconv/util:go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
"//vendor/github.com/machadovilaca/operator-observability/pkg/docs:go_default_library",
"//vendor/github.com/machadovilaca/operator-observability/pkg/operatormetrics:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/libvirt.org/go/libvirt:go_default_library",
],
)

Expand Down
262 changes: 42 additions & 220 deletions tools/doc-generator/doc-generator.go
@@ -1,252 +1,74 @@
package main

import (
"bufio"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"sort"
"strings"

"github.com/machadovilaca/operator-observability/pkg/operatormetrics"

domainstats "kubevirt.io/kubevirt/pkg/monitoring/domainstats/prometheus" // import for prometheus metrics
virt_api "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-api"
virt_controller "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-controller"
virt_operator "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-operator"
virtapi "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-api"
virtcontroller "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-controller"
virthandler "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-handler"
virtoperator "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-operator"
"kubevirt.io/kubevirt/pkg/monitoring/rules"
_ "kubevirt.io/kubevirt/pkg/virt-controller/watch"
)

// constant parts of the file
const (
genFileComment = `<!--
This is an auto-generated file.
PLEASE DO NOT EDIT THIS FILE.
See "Developing new metrics" below how to generate this file
-->`
title = "# KubeVirt metrics\n"
background = "This document aims to help users that are not familiar with all metrics exposed by different KubeVirt components.\n" +
"All metrics documented here are auto-generated by the utility tool `tools/doc-generator` and reflects exactly what is being exposed.\n\n"

KVSpecificMetrics = "## KubeVirt Metrics List\n" +
"### kubevirt_info\n" +
"Version information.\n\n"

opening = genFileComment + "\n\n" +
title +
background +
KVSpecificMetrics

// footer
footerHeading = "## Developing new metrics\n"
footerContent = "After developing new metrics or changing old ones, please run `make generate` to regenerate this document.\n\n" +
"If you feel that the new metric doesn't follow these rules, please change `doc-generator` with your needs.\n"

footer = footerHeading + footerContent
"github.com/machadovilaca/operator-observability/pkg/docs"
)

func main() {
handler := domainstats.Handler(1)
RegisterFakeDomainCollector()

req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
checkError(err)

recorder := httptest.NewRecorder()

handler.ServeHTTP(recorder, req)

metrics := getMetricsNotIncludeInEndpointByDefault()

if status := recorder.Code; status == http.StatusOK {
err := parseVirtMetrics(recorder.Body, &metrics)
checkError(err)

} else {
panic(fmt.Errorf("got HTTP status code of %d from /metrics", recorder.Code))
}
writeToFile(metrics)
}

func writeToFile(metrics metricList) {
newFile, err := os.Create("newmetrics.md")
checkError(err)
defer newFile.Close()

fmt.Fprint(newFile, opening)
metrics.writeToFile(newFile)

fmt.Fprint(newFile, footer)

}

type metric struct {
name string
description string
mType string
}

func (m metric) writeToFile(newFile io.WriteCloser) {
fmt.Fprintln(newFile, "###", m.name)
fmt.Fprintln(newFile, m.description, "Type:", m.mType+".")
fmt.Fprintln(newFile)
}

type metricList []metric

// Len implements sort.Interface.Len
func (m metricList) Len() int {
return len(m)
}

// Less implements sort.Interface.Less
func (m metricList) Less(i, j int) bool {
return m[i].name < m[j].name
}
const tpl = `# KubeVirt metrics
// Swap implements sort.Interface.Swap
func (m metricList) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
{{- range . }}
func (m metricList) writeToFile(newFile io.WriteCloser) {
for _, met := range m {
met.writeToFile(newFile)
}
}
{{ $deprecatedVersion := "" -}}
{{- with index .ExtraFields "DeprecatedVersion" -}}
{{- $deprecatedVersion = printf " in %s" . -}}
{{- end -}}
func getMetricsNotIncludeInEndpointByDefault() metricList {
metrics := metricList{
{
name: domainstats.MigrateVmiDataProcessedMetricName,
description: "The total Guest OS data processed and migrated to the new VM.",
mType: "Gauge",
},
{
name: domainstats.MigrateVmiDataRemainingMetricName,
description: "The remaining guest OS data to be migrated to the new VM.",
mType: "Gauge",
},
{
name: domainstats.MigrateVmiDirtyMemoryRateMetricName,
description: "The rate of memory being dirty in the Guest OS.",
mType: "Gauge",
},
{
name: domainstats.MigrateVmiMemoryTransferRateMetricName,
description: "The rate at which the memory is being transferred.",
mType: "Gauge",
},
{
name: "kubevirt_vmi_phase_count",
description: "Sum of VMIs per phase and node. `phase` can be one of the following: [`Pending`, `Scheduling`, `Scheduled`, `Running`, `Succeeded`, `Failed`, `Unknown`].",
mType: "Gauge",
},
{
name: "kubevirt_vmi_non_evictable",
description: "Indication for a VirtualMachine that its eviction strategy is set to Live Migration but is not migratable.",
mType: "Gauge",
},
}
{{- $stabilityLevel := "" -}}
{{- if and (.ExtraFields.StabilityLevel) (ne .ExtraFields.StabilityLevel "STABLE") -}}
{{- $stabilityLevel = printf "[%s%s] " .ExtraFields.StabilityLevel $deprecatedVersion -}}
{{- end -}}
err := virt_controller.SetupMetrics(nil, nil, nil, nil, nil, nil, nil, nil)
checkError(err)
for _, m := range virt_controller.ListMetrics() {
metrics = append(metrics, newMetric(m))
}
### {{ .Name }}
{{ print $stabilityLevel }}{{ .Help }} Type: {{ .Type -}}.
err = virt_api.SetupMetrics()
checkError(err)
for _, m := range virt_api.ListMetrics() {
metrics = append(metrics, newMetric(m))
}
{{- end }}
err = virt_operator.SetupMetrics()
checkError(err)
for _, m := range virt_operator.ListMetrics() {
metrics = append(metrics, newMetric(m))
}
## Developing new metrics
err = rules.SetupRules("")
checkError(err)
All metrics documented here are auto-generated and reflect exactly what is being
exposed. After developing new metrics or changing old ones please regenerate
this document.
`

for _, rule := range rules.ListRecordingRules() {
metrics = append(metrics, metric{
name: rule.GetOpts().Name,
description: rule.GetOpts().Help,
mType: string(rule.GetType()),
})
func main() {
if err := virtcontroller.SetupMetrics(nil, nil, nil, nil, nil, nil, nil, nil); err != nil {
panic(err)
}

return metrics
}

func newMetric(om operatormetrics.Metric) metric {
return metric{
name: om.GetOpts().Name,
description: om.GetOpts().Help,
mType: strings.Replace(string(om.GetType()), "Vec", "", 1),
if err := virtapi.SetupMetrics(); err != nil {
panic(err)
}
}

func parseMetricDesc(line string) (string, string) {
split := strings.Split(line, " ")
name := split[2]
split[3] = strings.Title(split[3])
description := strings.Join(split[3:], " ")
return name, description
}

func parseMetricType(scan *bufio.Scanner, name string) string {
for scan.Scan() {
typeLine := scan.Text()
if strings.HasPrefix(typeLine, "# TYPE ") {
split := strings.Split(typeLine, " ")
if split[2] == name {
return strings.Title(split[3])
}
}
if err := virtoperator.SetupMetrics(); err != nil {
panic(err)
}
return ""
}

const filter = "kubevirt_"

func parseVirtMetrics(r io.Reader, metrics *metricList) error {
scan := bufio.NewScanner(r)
for scan.Scan() {
helpLine := scan.Text()
if strings.HasPrefix(helpLine, "# HELP ") {
if strings.Contains(helpLine, filter) {
metName, metDesc := parseMetricDesc(helpLine)
metType := parseMetricType(scan, metName)
*metrics = append(*metrics, metric{name: metName, description: metDesc, mType: metType})
}
}
if err := virthandler.SetupMetrics("", "", 0, nil); err != nil {
panic(err)
}

if scan.Err() != nil {
return fmt.Errorf("failed to parse metrics from prometheus endpoint, %w", scan.Err())
if err := rules.SetupRules(""); err != nil {
panic(err)
}

sort.Sort(metrics)

// remove duplicates
for i := 0; i < len(*metrics)-1; i++ {
if (*metrics)[i].name == (*metrics)[i+1].name {
*metrics = append((*metrics)[:i], (*metrics)[i+1:]...)
i--
}
}
metricsList := operatormetrics.ListMetrics()
rulesList := rules.ListRecordingRules()

return nil
}
metricsList = append(metricsList, operatormetrics.NewGauge(operatormetrics.MetricOpts{
Name: "kubevirt_vmi_number_of_outdated",
Help: "Indication for the total number of VirtualMachineInstance workloads that are not running within the most up-to-date version of the virt-launcher environment.",
}))

func checkError(err error) {
if err != nil {
panic(err)
}
docsString := docs.BuildMetricsDocsWithCustomTemplate(metricsList, rulesList, tpl)
fmt.Print(docsString)
}

0 comments on commit 62268bb

Please sign in to comment.