diff --git a/docs/metrics.md b/docs/metrics.md index b65affe61bd6..fd3d0b000733 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -247,21 +247,19 @@ Metrics for the [Four Golden Signals](https://sre.google/sre-book/monitoring-dis #### `cronworkflows_concurrencypolicy_triggered` A counter of the number of times a CronWorkflow has triggered its `concurrencyPolicy` to limit the number of workflows running. - -| attribute | explanation | -|-----------|-------------| -| `name` | ⚠️ The name of the CronWorkflow | -| `namespace` | The namespace that the CronWorkflow is in | +| attribute | explanation | +|----------------------|----------------------------------------------------------------------------------| +| `name` | ⚠️ The name of the CronWorkflow | +| `namespace` | The namespace that the CronWorkflow is in | | `concurrency_policy` | The concurrency policy which was triggered, will be either `Forbid` or `Replace` | #### `cronworkflows_triggered_total` A counter of the total number of times a CronWorkflow has been triggered. Suppressed runs due to `concurrencyPolicy: Forbid` will not be counted. - -| attribute | explanation | -|-----------|-------------| -| `name` | ⚠️ The name of the CronWorkflow | +| attribute | explanation | +|-------------|-------------------------------------------| +| `name` | ⚠️ The name of the CronWorkflow | | `namespace` | The namespace that the CronWorkflow is in | #### `deprecated_feature` @@ -269,10 +267,9 @@ Suppressed runs due to `concurrencyPolicy: Forbid` will not be counted. Incidents of deprecated feature being used. Deprecated features are [explained here](deprecations.md). 🚨 This counter may go up much more than once for a single use of the feature. - -| attribute | explanation | -|-----------|-------------| -| `feature` | The name of the feature used | +| attribute | explanation | +|-------------|---------------------------------------| +| `feature` | The name of the feature used | | `namespace` | The namespace that the Workflow is in | `feature` will be one of: @@ -285,10 +282,9 @@ Deprecated features are [explained here](deprecations.md). #### `error_count` A counter of certain errors incurred by the controller by cause. - -| attribute | explanation | -|-----------|-------------| -| `cause` | The cause of the error | +| attribute | explanation | +|-----------|------------------------| +| `cause` | The cause of the error | The currently tracked specific errors are @@ -301,10 +297,9 @@ The currently tracked specific errors are A gauge of the number of workflows currently in the cluster in each phase. The `Running` count does not mean that a workflows pods are running, just that the controller has scheduled them. A workflow can be stuck in `Running` with pending pods for a long time. - -| attribute | explanation | -|-----------|-------------| -| `status` | Boolean: `true` or `false` | +| attribute | explanation | +|-----------|----------------------------| +| `status` | Boolean: `true` or `false` | #### `is_leader` @@ -313,18 +308,16 @@ A gauge indicating if this Controller is the [leader](high-availability.md#workf - `1` if leader or in standalone mode via [`LEADER_ELECTION_DISABLE=true`](environment-variables.md#controller). - `0` otherwise, indicating that this controller is a standby that is not currently running workflows. - This metric has no attributes. #### `k8s_request_duration` A histogram recording the API requests sent to the Kubernetes API. - -| attribute | explanation | -|-----------|-------------| -| `kind` | The kubernetes `kind` involved in the request such as `configmaps` | -| `verb` | The verb of the request, such as `Get` or `List` | -| `status_code` | The HTTP status code of the response | +| attribute | explanation | +|---------------|--------------------------------------------------------------------| +| `kind` | The kubernetes `kind` involved in the request such as `configmaps` | +| `verb` | The verb of the request, such as `Get` or `List` | +| `status_code` | The HTTP status code of the response | Default bucket sizes: 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 60, 180 This contains all the information contained in `k8s_request_total` along with timings. @@ -332,31 +325,27 @@ This contains all the information contained in `k8s_request_total` along with ti #### `k8s_request_total` A counter of the number of API requests sent to the Kubernetes API. - -| attribute | explanation | -|-----------|-------------| -| `kind` | The kubernetes `kind` involved in the request such as `configmaps` | -| `verb` | The verb of the request, such as `Get` or `List` | -| `status_code` | The HTTP status code of the response | +| attribute | explanation | +|---------------|--------------------------------------------------------------------| +| `kind` | The kubernetes `kind` involved in the request such as `configmaps` | +| `verb` | The verb of the request, such as `Get` or `List` | +| `status_code` | The HTTP status code of the response | This metric is calculable from `k8s_request_duration`, and it is suggested you just collect that metric instead. #### `log_messages` A count of log messages emitted by the controller by log level: `error`, `warn` and `info`. - -| attribute | explanation | -|-----------|-------------| -| `level` | The log level of the message | +| attribute | explanation | +|-----------|------------------------------| +| `level` | The log level of the message | #### `operation_duration_seconds` A histogram of durations of operations. An operation is a single workflow reconciliation loop within the workflow-controller. It's the time for the controller to process a single workflow after it has been read from the cluster and is a measure of the performance of the controller affected by the complexity of the workflow. - This metric has no attributes. - The environment variables `OPERATION_DURATION_METRIC_BUCKET_COUNT` and `MAX_OPERATION_TIME` configure the bucket sizes for this metric, unless they are specified using an `histogramBuckets` modifier in the `metricsConfig` block. #### `pod_missing` @@ -364,10 +353,9 @@ The environment variables `OPERATION_DURATION_METRIC_BUCKET_COUNT` and `MAX_OPER Incidents of pod missing. A counter of pods that were not seen - for example they are by being deleted by Kubernetes. You should only see this under high load. - -| attribute | explanation | -|-----------|-------------| -| `node_phase` | The phase that the pod's node was in | +| attribute | explanation | +|--------------------|----------------------------------------| +| `node_phase` | The phase that the pod's node was in | | `recently_started` | Boolean: was this pod started recently | `recently_started` is controlled by the [environment variable](environment-variables.md) `RECENTLY_STARTED_POD_DURATION` and defaults to 10 seconds. @@ -375,29 +363,26 @@ You should only see this under high load. #### `pod_pending_count` Total number of pods that started pending by reason. - -| attribute | explanation | -|-----------|-------------| -| `reason` | Summary of the kubernetes Reason for pending | -| `namespace` | The namespace that the pod is in | +| attribute | explanation | +|-------------|----------------------------------------------| +| `reason` | Summary of the kubernetes Reason for pending | +| `namespace` | The namespace that the pod is in | #### `pods_gauge` A gauge of the number of workflow created pods currently in the cluster in each phase. It is possible for a workflow to start, but no pods be running (for example cluster is too busy to run them). This metric sheds light on actual work being done. - -| attribute | explanation | -|-----------|-------------| -| `phase` | The phase that the pod is in | +| attribute | explanation | +|-----------|------------------------------| +| `phase` | The phase that the pod is in | #### `pods_total_count` Total number of pods that have entered each phase. - -| attribute | explanation | -|-----------|-------------| -| `phase` | The phase that the pod is in | +| attribute | explanation | +|-------------|----------------------------------| +| `phase` | The phase that the pod is in | | `namespace` | The namespace that the pod is in | This metric ignores the `PodInitializing` reason and does not count it. @@ -408,9 +393,8 @@ This is not directly controlled by the workflow controller, so it is possible fo A counter of additions to the work queues inside the controller. The rate of this shows how busy that area of the controller is - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|--------------|-----------------------| | `queue_name` | The name of the queue | Queues: @@ -427,9 +411,8 @@ This and associated metrics are all directly sourced from the [client-go workque A gauge of the current depth of the queues. If these get large then the workflow controller is not keeping up with the cluster. - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|--------------|-----------------------| | `queue_name` | The name of the queue | Queues: @@ -445,9 +428,8 @@ This and associated metrics are all directly sourced from the [client-go workque #### `queue_duration` A histogram of the time events in the queues are taking to be processed. - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|--------------|-----------------------| | `queue_name` | The name of the queue | Default bucket sizes: 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 60, 180 @@ -464,9 +446,8 @@ This and associated metrics are all directly sourced from the [client-go workque #### `queue_latency` A histogram of the time events in the queues are taking before they are processed. - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|--------------|-----------------------| | `queue_name` | The name of the queue | Default bucket sizes: 1, 5, 20, 60, 180 @@ -483,9 +464,8 @@ This and associated metrics are all directly sourced from the [client-go workque #### `queue_longest_running` A gauge of the number of seconds that this queue's longest running processor has been running for. - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|--------------|-----------------------| | `queue_name` | The name of the queue | Queues: @@ -501,9 +481,8 @@ This and associated metrics are all directly sourced from the [client-go workque #### `queue_retries` A counter of the number of times a message has been retried in the queue. - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|--------------|-----------------------| | `queue_name` | The name of the queue | Queues: @@ -519,9 +498,8 @@ This and associated metrics are all directly sourced from the [client-go workque #### `queue_unfinished_work` A gauge of the number of queue items that have not been processed yet. - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|--------------|-----------------------| | `queue_name` | The name of the queue | Queues: @@ -537,33 +515,30 @@ This and associated metrics are all directly sourced from the [client-go workque #### `total_count` A counter of workflows that have entered each phase for tracking them through their life-cycle, by namespace. - -| attribute | explanation | -|-----------|-------------| -| `phase` | The phase that the Workflow has entered | -| `namespace` | The namespace that the Workflow is in | +| attribute | explanation | +|-------------|-----------------------------------------| +| `phase` | The phase that the Workflow has entered | +| `namespace` | The namespace that the Workflow is in | #### `version` Build metadata for this Controller. - -| attribute | explanation | -|-----------|-------------| -| `version` | The version of Argo | -| `platform` | The [Go platform](https://go.dev/doc/install/source#environment) compiled for. Example: `linux/amd64` | -| `go_version` | Version of Go used | -| `build_date` | Build date | -| `compiler` | The compiler used. Example: `gc` | -| `git_commit` | The full Git SHA1 commit | -| `git_tree_state` | Whether the Git tree was `dirty` or `clean` when built | -| `git_tag` | The Git tag or `untagged` if it was not tagged | +| attribute | explanation | +|------------------|-------------------------------------------------------------------------------------------------------| +| `version` | The version of Argo | +| `platform` | The [Go platform](https://go.dev/doc/install/source#environment) compiled for. Example: `linux/amd64` | +| `go_version` | Version of Go used | +| `build_date` | Build date | +| `compiler` | The compiler used. Example: `gc` | +| `git_commit` | The full Git SHA1 commit | +| `git_tree_state` | Whether the Git tree was `dirty` or `clean` when built | +| `git_tag` | The Git tag or `untagged` if it was not tagged | #### `workers_busy_count` A gauge of queue workers that are busy. - -| attribute | explanation | -|-----------|-------------| +| attribute | explanation | +|---------------|-------------------| | `worker_type` | The type of queue | Worker Types: @@ -580,35 +555,31 @@ This and associated metrics are all directly sourced from the [client-go workque A gauge of the number of workflows with different conditions. This will tell you the number of workflows with running pods. - -| attribute | explanation | -|-----------|-------------| -| `type` | The type of condition, currently only `PodRunning` | -| `status` | Boolean: `true` or `false` | +| attribute | explanation | +|-----------|----------------------------------------------------| +| `type` | The type of condition, currently only `PodRunning` | +| `status` | Boolean: `true` or `false` | #### `workflowtemplate_runtime` A histogram of the runtime of workflows using `workflowTemplateRef` only. Counts both WorkflowTemplate and ClusterWorkflowTemplate usage. Records time between entering the `Running` phase and completion, so does not include any time in `Pending`. - -| attribute | explanation | -|-----------|-------------| -| `name` | ⚠️ The name of the WorkflowTemplate/ClusterWorkflowTemplate. | -| `namespace` | The namespace that the WorkflowTemplate is in | -| `cluster_scope` | A boolean set true if this is a ClusterWorkflowTemplate | +| attribute | explanation | +|-----------------|-------------------------------------------------------------| +| `name` | ⚠️ The name of the WorkflowTemplate/ClusterWorkflowTemplate. | +| `namespace` | The namespace that the WorkflowTemplate is in | +| `cluster_scope` | A boolean set true if this is a ClusterWorkflowTemplate | #### `workflowtemplate_triggered_total` A counter of workflows using `workflowTemplateRef` only, as they enter each phase. Counts both WorkflowTemplate and ClusterWorkflowTemplate usage. - -| attribute | explanation | -|-----------|-------------| -| `name` | ⚠️ The name of the WorkflowTemplate/ClusterWorkflowTemplate. | -| `namespace` | The namespace that the WorkflowTemplate is in | -| `cluster_scope` | A boolean set true if this is a ClusterWorkflowTemplate | - +| attribute | explanation | +|-----------------|-------------------------------------------------------------| +| `name` | ⚠️ The name of the WorkflowTemplate/ClusterWorkflowTemplate. | +| `namespace` | The namespace that the WorkflowTemplate is in | +| `cluster_scope` | A boolean set true if this is a ClusterWorkflowTemplate | ### Metric types diff --git a/go.mod b/go.mod index bb5df27b3378..12349fcf4837 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/klauspost/pgzip v1.2.6 github.com/minio/minio-go/v7 v7.0.77 + github.com/nao1215/markdown v0.6.0 github.com/prometheus/client_golang v1.19.1 github.com/prometheus/common v0.55.0 github.com/robfig/cron/v3 v3.0.1 @@ -117,13 +118,17 @@ require ( github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/karrick/godirwalk v1.17.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/segmentio/fasthash v1.0.3 // indirect diff --git a/go.sum b/go.sum index 60644ad71a5b..30d9f55348a1 100644 --- a/go.sum +++ b/go.sum @@ -552,6 +552,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI= +github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= @@ -613,6 +615,9 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= @@ -653,6 +658,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nao1215/markdown v0.6.0 h1:kqhrC47K434YA1jMTUwJwSV/hla8ifN3NzehMEffI/E= +github.com/nao1215/markdown v0.6.0/go.mod h1:ObBhnNduWwPN+bu4dtv4JoLRt57ONla7l//03iHIVhY= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -660,6 +667,8 @@ github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -710,6 +719,9 @@ github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qq github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= diff --git a/util/telemetry/builder/docs.go b/util/telemetry/builder/docs.go index 475242267a39..70e1bb8bd7c8 100644 --- a/util/telemetry/builder/docs.go +++ b/util/telemetry/builder/docs.go @@ -3,9 +3,12 @@ package main import ( "bytes" "fmt" + "io" "os" "slices" "strings" + + md "github.com/nao1215/markdown" ) func createMetricsDocs(filename string, metrics *metricsList, attribs *attributesList) { @@ -59,43 +62,49 @@ func createMetricsDocs(filename string, metrics *metricsList, attribs *attribute func metricsDocsLines(metrics *metricsList, attribs *attributesList) string { var out bytes.Buffer - fmt.Fprintf(&out, "\n") + outWriter := io.Writer(&out) + markdown := md.NewMarkdown(outWriter) for _, metric := range *metrics { - fmt.Fprintf(&out, "#### `%s`\n\n", metric.displayName()) - fmt.Fprintf(&out, "%s.\n", metric.Description) + markdown.PlainText("") + markdown.H4(md.Code(metric.displayName())) + markdown.PlainText("") + markdown.PlainTextf("%s.", metric.Description) if metric.ExtendedDescription != "" { - fmt.Fprintf(&out, "%s\n", strings.Trim(metric.ExtendedDescription, " \n\t\r")) + markdown.PlainText(strings.Trim(metric.ExtendedDescription, " \n\t\r")) } - fmt.Fprintf(&out, "\n") if len(metric.Attributes) > 0 { - fmt.Fprintf(&out, "| attribute | explanation |\n") - fmt.Fprintf(&out, "|-----------|-------------|\n") + rows := [][]string{} for _, metricAttrib := range metric.Attributes { if attrib := getAttribByName(metricAttrib.Name, attribs); attrib != nil { // Failure should already be recorded as an error - fmt.Fprintf(&out, "| `%s` | %s |\n", attrib.displayName(), attrib.Description) + rows = append(rows, []string{md.Code(attrib.displayName()), attrib.Description}) } } - fmt.Fprintf(&out, "\n") + + markdown.CustomTable(md.TableSet{ + Header: []string{"attribute", "explanation"}, + Rows: rows, + }, md.TableOptions{AutoWrapText: false}, + ) } else { - fmt.Fprintf(&out, "This metric has no attributes.\n\n") + markdown.PlainText("This metric has no attributes.") } if len(metric.DefaultBuckets) > 0 { - fmt.Fprintf(&out, "Default bucket sizes: ") + buckets := "" for i, bucket := range metric.DefaultBuckets { if i != 0 { - fmt.Fprintf(&out, ", ") + buckets = fmt.Sprintf("%s, ", buckets) } - fmt.Fprintf(&out, "%g", bucket) + buckets = fmt.Sprintf("%s%g", buckets, bucket) } - fmt.Fprintf(&out, "\n") + markdown.PlainTextf("Default bucket sizes: %s", buckets) } if metric.Notes != "" { - fmt.Fprintf(&out, "%s\n", strings.Trim(metric.Notes, " \n\t\r")) - fmt.Fprintf(&out, "\n") + markdown.PlainText(strings.Trim(metric.Notes, " \n\t\r")) } } + markdown.Build() return strings.TrimSuffix(out.String(), "\n") }