Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update metrics documentation generator
Signed-off-by: machadovilaca <machadovilaca@gmail.com>
- Loading branch information
1 parent
15c23f9
commit 8bd0228
Showing
9 changed files
with
274 additions
and
355 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
Oops, something went wrong.