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 Apr 16, 2024
1 parent a683add commit 6b0a987
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 355 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_memory_delta_from_requested_bytes
The delta between the pod with highest memory working set or rss and its requested memory for each container, virt-controller, virt-handler, virt-api and virt-operator. Type: Gauge.

Expand Down Expand Up @@ -145,7 +136,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 @@ -193,7 +184,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 @@ -283,6 +274,7 @@ Request latency in seconds. Broken down by verb and URL. Type: Histogram.
Number of HTTP requests, partitioned by status code, method, and host. Type: Counter.

## 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
14 changes: 2 additions & 12 deletions tools/doc-generator/BUILD.bazel
Expand Up @@ -2,27 +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
272 changes: 38 additions & 234 deletions tools/doc-generator/doc-generator.go
@@ -1,265 +1,69 @@
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_handler "kubevirt.io/kubevirt/pkg/monitoring/metrics/virt-handler"
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()
const tpl = `# KubeVirt metrics
fmt.Fprint(newFile, opening)
metrics.writeToFile(newFile)
{{- range . }}
fmt.Fprint(newFile, footer)
{{ $deprecatedVersion := "" -}}
{{- with index .ExtraFields "DeprecatedVersion" -}}
{{- $deprecatedVersion = printf " in %s" . -}}
{{- end -}}
}

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
{{- $stabilityLevel := "" -}}
{{- if and (.ExtraFields.StabilityLevel) (ne .ExtraFields.StabilityLevel "STABLE") -}}
{{- $stabilityLevel = printf "[%s%s] " .ExtraFields.StabilityLevel $deprecatedVersion -}}
{{- end -}}
// Len implements sort.Interface.Len
func (m metricList) Len() int {
return len(m)
}
### {{ .Name }}
{{ print $stabilityLevel }}{{ .Help }} Type: {{ .Type -}}.
// Less implements sort.Interface.Less
func (m metricList) Less(i, j int) bool {
return m[i].name < m[j].name
}

// Swap implements sort.Interface.Swap
func (m metricList) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}

func (m metricList) writeToFile(newFile io.WriteCloser) {
for _, met := range m {
met.writeToFile(newFile)
}
}

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",
},
}
{{- end }}
metrics = append(metrics, getVirtControllerMetrics()...)
metrics = append(metrics, getComponentMetrics(virt_api.SetupMetrics, virt_api.ListMetrics)...)
metrics = append(metrics, getComponentMetrics(virt_operator.SetupMetrics, virt_operator.ListMetrics)...)
metrics = append(metrics, getComponentMetrics(virt_handler.SetupMetrics, virt_handler.ListMetrics)...)
## 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()),
})
}

return metrics
}

func getVirtControllerMetrics() metricList {
err := virt_controller.SetupMetrics(nil, nil, nil, nil, nil, nil, nil, nil)
checkError(err)
return listComponentMetrics(virt_controller.ListMetrics)
}

func getComponentMetrics(setup func() error, list func() []operatormetrics.Metric) metricList {
err := setup()
checkError(err)
return listComponentMetrics(list)
}

func listComponentMetrics(list func() []operatormetrics.Metric) metricList {
metrics := metricList{}

for _, m := range list() {
metrics = append(metrics, newMetric(m))
}

err := operatormetrics.CleanRegistry()
checkError(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),
func main() {
if err := virtcontroller.SetupMetrics(nil, nil, nil, nil, nil, nil, nil, nil); 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 := virtapi.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 := virtoperator.SetupMetrics(); err != nil {
panic(err)
}

if scan.Err() != nil {
return fmt.Errorf("failed to parse metrics from prometheus endpoint, %w", scan.Err())
if err := virthandler.SetupMetrics("", "", 0, nil); 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--
}
if err := rules.SetupRules(""); err != nil {
panic(err)
}

return nil
}
metricsList := operatormetrics.ListMetrics()
rulesList := rules.ListRecordingRules()

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

0 comments on commit 6b0a987

Please sign in to comment.