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 9 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.
Jump to
Jump to file
Failed to load files.
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ def parse(span_kind_value):
return kind_map.get(span_kind_value)


class InstrumentKind(Enum):
Empty = 1
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
Counter = 2
AsynchronousCounter = 3
Histogram = 4
AsynchronousGauge = 5
UpDownCounter = 6
AsynchronousUpDownCounter = 7

def __str__(self):
return self.name


def parse_semantic_convention_type(type_value):
# Gracefully transition to the new types
if type_value is None:
Expand Down Expand Up @@ -238,7 +251,35 @@ 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):
self.id: str = metric.get("id")
self.fqn = "{}.{}".format(parent_prefix, self.id)
self.instrument: InstrumentKind = InstrumentKind[metric.get("instrument")]
self.units: str = metric.get("units")
self.brief: str = metric.get("brief")

if None in [metric.get("instrument"), self.id, self.units, self.brief]:
raise ValueError

def __init__(self, group):
super().__init__(group)
self.metrics = ()
if group.get("metrics"):
try:
self.metrics: Tuple[MetricSemanticConvention.Metric] = tuple(
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
map(
lambda m: MetricSemanticConvention.Metric(m, self.prefix),
group.get("metrics"),
)
)
except ValueError as e:
raise ValidationError.from_yaml_pos(
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
self._position,
"id, instrument, units, and brief must all be defined for concrete metrics",
) from e


@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,24 @@ def to_markdown_attr(
)
)

@staticmethod
def to_markdown_metric_table(
semconv: MetricSemanticConvention, output: io.StringIO
):
"""
This method renders metrics as markdown table entry
"""
output.write(
"| Name | Instrument | Unit ([UCUM](README.md#instrument-units)) | Description |\n"
Oberon00 marked this conversation as resolved.
Show resolved Hide resolved
"| -------- | ---------------- | --------- | -------------- |\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 @@ -414,15 +434,28 @@ 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))

if self.render_ctx.is_metric_table:
if isinstance(semconv, MetricSemanticConvention):
self.to_markdown_metric_table(semconv, output)
else:
raise ValueError(
"semconv `{}` was specified with `metric_table`, but it is not a metric convention".format(
semconv.semconv_id
)
)

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.is_metric_table:
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
break
if self.render_ctx.group_key is not None:
if attr.tag == self.render_ctx.group_key:
attr_to_print.append(attr)
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](README.md#instrument-units)) | Description |
| -------- | ---------------- | --------- | -------------- |
| `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](README.md#instrument-units)) | Description |
| -------- | ---------------- | --------- | -------------- |
| `metric.http.server.duration` | Histogram | `ms` | Measures the duration of the inbound HTTP request. |
| `metric.http.server.active_requests` | AsynchronousUpDownCounter | `{requests}` | Measures the number of concurrent HTTP requests that are currently in-flight. |
<!-- 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 -->
172 changes: 172 additions & 0 deletions semantic-conventions/src/tests/data/markdown/metrics_tables/http.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
groups:
- id: http
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
prefix: http
brief: 'This document defines semantic conventions for HTTP client and server Spans.'
note: >
These conventions can be used for http and https schemes
and various HTTP versions like 1.1, 2 and SPDY.
attributes:
- id: method
type: string
required: always
brief: 'HTTP request method.'
sampling_relevant: true
examples: ["GET", "POST", "HEAD"]
- id: url
type: string
brief: >
Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`.
Usually the fragment is not transmitted over HTTP, but if it is known, it should be included nevertheless.
note: >
`http.url` MUST NOT contain credentials passed via URL in form of `https://username:password@www.example.com/`.
In such case the attribute's value should be `https://www.example.com/`.
sampling_relevant: true
examples: ['https://www.foo.bar/search?q=OpenTelemetry#SemConv']
- id: target
type: string
brief: 'The full request target as passed in a HTTP request line or equivalent.'
sampling_relevant: true
examples: ['/path/12314/?q=ddds#123']
- id: host
type: string
brief: >
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.
note: >
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.
sampling_relevant: true
examples: ['www.example.org']
- id: scheme
type: string
brief: 'The URI scheme identifying the used protocol.'
sampling_relevant: true
examples: ["http", "https"]
- id: status_code
type: int
required:
conditional: If and only if one was received/sent.
brief: '[HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6).'
examples: [200]
- id: flavor
type:
# Default value: `true`. If false, it helps the code gen tool to
# encode checks that only accept the listed values.
allow_custom_values: true
members:
jamesmoessis marked this conversation as resolved.
Show resolved Hide resolved
- id: http_1_0
value: '1.0'
brief: 'HTTP 1.0'
- id: http_1_1
value: '1.1'
brief: 'HTTP 1.1'
- id: http_2_0
value: '2.0'
brief: 'HTTP 2'
- id: spdy
value: 'SPDY'
brief: 'SPDY protocol.'
- id: quic
value: 'QUIC'
brief: 'QUIC protocol.'
brief: 'Kind of HTTP protocol used.'
note: >
If `net.transport` is not specified, it can be assumed to be `IP.TCP` except if `http.flavor`
is `QUIC`, in which case `IP.UDP` is assumed.
- id: user_agent
type: string
brief: 'Value of the [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) header sent by the client.'
examples: ['CERN-LineMode/2.15 libwww/2.17b3']
- id: request_content_length
type: int
brief: >
The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and
is often, but not always, present as the [Content-Length](https://tools.ietf.org/html/rfc7230#section-3.3.2)
header. For requests using transport encoding, this should be the compressed size.
examples: 3495
- id: request_content_length_uncompressed
type: int
brief: >
The size of the uncompressed request payload body after transport decoding. Not set if transport encoding not used.
examples: 5493
- id: response_content_length
type: int
brief: >
The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and
is often, but not always, present as the [Content-Length](https://tools.ietf.org/html/rfc7230#section-3.3.2)
header. For requests using transport encoding, this should be the compressed size.
examples: 3495
- id: response_content_length_uncompressed
type: int
brief: >
The size of the uncompressed response payload body after transport decoding. Not set if transport encoding not used.
examples: 5493
- ref: net.peer.name
sampling_relevant: true
- ref: net.peer.ip
sampling_relevant: true
- ref: net.peer.port
sampling_relevant: true
constraints:
- include: network

- id: http.client
prefix: http
extends: http
span_kind: client
brief: 'Semantic Convention for HTTP Client'
constraints:
- any_of:
- [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]

- id: http.server
prefix: http
extends: http
span_kind: server
brief: 'Semantic Convention for HTTP Server'
attributes:
- id: server_name
type: string
brief: >
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).
note: >
`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.
examples: ['example.com']
- id: route
type: string
brief: >
The matched route (path template).
examples: '/users/:userID?'
- id: client_ip
type: string
brief: >
The IP address of the original client behind all proxies, if
known (e.g. from [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)).
note: |
This is not necessarily the same as `net.peer.ip`, which would
identify the network-level peer, which may be a proxy.

This attribute should be set when a source of information different
from the one used for `net.peer.ip`, is available even if that other
source just confirms the same value as `net.peer.ip`.
Rationale: For `net.peer.ip`, one typically does not know if it
comes from a proxy, reverse proxy, or the actual client. Setting
`http.client_ip` when it's the same as `net.peer.ip` means that
one is at least somewhat confident that the address is not that of
the closest proxy.
examples: '83.164.160.102'
constraints:
- any_of:
- [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]