Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion alpacloud/promls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ python_distribution(
long_description_path="alpacloud/promls/readme.md",
provides=python_artifact(
name="alpacloud_promls",
version="0.2.0",
version="0.3.0",
description="A visualiser for available Prometheus metrics",
author="Daniel Goldman",
entry_points={"console_scripts": ["promls = alpacloud.promls.cli:search"]},
Expand Down
Binary file removed alpacloud/promls/browse.png
Binary file not shown.
237 changes: 237 additions & 0 deletions alpacloud/promls/browse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions alpacloud/promls/changelog.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
# 0

# 0.3

# 0.3.0

- fix : parser can parse escaped sequences
- feature : parser can report errors and is more robust
- feature : show labels in browser
- ui : tweaks for browser tree
- feature : combine submetrics of summaries and histograms

# 0.2

## 0.2.0

- refactor : drop jank fuzzy finder

# 0.1

## 0.1.0

- release
46 changes: 35 additions & 11 deletions alpacloud/promls/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import click

from alpacloud.promls.fetch import FetcherURL, Parser
from alpacloud.promls.fetch import Collector, FetcherURL, ParseError, Parser
from alpacloud.promls.filter import MetricsTree, filter_any, filter_name, filter_path
from alpacloud.promls.metrics import Metric
from alpacloud.promls.util import paths_to_tree
Expand Down Expand Up @@ -41,6 +41,12 @@ def parse(ctx, param, value):
help=f"Display mode: {', '.join(m.value for m in PrintMode)}",
)
opt_filter = click.option("--filter")
opt_combine = click.option(
"--combine-submetrics",
is_flag=True,
help="Combine submetrics into their parent. For example, combine a histogram's `sum` and `count` into the main histogram metric.",
default=True,
)


def common_args():
Expand All @@ -50,14 +56,18 @@ def decorator(f):
f = opt_filter(f)
f = opt_mode(f)
f = arg_url(f)
f = opt_combine(f)
return f

return decorator


def do_fetch(url: str):
def do_fetch(url: str, combine_submetrics: bool):
"""Do the fetch and parse."""
return MetricsTree(Parser().parse(FetcherURL(url).fetch()))
lines, errors = Parser.parse_all(FetcherURL(url).fetch())
values = Collector(lines, combine_submetrics).assemble()

return MetricsTree({e.name: e for e in values}), errors


def mk_indent(i: int, s: str) -> str:
Expand Down Expand Up @@ -104,42 +114,56 @@ def do_print(tree: MetricsTree, mode: PrintMode):
return txt


def print_errors(errors: list[ParseError]):
"""Print parse errors"""
if not errors:
return
click.echo(f"warning: parse errors: {len(errors)}", err=True)
for err in errors:
click.echo(str(err), err=True)


@click.group()
def search():
"""Search metrics"""


@search.command()
@common_args()
def name(url, filter: str, display: PrintMode):
def name(url, filter: str, display: PrintMode, combine_submetrics: bool):
"""Filter metrics by their name"""
tree = do_fetch(url)
tree, errors = do_fetch(url, combine_submetrics)
print_errors(errors)
filtered = tree.filter(filter_name(re.compile(filter)))
click.echo(do_print(filtered, display))


@search.command()
@common_args()
def any(url, filter: str, display: PrintMode):
def any(url, filter: str, display: PrintMode, combine_submetrics: bool):
"""Filter metrics by any of their properties"""
tree = do_fetch(url)
tree, errors = do_fetch(url, combine_submetrics)
print_errors(errors)
filtered = tree.filter(filter_any(re.compile(filter)))
click.echo(do_print(filtered, display))


@search.command()
@common_args()
def path(url, filter: str, display: PrintMode):
def path(url, filter: str, display: PrintMode, combine_submetrics: bool):
"""Filter metrics by their path"""
tree = do_fetch(url)
tree, errors = do_fetch(url, combine_submetrics)
print_errors(errors)
filtered = tree.filter(filter_path(filter.split("_")))
click.echo(do_print(filtered, display))


@search.command()
@arg_url
@opt_filter
def browse(url, filter: str):
@opt_combine
def browse(url, filter: str, combine_submetrics: bool):
"""Browse metrics in an interactive visualizer"""
real_filter = filter or ".*"
PromlsVisApp(do_fetch(url), real_filter, lambda s: filter_any(re.compile(s))).run()
results, errors = do_fetch(url, combine_submetrics)
PromlsVisApp(results, errors, real_filter, lambda s: filter_any(re.compile(s))).run()
19 changes: 10 additions & 9 deletions alpacloud/promls/cli_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Tests for CLI functions."""
# pylint: disable=redefined-outer-name,

import json

Expand All @@ -13,11 +14,11 @@
def test_metrics():
"""Common test data for all PrintMode tests."""
return [
Metric(name="http_requests_total", help="Total HTTP requests", type="counter"),
Metric(name="http_requests_failed", help="Failed HTTP requests", type="counter"),
Metric(name="http_response_time_seconds", help="HTTP response time", type="histogram"),
Metric(name="database_connections_active", help="Active database connections", type="gauge"),
Metric(name="database_queries_total", help="Total database queries", type="counter"),
Metric(name="http_requests_total", help="Total HTTP requests", type="counter", labels=[]),
Metric(name="http_requests_failed", help="Failed HTTP requests", type="counter", labels=[]),
Metric(name="http_response_time_seconds", help="HTTP response time", type="histogram", labels=[]),
Metric(name="database_connections_active", help="Active database connections", type="gauge", labels=[]),
Metric(name="database_queries_total", help="Total database queries", type="counter", labels=[]),
]


Expand Down Expand Up @@ -72,7 +73,7 @@ def test_flat_empty_tree(self):

def test_flat_metric_without_help(self):
"""Test flat mode with metric without help text."""
metric = Metric(name="test_metric", help="", type="counter")
metric = Metric(name="test_metric", help="", type="counter", labels=[])
tree = MetricsTree.mk_tree([metric])
result = do_print(tree, PrintMode.flat)

Expand Down Expand Up @@ -121,7 +122,7 @@ def test_full_empty_tree(self):

def test_full_metric_without_help(self):
"""Test full mode with metric without help text."""
metric = Metric(name="test_metric", help="", type="counter")
metric = Metric(name="test_metric", help="", type="counter", labels=[])
tree = MetricsTree.mk_tree([metric])
result = do_print(tree, PrintMode.full)

Expand Down Expand Up @@ -178,7 +179,7 @@ def test_tree_empty_tree(self):

def test_tree_metric_without_underscore(self):
"""Test tree mode with metric without underscore."""
metric = Metric(name="simplemetric", help="A simple metric", type="gauge")
metric = Metric(name="simplemetric", help="A simple metric", type="gauge", labels=[])
tree = MetricsTree.mk_tree([metric])
result = do_print(tree, PrintMode.tree)

Expand Down Expand Up @@ -234,7 +235,7 @@ def test_json_empty_tree(self):

def test_json_metric_structure(self):
"""Test json mode metric structure."""
metric = Metric(name="test_metric", help="Test help", type="counter")
metric = Metric(name="test_metric", help="Test help", type="counter", labels=[])
tree = MetricsTree.mk_tree([metric])
result = do_print(tree, PrintMode.json)
parsed = json.loads(result)
Expand Down
Loading
Loading