2022-06-27 06:02:21 +00:00
|
|
|
//
|
|
|
|
// DISCLAIMER
|
|
|
|
//
|
2023-02-24 11:16:35 +00:00
|
|
|
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
|
2022-06-27 06:02:21 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
|
|
//
|
|
|
|
|
|
|
|
package internal
|
|
|
|
|
|
|
|
import (
|
|
|
|
_ "embed"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
"github.com/arangodb/kube-arangodb/internal/md"
|
2022-12-22 09:49:51 +00:00
|
|
|
"github.com/arangodb/kube-arangodb/pkg/util/strings"
|
2022-06-27 06:02:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
//go:embed metrics.go.tmpl
|
|
|
|
var metricsGoTemplate []byte
|
|
|
|
|
|
|
|
//go:embed metrics.item.go.tmpl
|
|
|
|
var metricsItemGoTemplate []byte
|
|
|
|
|
|
|
|
//go:embed metrics.item.tmpl
|
|
|
|
var metricItemTemplate []byte
|
|
|
|
|
|
|
|
//go:embed metrics.yaml
|
|
|
|
var metricsData []byte
|
|
|
|
|
|
|
|
type MetricsDoc struct {
|
|
|
|
Destination string `json:"destination" yaml:"destination"`
|
|
|
|
Documentation string `json:"documentation" yaml:"documentation"`
|
|
|
|
|
|
|
|
Namespaces Namespaces `json:"namespaces" yaml:"namespaces"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Namespaces map[string]Groups
|
|
|
|
|
|
|
|
func (n Namespaces) Keys() []string {
|
|
|
|
r := make([]string, 0, len(n))
|
|
|
|
|
|
|
|
for k := range n {
|
|
|
|
r = append(r, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(r)
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
type Groups map[string]Metrics
|
|
|
|
|
|
|
|
func (n Groups) Keys() []string {
|
|
|
|
r := make([]string, 0, len(n))
|
|
|
|
|
|
|
|
for k := range n {
|
|
|
|
r = append(r, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(r)
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
type Metrics map[string]Metric
|
|
|
|
|
|
|
|
func (n Metrics) Keys() []string {
|
|
|
|
r := make([]string, 0, len(n))
|
|
|
|
|
|
|
|
for k := range n {
|
|
|
|
r = append(r, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(r)
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
type Metric struct {
|
|
|
|
Description string `json:"description" yaml:"description"`
|
|
|
|
Type string `json:"type" yaml:"type"`
|
|
|
|
ShortDescription string `json:"shortDescription" yaml:"shortDescription"`
|
|
|
|
|
|
|
|
Labels []Label `json:"labels" yaml:"labels"`
|
|
|
|
AlertingRules []Alerting `json:"alertingRules" yaml:"alertingRules"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Alerting struct {
|
|
|
|
Priority string `json:"priority" yaml:"priority"`
|
|
|
|
Query string `json:"query" yaml:"query"`
|
|
|
|
Description string `json:"description" yaml:"description"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Label struct {
|
2022-06-30 19:58:13 +00:00
|
|
|
Key string `json:"key" yaml:"key"`
|
|
|
|
Description string `json:"description" yaml:"description"`
|
|
|
|
Type *string `json:"type" yaml:"type"`
|
2022-06-27 06:02:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func GenerateMetricsDocumentation(root string, in MetricsDoc) error {
|
|
|
|
docsRoot := path.Join(root, in.Documentation)
|
|
|
|
goFilesRoot := path.Join(root, in.Destination)
|
|
|
|
|
|
|
|
if _, err := os.Stat(docsRoot); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
if err := os.MkdirAll(docsRoot, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := os.Stat(goFilesRoot); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
if err := os.MkdirAll(goFilesRoot, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := generateMetricsREADME(docsRoot, in); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := generateMetricsGO(goFilesRoot, in); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateMetricFile(root, name string, m Metric) error {
|
|
|
|
key := md.NewColumn("Label", md.ColumnCenterAlign)
|
|
|
|
description := md.NewColumn("Description", md.ColumnLeftAlign)
|
|
|
|
priority := md.NewColumn("Priority", md.ColumnCenterAlign)
|
|
|
|
query := md.NewColumn("Query", md.ColumnCenterAlign)
|
|
|
|
t := md.NewTable(
|
|
|
|
key,
|
|
|
|
description,
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, l := range m.Labels {
|
|
|
|
if err := t.AddRow(map[md.Column]string{
|
|
|
|
key: l.Key,
|
|
|
|
description: l.Description,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ta := md.NewTable(
|
|
|
|
priority,
|
|
|
|
query,
|
|
|
|
description,
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, l := range m.AlertingRules {
|
|
|
|
if err := ta.AddRow(map[md.Column]string{
|
|
|
|
priority: l.Priority,
|
|
|
|
query: l.Query,
|
|
|
|
description: l.Description,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
q, err := template.New("metrics").Parse(string(metricItemTemplate))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := os.OpenFile(path.Join(root, fmt.Sprintf("%s.md", name)), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := q.Execute(out, map[string]interface{}{
|
|
|
|
"name": name,
|
|
|
|
"type": m.Type,
|
|
|
|
"description": m.Description,
|
|
|
|
"labels_table": t.Render(),
|
|
|
|
"labels": len(m.Labels) > 0,
|
|
|
|
"alerting_table": ta.Render(),
|
|
|
|
"alerting": len(m.AlertingRules) > 0,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := out.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateMetricsREADME(root string, in MetricsDoc) error {
|
|
|
|
name := md.NewColumn("Name", md.ColumnCenterAlign)
|
|
|
|
ns := md.NewColumn("Namespace", md.ColumnCenterAlign)
|
|
|
|
group := md.NewColumn("Group", md.ColumnCenterAlign)
|
|
|
|
typeCol := md.NewColumn("Type", md.ColumnCenterAlign)
|
|
|
|
description := md.NewColumn("Description", md.ColumnLeftAlign)
|
|
|
|
t := md.NewTable(
|
|
|
|
name,
|
|
|
|
ns,
|
|
|
|
group,
|
|
|
|
typeCol,
|
|
|
|
description,
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, namespace := range in.Namespaces.Keys() {
|
|
|
|
for _, g := range in.Namespaces[namespace].Keys() {
|
|
|
|
for _, metric := range in.Namespaces[namespace][g].Keys() {
|
|
|
|
mname := fmt.Sprintf("%s_%s_%s", namespace, g, metric)
|
|
|
|
rname := fmt.Sprintf("[%s](./%s.md)", mname, mname)
|
|
|
|
|
|
|
|
details := in.Namespaces[namespace][g][metric]
|
|
|
|
|
|
|
|
if err := t.AddRow(map[md.Column]string{
|
|
|
|
name: rname,
|
|
|
|
ns: namespace,
|
|
|
|
group: g,
|
|
|
|
description: details.ShortDescription,
|
|
|
|
typeCol: details.Type,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := generateMetricFile(root, mname, details); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-24 08:29:35 +00:00
|
|
|
if err := md.ReplaceSectionsInFile(path.Join(root, "README.md"), map[string]string{
|
|
|
|
"metricsTable": md.WrapWithNewLines(t.Render()),
|
2022-06-27 06:02:21 +00:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateLabels(labels []Label) string {
|
|
|
|
if len(labels) == 0 {
|
|
|
|
return "nil"
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := make([]string, len(labels))
|
|
|
|
|
|
|
|
for id := range labels {
|
|
|
|
parts[id] = fmt.Sprintf("`%s`", labels[id].Key)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("[]string{%s}", strings.Join(parts, ", "))
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateMetricsGO(root string, in MetricsDoc) error {
|
|
|
|
i, err := template.New("metrics").Parse(string(metricsItemGoTemplate))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, namespace := range in.Namespaces.Keys() {
|
|
|
|
for _, g := range in.Namespaces[namespace].Keys() {
|
|
|
|
for _, metric := range in.Namespaces[namespace][g].Keys() {
|
|
|
|
details := in.Namespaces[namespace][g][metric]
|
|
|
|
|
|
|
|
mname := fmt.Sprintf("%s_%s_%s", namespace, g, metric)
|
|
|
|
|
|
|
|
out, err := os.OpenFile(path.Join(root, fmt.Sprintf("%s.go", mname)), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := strings.Split(mname, "_")
|
|
|
|
tparts := strings.Split(strings.Title(strings.Join(parts, " ")), " ")
|
|
|
|
|
|
|
|
fnameParts := make([]string, len(parts))
|
|
|
|
for id := range parts {
|
|
|
|
if id == 0 {
|
|
|
|
fnameParts[id] = parts[id]
|
|
|
|
} else {
|
|
|
|
fnameParts[id] = tparts[id]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-30 19:58:13 +00:00
|
|
|
var keys []string
|
|
|
|
var params []string
|
|
|
|
|
|
|
|
params = append(params, "value float64")
|
|
|
|
keys = append(keys, "value")
|
|
|
|
|
|
|
|
for _, label := range details.Labels {
|
2022-07-07 12:09:17 +00:00
|
|
|
v := strings.Split(strings.ToLower(label.Key), "_")
|
|
|
|
for id := range v {
|
|
|
|
if id == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
v[id] = strings.Title(v[id])
|
|
|
|
}
|
|
|
|
|
|
|
|
k := strings.Join(v, "")
|
|
|
|
|
2022-06-30 19:58:13 +00:00
|
|
|
keys = append(keys, k)
|
|
|
|
|
|
|
|
if t := label.Type; t != nil {
|
|
|
|
params = append(params, fmt.Sprintf("%s %s", k, *t))
|
|
|
|
} else {
|
|
|
|
params = append(params, fmt.Sprintf("%s string", k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-27 06:02:21 +00:00
|
|
|
if err := i.Execute(out, map[string]interface{}{
|
|
|
|
"name": mname,
|
|
|
|
"fname": strings.Join(fnameParts, ""),
|
|
|
|
"ename": strings.Join(tparts, ""),
|
|
|
|
"shortDescription": details.ShortDescription,
|
|
|
|
"labels": generateLabels(details.Labels),
|
2022-06-30 19:58:13 +00:00
|
|
|
"type": details.Type,
|
|
|
|
"fparams": strings.Join(params, ", "),
|
|
|
|
"fkeys": strings.Join(keys, ", "),
|
2022-06-27 06:02:21 +00:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := out.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := os.OpenFile(path.Join(root, "metrics.go"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
q, err := template.New("metrics").Parse(string(metricsGoTemplate))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := q.Execute(out, nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := out.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|