From 1922db0586b0ab67b591a1a677df74b9f23084fd Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Sun, 12 Mar 2023 00:18:33 +0100 Subject: [PATCH] Document command line tools Signed-off-by: Julien Pivotto --- Makefile | 5 + cmd/prometheus/main.go | 10 + cmd/prometheus/main_test.go | 29 ++ cmd/promtool/main.go | 5 + cmd/promtool/main_test.go | 31 ++ docs/command-line/index.md | 4 + docs/command-line/prometheus.md | 59 ++++ docs/command-line/promtool.md | 536 ++++++++++++++++++++++++++++++++ docs/feature_flags.md | 2 +- docs/migration.md | 2 +- docs/stability.md | 2 +- util/documentcli/documentcli.go | 252 +++++++++++++++ 12 files changed, 934 insertions(+), 3 deletions(-) create mode 100644 docs/command-line/index.md create mode 100644 docs/command-line/prometheus.md create mode 100644 docs/command-line/promtool.md create mode 100644 util/documentcli/documentcli.go diff --git a/Makefile b/Makefile index e345c1a88..3877ee719 100644 --- a/Makefile +++ b/Makefile @@ -133,3 +133,8 @@ bench_tsdb: $(PROMU) @$(GO) tool pprof --alloc_space -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/mem.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/memprof.alloc.svg @$(GO) tool pprof -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/block.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/blockprof.svg @$(GO) tool pprof -svg $(PROMTOOL) $(TSDB_BENCHMARK_OUTPUT_DIR)/mutex.prof > $(TSDB_BENCHMARK_OUTPUT_DIR)/mutexprof.svg + +.PHONY: cli-documentation +cli-documentation: + $(GO) run ./cmd/prometheus/ --write-documentation > docs/command-line/prometheus.md + $(GO) run ./cmd/promtool/ write-documentation > docs/command-line/promtool.md diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index cc72a1871..f4f6af20d 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -70,6 +70,7 @@ import ( "github.com/prometheus/prometheus/tracing" "github.com/prometheus/prometheus/tsdb" "github.com/prometheus/prometheus/tsdb/agent" + "github.com/prometheus/prometheus/util/documentcli" "github.com/prometheus/prometheus/util/logging" prom_runtime "github.com/prometheus/prometheus/util/runtime" "github.com/prometheus/prometheus/web" @@ -413,6 +414,15 @@ func main() { promlogflag.AddFlags(a, &cfg.promlogConfig) + a.Flag("write-documentation", "Generate command line documentation. Internal use.").Hidden().Action(func(ctx *kingpin.ParseContext) error { + if err := documentcli.GenerateMarkdown(a.Model(), os.Stdout); err != nil { + os.Exit(1) + return err + } + os.Exit(0) + return nil + }).Bool() + _, err := a.Parse(os.Args[1:]) if err != nil { fmt.Fprintln(os.Stderr, fmt.Errorf("Error parsing commandline arguments: %w", err)) diff --git a/cmd/prometheus/main_test.go b/cmd/prometheus/main_test.go index 9fbca5c33..26d11e21e 100644 --- a/cmd/prometheus/main_test.go +++ b/cmd/prometheus/main_test.go @@ -23,6 +23,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "syscall" "testing" @@ -483,3 +484,31 @@ func TestModeSpecificFlags(t *testing.T) { }) } } + +func TestDocumentation(t *testing.T) { + if runtime.GOOS == "windows" { + t.SkipNow() + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cmd := exec.CommandContext(ctx, promPath, "-test.main", "--write-documentation") + + var stdout bytes.Buffer + cmd.Stdout = &stdout + + if err := cmd.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + if exitError.ExitCode() != 0 { + fmt.Println("Command failed with non-zero exit code") + } + } + } + + generatedContent := strings.ReplaceAll(stdout.String(), filepath.Base(promPath), strings.TrimSuffix(filepath.Base(promPath), ".test")) + + expectedContent, err := os.ReadFile(filepath.Join("..", "..", "docs", "command-line", "prometheus.md")) + require.NoError(t, err) + + require.Equal(t, string(expectedContent), generatedContent, "Generated content does not match documentation. Hint: run `make cli-documentation`.") +} diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index 7a8ed08ff..3988957ef 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -59,6 +59,7 @@ import ( _ "github.com/prometheus/prometheus/plugins" // Register plugins. "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/scrape" + "github.com/prometheus/prometheus/util/documentcli" ) const ( @@ -223,6 +224,8 @@ func main() { featureList := app.Flag("enable-feature", "Comma separated feature names to enable (only PromQL related and no-default-scrape-port). See https://prometheus.io/docs/prometheus/latest/feature_flags/ for the options and more details.").Default("").Strings() + documentationCmd := app.Command("write-documentation", "Generate command line documentation. Internal use.").Hidden() + parsedCmd := kingpin.MustParse(app.Parse(os.Args[1:])) var p printer @@ -329,6 +332,8 @@ func main() { case importRulesCmd.FullCommand(): os.Exit(checkErr(importRules(serverURL, httpRoundTripper, *importRulesStart, *importRulesEnd, *importRulesOutputDir, *importRulesEvalInterval, *maxBlockDuration, *importRulesFiles...))) + case documentationCmd.FullCommand(): + os.Exit(checkErr(documentcli.GenerateMarkdown(app.Model(), os.Stdout))) } } diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go index f8254bdbb..6cfa48798 100644 --- a/cmd/promtool/main_test.go +++ b/cmd/promtool/main_test.go @@ -14,6 +14,8 @@ package main import ( + "bytes" + "context" "errors" "fmt" "net/http" @@ -21,6 +23,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "runtime" "strings" "syscall" @@ -433,3 +436,31 @@ func TestExitCodes(t *testing.T) { }) } } + +func TestDocumentation(t *testing.T) { + if runtime.GOOS == "windows" { + t.SkipNow() + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + cmd := exec.CommandContext(ctx, promtoolPath, "-test.main", "write-documentation") + + var stdout bytes.Buffer + cmd.Stdout = &stdout + + if err := cmd.Run(); err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + if exitError.ExitCode() != 0 { + fmt.Println("Command failed with non-zero exit code") + } + } + } + + generatedContent := strings.ReplaceAll(stdout.String(), filepath.Base(promtoolPath), strings.TrimSuffix(filepath.Base(promtoolPath), ".test")) + + expectedContent, err := os.ReadFile(filepath.Join("..", "..", "docs", "command-line", "promtool.md")) + require.NoError(t, err) + + require.Equal(t, string(expectedContent), generatedContent, "Generated content does not match documentation. Hint: run `make cli-documentation`.") +} diff --git a/docs/command-line/index.md b/docs/command-line/index.md new file mode 100644 index 000000000..53786fbb2 --- /dev/null +++ b/docs/command-line/index.md @@ -0,0 +1,4 @@ +--- +title: Command Line +sort_rank: 9 +--- diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md new file mode 100644 index 000000000..46f286e00 --- /dev/null +++ b/docs/command-line/prometheus.md @@ -0,0 +1,59 @@ +--- +title: prometheus +--- + +# prometheus + +The Prometheus monitoring server + + + +## Flags + +| Flag | Description | Default | +| --- | --- | --- | +| -h, --help | Show context-sensitive help (also try --help-long and --help-man). | | +| --version | Show application version. | | +| --config.file | Prometheus configuration file path. | `prometheus.yml` | +| --web.listen-address | Address to listen on for UI, API, and telemetry. | `0.0.0.0:9090` | +| --web.config.file | [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication. | | +| --web.read-timeout | Maximum duration before timing out read of the request, and closing idle connections. | `5m` | +| --web.max-connections | Maximum number of simultaneous connections. | `512` | +| --web.external-url | The URL under which Prometheus is externally reachable (for example, if Prometheus is served via a reverse proxy). Used for generating relative and absolute links back to Prometheus itself. If the URL has a path portion, it will be used to prefix all HTTP endpoints served by Prometheus. If omitted, relevant URL components will be derived automatically. | | +| --web.route-prefix | Prefix for the internal routes of web endpoints. Defaults to path of --web.external-url. | | +| --web.user-assets | Path to static asset directory, available at /user. | | +| --web.enable-lifecycle | Enable shutdown and reload via HTTP request. | `false` | +| --web.enable-admin-api | Enable API endpoints for admin control actions. | `false` | +| --web.enable-remote-write-receiver | Enable API endpoint accepting remote write requests. | `false` | +| --web.console.templates | Path to the console template directory, available at /consoles. | `consoles` | +| --web.console.libraries | Path to the console library directory. | `console_libraries` | +| --web.page-title | Document title of Prometheus instance. | `Prometheus Time Series Collection and Processing Server` | +| --web.cors.origin | Regex for CORS origin. It is fully anchored. Example: 'https?://(domain1|domain2)\.com' | `.*` | +| --storage.tsdb.path | Base path for metrics storage. Use with server mode only. | `data/` | +| --storage.tsdb.retention | [DEPRECATED] How long to retain samples in storage. This flag has been deprecated, use "storage.tsdb.retention.time" instead. Use with server mode only. | | +| --storage.tsdb.retention.time | How long to retain samples in storage. When this flag is set it overrides "storage.tsdb.retention". If neither this flag nor "storage.tsdb.retention" nor "storage.tsdb.retention.size" is set, the retention time defaults to 15d. Units Supported: y, w, d, h, m, s, ms. Use with server mode only. | | +| --storage.tsdb.retention.size | Maximum number of bytes that can be stored for blocks. A unit is required, supported units: B, KB, MB, GB, TB, PB, EB. Ex: "512MB". Based on powers-of-2, so 1KB is 1024B. Use with server mode only. | | +| --storage.tsdb.no-lockfile | Do not create lockfile in data directory. Use with server mode only. | `false` | +| --storage.tsdb.head-chunks-write-queue-size | Size of the queue through which head chunks are written to the disk to be m-mapped, 0 disables the queue completely. Experimental. Use with server mode only. | `0` | +| --storage.agent.path | Base path for metrics storage. Use with agent mode only. | `data-agent/` | +| --storage.agent.wal-compression | Compress the agent WAL. Use with agent mode only. | `true` | +| --storage.agent.retention.min-time | Minimum age samples may be before being considered for deletion when the WAL is truncated Use with agent mode only. | | +| --storage.agent.retention.max-time | Maximum age samples may be before being forcibly deleted when the WAL is truncated Use with agent mode only. | | +| --storage.agent.no-lockfile | Do not create lockfile in data directory. Use with agent mode only. | `false` | +| --storage.remote.flush-deadline | How long to wait flushing sample on shutdown or config reload. | `1m` | +| --storage.remote.read-sample-limit | Maximum overall number of samples to return via the remote read interface, in a single query. 0 means no limit. This limit is ignored for streamed response types. Use with server mode only. | `5e7` | +| --storage.remote.read-concurrent-limit | Maximum number of concurrent remote read calls. 0 means no limit. Use with server mode only. | `10` | +| --storage.remote.read-max-bytes-in-frame | Maximum number of bytes in a single frame for streaming remote read response types before marshalling. Note that client might have limit on frame size as well. 1MB as recommended by protobuf by default. Use with server mode only. | `1048576` | +| --rules.alert.for-outage-tolerance | Max time to tolerate prometheus outage for restoring "for" state of alert. Use with server mode only. | `1h` | +| --rules.alert.for-grace-period | Minimum duration between alert and restored "for" state. This is maintained only for alerts with configured "for" time greater than grace period. Use with server mode only. | `10m` | +| --rules.alert.resend-delay | Minimum amount of time to wait before resending an alert to Alertmanager. Use with server mode only. | `1m` | +| --alertmanager.notification-queue-capacity | The capacity of the queue for pending Alertmanager notifications. Use with server mode only. | `10000` | +| --query.lookback-delta | The maximum lookback duration for retrieving metrics during expression evaluations and federation. Use with server mode only. | `5m` | +| --query.timeout | Maximum time a query may take before being aborted. Use with server mode only. | `2m` | +| --query.max-concurrency | Maximum number of queries executed concurrently. Use with server mode only. | `20` | +| --query.max-samples | Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return. Use with server mode only. | `50000000` | +| --enable-feature | Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. | | +| --log.level | Only log messages with the given severity or above. One of: [debug, info, warn, error] | `info` | +| --log.format | Output format of log messages. One of: [logfmt, json] | `logfmt` | + + diff --git a/docs/command-line/promtool.md b/docs/command-line/promtool.md new file mode 100644 index 000000000..42d853b85 --- /dev/null +++ b/docs/command-line/promtool.md @@ -0,0 +1,536 @@ +--- +title: promtool +--- + +# promtool + +Tooling for the Prometheus monitoring system. + + + +## Flags + +| Flag | Description | +| --- | --- | +| -h, --help | Show context-sensitive help (also try --help-long and --help-man). | +| --version | Show application version. | +| --enable-feature | Comma separated feature names to enable (only PromQL related and no-default-scrape-port). See https://prometheus.io/docs/prometheus/latest/feature_flags/ for the options and more details. | + + + + +## Commands + +| Command | Description | +| --- | --- | +| help | Show help. | +| check | Check the resources for validity. | +| query | Run query against a Prometheus server. | +| debug | Fetch debug information. | +| test | Unit testing. | +| tsdb | Run tsdb commands. | + + + + +### `promtool help` + +Show help. + + + +#### Arguments + +| Argument | Description | +| --- | --- | +| command | Show help on command. | + + + + +### `promtool check` + +Check the resources for validity. + + + +#### Flags + +| Flag | Description | +| --- | --- | +| --extended | Print extended information related to the cardinality of the metrics. | + + + + +##### `promtool check service-discovery` + +Perform service discovery for the given job name and report the results, including relabeling. + + + +###### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| --timeout | The time to wait for discovery results. | `30s` | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| config-file | The prometheus config file. | Yes | +| job | The job to run service discovery for. | Yes | + + + + +##### `promtool check config` + +Check if the config files are valid or not. + + + +###### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| --syntax-only | Only check the config file syntax, ignoring file and content validation referenced in the config | | +| --lint | Linting checks to apply to the rules specified in the config. Available options are: all, duplicate-rules, none. Use --lint=none to disable linting | `duplicate-rules` | +| --lint-fatal | Make lint errors exit with exit code 3. | `false` | +| --agent | Check config file for Prometheus in Agent mode. | | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| config-files | The config files to check. | Yes | + + + + +##### `promtool check web-config` + +Check if the web config files are valid or not. + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| web-config-files | The config files to check. | Yes | + + + + +##### `promtool check rules` + +Check if the rule files are valid or not. + + + +###### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| --lint | Linting checks to apply. Available options are: all, duplicate-rules, none. Use --lint=none to disable linting | `duplicate-rules` | +| --lint-fatal | Make lint errors exit with exit code 3. | `false` | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| rule-files | The rule files to check. | Yes | + + + + +##### `promtool check metrics` + +Pass Prometheus metrics over stdin to lint them for consistency and correctness. + +examples: + +$ cat metrics.prom | promtool check metrics + +$ curl -s http://localhost:9090/metrics | promtool check metrics + + + +### `promtool query` + +Run query against a Prometheus server. + + + +#### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| -o, --format | Output format of the query. | `promql` | +| --http.config.file | HTTP client configuration file for promtool to connect to Prometheus. | | + + + + +##### `promtool query instant` + +Run instant query. + + + +###### Flags + +| Flag | Description | +| --- | --- | +| --time | Query evaluation time (RFC3339 or Unix timestamp). | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| server | Prometheus server to query. | Yes | +| expr | PromQL query expression. | Yes | + + + + +##### `promtool query range` + +Run range query. + + + +###### Flags + +| Flag | Description | +| --- | --- | +| --header | Extra headers to send to server. | +| --start | Query range start time (RFC3339 or Unix timestamp). | +| --end | Query range end time (RFC3339 or Unix timestamp). | +| --step | Query step size (duration). | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| server | Prometheus server to query. | Yes | +| expr | PromQL query expression. | Yes | + + + + +##### `promtool query series` + +Run series query. + + + +###### Flags + +| Flag | Description | +| --- | --- | +| --match | Series selector. Can be specified multiple times. | +| --start | Start time (RFC3339 or Unix timestamp). | +| --end | End time (RFC3339 or Unix timestamp). | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| server | Prometheus server to query. | Yes | + + + + +##### `promtool query labels` + +Run labels query. + + + +###### Flags + +| Flag | Description | +| --- | --- | +| --start | Start time (RFC3339 or Unix timestamp). | +| --end | End time (RFC3339 or Unix timestamp). | +| --match | Series selector. Can be specified multiple times. | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| server | Prometheus server to query. | Yes | +| name | Label name to provide label values for. | Yes | + + + + +### `promtool debug` + +Fetch debug information. + + + +##### `promtool debug pprof` + +Fetch profiling debug information. + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| server | Prometheus server to get pprof files from. | Yes | + + + + +##### `promtool debug metrics` + +Fetch metrics debug information. + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| server | Prometheus server to get metrics from. | Yes | + + + + +##### `promtool debug all` + +Fetch all debug information. + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| server | Prometheus server to get all debug information from. | Yes | + + + + +### `promtool test` + +Unit testing. + + + +##### `promtool test rules` + +Unit tests for rules. + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| test-rule-file | The unit test file. | Yes | + + + + +### `promtool tsdb` + +Run tsdb commands. + + + +##### `promtool tsdb bench` + +Run benchmarks. + + + +##### `promtool tsdb bench write` + +Run a write performance benchmark. + + + +###### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| --out | Set the output path. | `benchout` | +| --metrics | Number of metrics to read. | `10000` | +| --scrapes | Number of scrapes to simulate. | `3000` | + + + + +###### Arguments + +| Argument | Description | Default | +| --- | --- | --- | +| file | Input file with samples data, default is (../../tsdb/testdata/20kseries.json). | `../../tsdb/testdata/20kseries.json` | + + + + +##### `promtool tsdb analyze` + +Analyze churn, label pair cardinality and compaction efficiency. + + + +###### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| --limit | How many items to show in each list. | `20` | +| --extended | Run extended analysis. | | + + + + +###### Arguments + +| Argument | Description | Default | +| --- | --- | --- | +| db path | Database path (default is data/). | `data/` | +| block id | Block to analyze (default is the last block). | | + + + + +##### `promtool tsdb list` + +List tsdb blocks. + + + +###### Flags + +| Flag | Description | +| --- | --- | +| -r, --human-readable | Print human readable values. | + + + + +###### Arguments + +| Argument | Description | Default | +| --- | --- | --- | +| db path | Database path (default is data/). | `data/` | + + + + +##### `promtool tsdb dump` + +Dump samples from a TSDB. + + + +###### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| --min-time | Minimum timestamp to dump. | `-9223372036854775808` | +| --max-time | Maximum timestamp to dump. | `9223372036854775807` | +| --match | Series selector. | `{__name__=~'(?s:.*)'}` | + + + + +###### Arguments + +| Argument | Description | Default | +| --- | --- | --- | +| db path | Database path (default is data/). | `data/` | + + + + +##### `promtool tsdb create-blocks-from` + +[Experimental] Import samples from input and produce TSDB blocks. Please refer to the storage docs for more details. + + + +###### Flags + +| Flag | Description | +| --- | --- | +| -r, --human-readable | Print human readable values. | +| -q, --quiet | Do not print created blocks. | + + + + +##### `promtool tsdb create-blocks-from openmetrics` + +Import samples from OpenMetrics input and produce TSDB blocks. Please refer to the storage docs for more details. + + + +###### Arguments + +| Argument | Description | Default | Required | +| --- | --- | --- | --- | +| input file | OpenMetrics file to read samples from. | | Yes | +| output directory | Output directory for generated blocks. | `data/` | | + + + + +##### `promtool tsdb create-blocks-from rules` + +Create blocks of data for new recording rules. + + + +###### Flags + +| Flag | Description | Default | +| --- | --- | --- | +| --http.config.file | HTTP client configuration file for promtool to connect to Prometheus. | | +| --url | The URL for the Prometheus API with the data where the rule will be backfilled from. | `http://localhost:9090` | +| --start | The time to start backfilling the new rule from. Must be a RFC3339 formatted date or Unix timestamp. Required. | | +| --end | If an end time is provided, all recording rules in the rule files provided will be backfilled to the end time. Default will backfill up to 3 hours ago. Must be a RFC3339 formatted date or Unix timestamp. | | +| --output-dir | Output directory for generated blocks. | `data/` | +| --eval-interval | How frequently to evaluate rules when backfilling if a value is not set in the recording rule files. | `60s` | + + + + +###### Arguments + +| Argument | Description | Required | +| --- | --- | --- | +| rule-files | A list of one or more files containing recording rules to be backfilled. All recording rules listed in the files will be backfilled. Alerting rules are not evaluated. | Yes | + + diff --git a/docs/feature_flags.md b/docs/feature_flags.md index ae5d87998..58e49e3b4 100644 --- a/docs/feature_flags.md +++ b/docs/feature_flags.md @@ -1,6 +1,6 @@ --- title: Feature flags -sort_rank: 11 +sort_rank: 12 --- # Feature flags diff --git a/docs/migration.md b/docs/migration.md index 49e9aee8b..cb88bbfd6 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1,6 +1,6 @@ --- title: Migration -sort_rank: 9 +sort_rank: 10 --- # Prometheus 2.0 migration guide diff --git a/docs/stability.md b/docs/stability.md index 7bb8e3184..e4cc3203b 100644 --- a/docs/stability.md +++ b/docs/stability.md @@ -1,6 +1,6 @@ --- title: API Stability -sort_rank: 10 +sort_rank: 11 --- # API Stability Guarantees diff --git a/util/documentcli/documentcli.go b/util/documentcli/documentcli.go new file mode 100644 index 000000000..c199d8d9b --- /dev/null +++ b/util/documentcli/documentcli.go @@ -0,0 +1,252 @@ +// Copyright 2023 The Prometheus Authors +// 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. + +// If we decide to employ this auto generation of markdown documentation for +// amtool and alertmanager, this package could potentially be moved to +// prometheus/common. However, it is crucial to note that this functionality is +// tailored specifically to the way in which the Prometheus documentation is +// rendered, and should be avoided for use by third-party users. + +package documentcli + +import ( + "bytes" + "fmt" + "io" + "strings" + + "github.com/alecthomas/kingpin/v2" +) + +// GenerateMarkdown generates the markdown documentation for an application from +// its kingpin ApplicationModel. +func GenerateMarkdown(model *kingpin.ApplicationModel, writer io.Writer) error { + h := header(model.Name, model.Help) + if _, err := writer.Write(h); err != nil { + return err + } + + if err := writeFlagTable(writer, 0, model.FlagGroupModel); err != nil { + return err + } + + if err := writeArgTable(writer, 0, model.ArgGroupModel); err != nil { + return err + } + + if err := writeCmdTable(writer, model.CmdGroupModel); err != nil { + return err + } + + return writeSubcommands(writer, 1, model.Name, model.CmdGroupModel.Commands) +} + +func header(title, help string) []byte { + return []byte(fmt.Sprintf(`--- +title: %s +--- + +# %s + +%s + +`, title, title, help)) +} + +func createFlagRow(flag *kingpin.FlagModel) []string { + defaultVal := "" + if len(flag.Default) > 0 && len(flag.Default[0]) > 0 { + defaultVal = fmt.Sprintf("`%s`", flag.Default[0]) + } + + name := fmt.Sprintf(`--%s`, flag.Name) + if flag.Short != '\x00' { + name = fmt.Sprintf(`-%c, --%s`, flag.Short, flag.Name) + } + + return []string{name, flag.Help, defaultVal} +} + +func writeFlagTable(writer io.Writer, level int, fgm *kingpin.FlagGroupModel) error { + if fgm == nil || len(fgm.Flags) == 0 { + return nil + } + + rows := [][]string{ + {"Flag", "Description", "Default"}, + } + + for _, flag := range fgm.Flags { + if !flag.Hidden { + row := createFlagRow(flag) + rows = append(rows, row) + } + } + + return writeTable(writer, rows, fmt.Sprintf("%s Flags", strings.Repeat("#", level+2))) +} + +func createArgRow(arg *kingpin.ArgModel) []string { + defaultVal := "" + if len(arg.Default) > 0 { + defaultVal = fmt.Sprintf("`%s`", arg.Default[0]) + } + + required := "" + if arg.Required { + required = "Yes" + } + + return []string{arg.Name, arg.Help, defaultVal, required} +} + +func writeArgTable(writer io.Writer, level int, agm *kingpin.ArgGroupModel) error { + if agm == nil || len(agm.Args) == 0 { + return nil + } + + rows := [][]string{ + {"Argument", "Description", "Default", "Required"}, + } + + for _, arg := range agm.Args { + row := createArgRow(arg) + rows = append(rows, row) + } + + return writeTable(writer, rows, fmt.Sprintf("%s Arguments", strings.Repeat("#", level+2))) +} + +func createCmdRow(cmd *kingpin.CmdModel) []string { + if cmd.Hidden { + return nil + } + return []string{cmd.FullCommand, cmd.Help} +} + +func writeCmdTable(writer io.Writer, cgm *kingpin.CmdGroupModel) error { + if cgm == nil || len(cgm.Commands) == 0 { + return nil + } + + rows := [][]string{ + {"Command", "Description"}, + } + + for _, cmd := range cgm.Commands { + row := createCmdRow(cmd) + if row != nil { + rows = append(rows, row) + } + } + + return writeTable(writer, rows, "## Commands") +} + +func writeTable(writer io.Writer, data [][]string, header string) error { + if len(data) < 2 { + return nil + } + + buf := bytes.NewBuffer(nil) + + buf.WriteString(fmt.Sprintf("\n\n%s\n\n", header)) + columnsToRender := determineColumnsToRender(data) + + headers := data[0] + buf.WriteString("|") + for _, j := range columnsToRender { + buf.WriteString(fmt.Sprintf(" %s |", headers[j])) + } + buf.WriteString("\n") + + buf.WriteString("|") + for range columnsToRender { + buf.WriteString(" --- |") + } + buf.WriteString("\n") + + for i := 1; i < len(data); i++ { + row := data[i] + buf.WriteString("|") + for _, j := range columnsToRender { + buf.WriteString(fmt.Sprintf(" %s |", row[j])) + } + buf.WriteString("\n") + } + + if _, err := writer.Write(buf.Bytes()); err != nil { + return err + } + + if _, err := writer.Write([]byte("\n\n")); err != nil { + return err + } + + return nil +} + +func determineColumnsToRender(data [][]string) []int { + columnsToRender := []int{} + if len(data) == 0 { + return columnsToRender + } + for j := 0; j < len(data[0]); j++ { + renderColumn := false + for i := 1; i < len(data); i++ { + if data[i][j] != "" { + renderColumn = true + break + } + } + if renderColumn { + columnsToRender = append(columnsToRender, j) + } + } + return columnsToRender +} + +func writeSubcommands(writer io.Writer, level int, modelName string, commands []*kingpin.CmdModel) error { + level++ + if level > 4 { + level = 4 + } + for _, cmd := range commands { + if cmd.Hidden { + continue + } + + help := cmd.Help + if cmd.HelpLong != "" { + help = cmd.HelpLong + } + if _, err := writer.Write([]byte(fmt.Sprintf("\n\n%s `%s %s`\n\n%s\n\n", strings.Repeat("#", level+1), modelName, cmd.FullCommand, help))); err != nil { + return err + } + + if err := writeFlagTable(writer, level, cmd.FlagGroupModel); err != nil { + return err + } + + if err := writeArgTable(writer, level, cmd.ArgGroupModel); err != nil { + return err + } + + if cmd.CmdGroupModel != nil && len(cmd.CmdGroupModel.Commands) > 0 { + if err := writeSubcommands(writer, level+1, modelName, cmd.CmdGroupModel.Commands); err != nil { + return err + } + } + } + return nil +}