Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metrics table generation from yaml #79

Merged
merged 59 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c1ccabd
functionality for metrics table generation from yaml
jamesmoessis Jan 11, 2022
40d73ef
tests for metric convention yaml->markdown
jamesmoessis Jan 12, 2022
beead27
appease linter
jamesmoessis Jan 12, 2022
6e4ff11
use zip instead of enumerate
jamesmoessis Jan 25, 2022
cd9d815
raise error if metric_table is specified on non-metric semconv
jamesmoessis Jan 25, 2022
f4aef51
update syntax.md with initial metrics syntax
jamesmoessis Jan 25, 2022
2421f0b
remove duplicated code and files
jamesmoessis Jan 25, 2022
5ac5895
add extra_yaml_files param and use it in metric_table test
jamesmoessis Jan 25, 2022
bec1a1b
use 'FQN = prefix + id' for metric FQNs
jamesmoessis Jan 27, 2022
22373cd
change InstrumentKind enum to ALL_CAPS and remove sync/async distinction
jamesmoessis Apr 13, 2022
ec98643
implement syntax review and semantics docs
jamesmoessis Apr 13, 2022
9bcc6e4
raise error from different position to avoid re-raising
jamesmoessis Apr 13, 2022
87dfe5d
remove markdown link
jamesmoessis Apr 13, 2022
6df71da
implement various review items
jamesmoessis Apr 14, 2022
a96e962
Merge branch 'main' into metrics-semconv
jamesmoessis Apr 14, 2022
93eb443
upgrade black version to try solve build failure
jamesmoessis Apr 14, 2022
a77142c
appease linter
jamesmoessis Apr 14, 2022
c50b9e0
fix typo
jamesmoessis Apr 14, 2022
13316cd
stop using enum for instrument kind
jamesmoessis Apr 26, 2022
e01f296
extract attribute table writing to own function
jamesmoessis Apr 26, 2022
5fbdcf9
appease linter
jamesmoessis Apr 26, 2022
a3b7e7f
add link to UCUM
jamesmoessis Apr 26, 2022
8ec68ed
consistent camelcase syntax
jamesmoessis Apr 26, 2022
e6d4da2
Merge branch 'main' into metrics-semconv
jamesmoessis Jun 6, 2022
dfe7b0e
Address reyang comments
jamesmoessis Jun 6, 2022
cde4200
update tests so they pass with latest main
jamesmoessis Jun 6, 2022
9217e78
move logic of stripping metric. prefix
jamesmoessis Jun 6, 2022
9178ab3
appease linter
jamesmoessis Jun 6, 2022
a012741
remove 'metric.' prefix so we don't have to removeprefix() when seria…
jamesmoessis Jun 6, 2022
b06e586
Merge branch 'main' into metrics-semconv
jamesmoessis Sep 8, 2022
f028520
make test match current changed expectation for requirement level
jamesmoessis Sep 9, 2022
68b1154
new yaml structure for metrics
jamesmoessis Sep 9, 2022
849c376
change the structure of yaml for metrics
jamesmoessis Sep 9, 2022
fd33c98
foo bar test for new metric structure
jamesmoessis Sep 9, 2022
6536fb8
alter syntax definition in readme
jamesmoessis Sep 9, 2022
9e7e718
simplify test slightly
jamesmoessis Sep 9, 2022
f6f1d5a
implement several review items
jamesmoessis Sep 13, 2022
3f3a104
units -> unit in some places I missed before
jamesmoessis Sep 13, 2022
f2f64b6
newline for readability
jamesmoessis Sep 13, 2022
766cc5d
remove unneeded yaml definition
jamesmoessis Sep 13, 2022
f1f7e73
add JSON schema definition for MetricSemanticConvention
jamesmoessis Sep 13, 2022
167ba00
add changelog
jamesmoessis Sep 13, 2022
5c38093
appease linter
jamesmoessis Sep 13, 2022
8fb5af0
address comment regarding instrument types
jamesmoessis Sep 16, 2022
3a775d3
update docs on metric_table semconv generator example
jamesmoessis Oct 5, 2022
d237bf2
restore event name in syntax.md which erroneously removed
jamesmoessis Oct 19, 2022
e8259dd
add more metrics/attributes to test, clarifying intentions
jamesmoessis Oct 19, 2022
6a67c74
use different prefix in tests for clarity
jamesmoessis Oct 24, 2022
e524ab4
differentiate parent and concrete metric types with different semanti…
jamesmoessis Oct 24, 2022
4a8aabd
update syntax.md and JSON schema for metric_group addition
jamesmoessis Oct 26, 2022
9943b8f
address comment regarding wording in changelog
jamesmoessis Nov 29, 2022
cf47089
remove unnecessary 'extends' from test case
jamesmoessis Nov 30, 2022
086b616
address comments about clarifying and rewording definitions in syntax.md
jamesmoessis Nov 30, 2022
9da7198
remove field that stores instrument markdown repr, instead calculate …
jamesmoessis Nov 30, 2022
cff3949
appease lint
jamesmoessis Nov 30, 2022
57d8c4f
Merge branch 'main' into metrics-semconv
jamesmoessis Nov 30, 2022
fdd98f3
appease new linter f-string requirements
jamesmoessis Dec 1, 2022
0f086b0
use f-string and inline a variable
jamesmoessis Dec 6, 2022
f393d8b
Merge branch 'main' into metrics-semconv
jamesmoessis Jan 19, 2023
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
3 changes: 3 additions & 0 deletions semantic-conventions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ convention that have the tag `network`.
`<!-- semconv http.server(tag=network, full) -->` will print the constraints and attributes of both `http` and `http.server`
semantic conventions that have the tag `network`.

`<!-- semconv metric.http.server(metric_table) -->` will print a table of metrics with all metrics prefixed with
Oberon00 marked this conversation as resolved.
Show resolved Hide resolved
`metric.http.server`.

## Code Generator

The image supports [Jinja](https://jinja.palletsprojects.com/en/2.11.x/) templates to generate code from the models.
Expand Down
2 changes: 1 addition & 1 deletion semantic-conventions/dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
black==21.8b0
black==22.3.0
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
mypy==0.910
pytest==6.2.5
flake8==3.9.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ def parse(span_kind_value):
return kind_map.get(span_kind_value)


class InstrumentKind(Enum):
COUNTER = 1
UP_DOWN_COUNTER = 2
HISTOGRAM = 3
GAUGE = 4

@staticmethod
def parse(instrument_kind_value):
return InstrumentKind.kind_map().get(instrument_kind_value)

@staticmethod
def kind_map():
return {
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
"Counter": InstrumentKind.COUNTER,
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
"UpDownCounter": InstrumentKind.UP_DOWN_COUNTER,
"Histogram": InstrumentKind.HISTOGRAM,
"Gauge": InstrumentKind.GAUGE,
}

def __str__(self):
# reverse lookup kind_map()
return next(filter(lambda i: i[1] is self, InstrumentKind.kind_map().items()))[
Oberon00 marked this conversation as resolved.
Show resolved Hide resolved
0
]


def parse_semantic_convention_type(type_value):
# Gracefully transition to the new types
if type_value is None:
Expand Down Expand Up @@ -238,7 +264,43 @@ def __init__(self, group):
class MetricSemanticConvention(BaseSemanticConvention):
GROUP_TYPE_NAME = "metric"

allowed_keys = ()
allowed_keys: Tuple[str, ...] = BaseSemanticConvention.allowed_keys + ("metrics",)

class Metric:
def __init__(self, metric, parent_prefix, position):
self.id: str = metric.get("id")
self.fqn = "{}.{}".format(parent_prefix, self.id)
self._position = position
self.units: str = metric.get("units")
self.brief: str = metric.get("brief")
instrument_str = metric.get("instrument")
self.instrument: InstrumentKind = InstrumentKind.parse(instrument_str)

if None in [instrument_str, self.id, self.units, self.brief]:
raise ValidationError.from_yaml_pos(
self._position,
"id, instrument, units, and brief must all be defined for concrete metrics",
)
if self.instrument is None:
raise ValidationError.from_yaml_pos(
self._position,
"Instrument '{}' is not a valid instrument name".format(
metric.get("instrument")
),
)

def __init__(self, group):
super().__init__(group)
self.metrics = ()
if group.get("metrics"):
self.metrics: Tuple[MetricSemanticConvention.Metric, ...] = tuple(
map(
lambda m: MetricSemanticConvention.Metric(
m, self.prefix, self._position
),
group.get("metrics"),
)
)


@dataclass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
)
from opentelemetry.semconv.model.semantic_convention import (
EventSemanticConvention,
MetricSemanticConvention,
SemanticConventionSet,
UnitSemanticConvention,
)
Expand All @@ -41,6 +42,7 @@ class RenderContext:
def __init__(self):
self.is_full = False
self.is_remove_constraint = False
self.is_metric_table = False
self.group_key = ""
self.enums = []
self.notes = []
Expand All @@ -66,7 +68,7 @@ class MarkdownRenderer:
)
p_end = re.compile("<!--\\s*endsemconv\\s*-->")
default_break_conditional_labels = 50
valid_parameters = ["tag", "full", "remove_constraints"]
valid_parameters = ["tag", "full", "remove_constraints", "metric_table"]

prelude = "<!-- semconv {} -->\n"
table_headers = "| Attribute | Type | Description | Examples | Required |\n|---|---|---|---|---|\n"
Expand Down Expand Up @@ -174,6 +176,31 @@ def to_markdown_attr(
)
)

@staticmethod
def to_markdown_metric_table(
semconv: MetricSemanticConvention, output: io.StringIO
):
"""
This method renders metrics as markdown table entry
"""
if not isinstance(semconv, MetricSemanticConvention):
raise ValueError(
"semconv `{}` was specified with `metric_table`, but it is not a metric convention".format(
semconv.semconv_id
)
)

output.write(
"| Name | Instrument | Unit (UCUM) | Description |\n"
"| -------- | ------------- | ----------- | -------------- |\n"
)
for metric in semconv.metrics:
output.write(
"| `{}` | {} | `{}` | {} |\n".format(
metric.fqn, metric.instrument, metric.units, metric.brief
)
)

def to_markdown_anyof(self, anyof: AnyOf, output: io.StringIO):
"""
This method renders anyof constraints into markdown lists
Expand Down Expand Up @@ -416,31 +443,36 @@ def _render_group(self, semconv, parameters, output):
self.render_ctx.is_remove_constraint = "remove_constraints" in parameters
self.render_ctx.group_key = parameters.get("tag")
self.render_ctx.is_full = "full" in parameters
self.render_ctx.is_metric_table = "metric_table" in parameters

if isinstance(semconv, EventSemanticConvention):
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
output.write("The event name MUST be `{}`.\n\n".format(semconv.name))

attr_to_print = []
attr: SemanticAttribute
for attr in sorted(
semconv.attributes, key=lambda a: "" if a.ref is None else a.ref
):
if self.render_ctx.group_key is not None:
if attr.tag == self.render_ctx.group_key:
if self.render_ctx.is_metric_table:
self.to_markdown_metric_table(semconv, output)
else:
attr: SemanticAttribute
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
for attr in sorted(
semconv.attributes, key=lambda a: "" if a.ref is None else a.ref
):
if self.render_ctx.group_key is not None:
if attr.tag == self.render_ctx.group_key:
attr_to_print.append(attr)
continue
if self.render_ctx.is_full or attr.is_local:
attr_to_print.append(attr)
continue
if self.render_ctx.is_full or attr.is_local:
attr_to_print.append(attr)
if self.render_ctx.group_key is not None and not attr_to_print:
raise ValueError(
"No attributes retained for '{}' filtering by '{}'".format(
semconv.semconv_id, self.render_ctx.group_key
if self.render_ctx.group_key is not None and not attr_to_print:
raise ValueError(
"No attributes retained for '{}' filtering by '{}'".format(
semconv.semconv_id, self.render_ctx.group_key
)
)
)
if attr_to_print:
output.write(MarkdownRenderer.table_headers)
for attr in attr_to_print:
self.to_markdown_attr(attr, output)
if attr_to_print:
output.write(MarkdownRenderer.table_headers)
for attr in attr_to_print:
self.to_markdown_attr(attr, output)

self.to_markdown_notes(output)
if not self.render_ctx.is_remove_constraint:
for cnst in semconv.constraints:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@

## Common Attributes

The following attributes SHOULD be included on all HTTP metrics for both server and client.

<!-- semconv metric.http -->
| Attribute | Type | Description | Examples | Required |
|---|---|---|---|---|
| `http.host` | string | The value of the [HTTP host header](https://tools.ietf.org/html/rfc7230#section-5.4). An empty Host header should also be reported, see note. [1] | `www.example.org` | See attribute alternatives |
| `http.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | Yes |
| `http.scheme` | string | The URI scheme identifying the used protocol. | `http`; `https` | See attribute alternative |
| `http.status_code` | int | [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6). | `200` | No |

**[1]:** When the header is present but empty the attribute SHOULD be set to the empty string. Note that this is a valid situation that is expected in certain cases, according the aforementioned [section of RFC 7230](https://tools.ietf.org/html/rfc7230#section-5.4). When the header is not set the attribute MUST NOT be set.
<!-- endsemconv -->

## HTTP Client

### HTTP Client Metrics

<!-- semconv metric.http.client(metric_table,remove_constraints) -->
| Name | Instrument | Unit (UCUM) | Description |
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
| -------- | ------------- | ----------- | -------------- |
| `metric.http.client.duration` | Histogram | `ms` | Measures the duration of the outbound HTTP request. |
<!-- endsemconv -->

### HTTP Client Attributes

The following attributes SHOULD be included on HTTP Client metrics, where applicable and available.

<!-- semconv metric.http.client -->
| Attribute | Type | Description | Examples | Required |
|---|---|---|---|---|
| `net.peer.ip` | string | Remote address of the peer (dotted decimal for IPv4 or [RFC5952](https://tools.ietf.org/html/rfc5952) for IPv6) | `127.0.0.1` | See below |
| `net.peer.name` | string | Remote hostname or similar, see note below. | `example.com` | See below |
| `net.peer.port` | int | Remote port number. | `80`; `8080`; `443` | See below |

**Additional attribute requirements:** At least one of the following sets of attributes is required:

* `http.url`
* `http.scheme`, `http.host`, `http.target`
* `http.scheme`, `net.peer.name`, `net.peer.port`, `http.target`
* `http.scheme`, `net.peer.ip`, `net.peer.port`, `http.target`
<!-- endsemconv -->

## HTTP Server

### HTTP Server Metrics

<!-- semconv metric.http.server(metric_table,remove_constraints) -->
| Name | Instrument | Unit (UCUM) | Description |
| -------- | ------------- | ----------- | -------------- |
| `metric.http.server.duration` | Histogram | `ms` | Measures the duration of the inbound HTTP request. |
| `metric.http.server.active_requests` | UpDownCounter | `{requests}` | Measures the number of concurrent HTTP requests that are currently in-flight. |
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
<!-- endsemconv -->

### HTTP Server Attributes

The following attributes SHOULD be included on HTTP Server metrics, where applicable and available.

<!-- semconv metric.http.server -->
| Attribute | Type | Description | Examples | Required |
|---|---|---|---|---|
| `http.server_name` | string | The primary server name of the matched virtual host. This should be obtained via configuration. If no such configuration can be obtained, this attribute MUST NOT be set ( `net.host.name` should be used instead). [1] | `example.com` | See below |
| `net.host.name` | string | Local hostname or similar, see note below. | `localhost` | See below |
| `net.host.port` | int | Like `net.peer.port` but for the host port. | `35555` | See below |

**[1]:** `http.url` is usually not readily available on the server side but would have to be assembled in a cumbersome and sometimes lossy process from other information (see e.g. open-telemetry/opentelemetry-python/pull/148). It is thus preferred to supply the raw data that is available.

**Additional attribute requirements:** At least one of the following sets of attributes is required:

* `http.scheme`, `http.host`, `http.target`
* `http.scheme`, `http.server_name`, `net.host.port`, `http.target`
* `http.scheme`, `net.host.name`, `net.host.port`, `http.target`
* `http.url`
<!-- endsemconv -->
Loading