diff --git a/Makefile b/Makefile index 2c157f2..5641747 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,11 @@ $(BUF): MDOX = mdox $(MDOX): @go install github.com/bwplotka/mdox@latest + +# Hacky, replace the binary path for yourself for now. +# One could use docker as well. TODO: Fix this. +WEAVER = ../otel-weaver/target/debug/weaver + # ------ .PHONY: help @@ -27,7 +32,6 @@ help: ## Display this help and any documented user-facing targets. Other undocum help: @awk 'BEGIN {FS = ": ##"; printf "Usage:\n make \n\nTargets:\n"} /^[a-zA-Z0-9_\.\-\/%]+: ##/ { printf " %-45s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) - .PHONY: docker docker: @export DOCKER_IMAGE=$(DOCKER_IMAGE) DOCKER_TAG=$(DOCKER_TAG) && bash scripts/build-docker.sh $(DOCKER_PUSH) @@ -49,3 +53,13 @@ format: $(GOFUMPT) $(GOIMPORTS) $(MDOX) @echo ">> format documentation" @$(MDOX) fmt --soft-wraps ./*.md +SEMCONV_VERSION ?= v0.1.0 +.PHONY: gen # Generate artefacts e.g. metric definitions from my-org semconv. +gen: + @echo ">> weaver generate" + @$(WEAVER) registry generate \ + --registry=./my-org/semconv/$(SEMCONV_VERSION) \ + --templates=./client_golang/semconv \ + go \ + ./go/my-app/semconv/$(SEMCONV_VERSION) + diff --git a/client_golang/semconv/registry/go/metric.go.j2 b/client_golang/semconv/registry/go/metric.go.j2 new file mode 100644 index 0000000..746bd1d --- /dev/null +++ b/client_golang/semconv/registry/go/metric.go.j2 @@ -0,0 +1,87 @@ +{%- set my_file_name = ctx.metric_name | lower | snake_case ~ ".go" -%} +{{- template.set_file_name(my_file_name) -}} +{% set construct_name = ctx.id.split('.')[-1] %} +{%- set instrToVecTypes = { + 'counter': "CounterVec", + 'gauge': "GaugeVec", +}-%} +{%- set instrToOptTypes = { + 'counter': "CounterOpts", + 'gauge': "GaugeOpts", +}-%} +{%- set labelTypeToTypes = { + 'string': "string", + 'int': "int", + 'double': "float64", +}-%} +{% macro const_label_type(label) -%} +{{ construct_name | pascal_case ~ label.tag | pascal_case }} +{%- endmacro %} +// Copyright 2025 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. + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // TODO(bwplotka): Use id prefix or something more unique? + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +{%- for label in ctx.attributes -%} + {%- if label.type.members is defined %} + +type {{ const_label_type(label) }} string + +const ( + {%- for value in label.type.members %} + {{ value.id | pascal_case }}{{ const_label_type(label) }} {{ const_label_type(label) }} = "{{ value.value }}" + {%- endfor %} +) + {%- endif %} +{%- endfor %} + +func MustNew{{ construct_name | pascal_case }}{{ ctx.instrument | pascal_case }}Vec(reg prometheus.Registerer) *prometheus.{{ instrToVecTypes[ctx.instrument] }} { + return promauto.With(reg).New{{ instrToVecTypes[ctx.instrument] }}(prometheus.{{ instrToOptTypes[ctx.instrument] }}{ + Name: "{{ ctx.metric_name }}", + {% if ctx.note is defined -%} + Help: "{{ ctx.note }}", + {% else -%} + Help: "{{ ctx.brief }}", + {%- endif %} + // Unit: "{{ ctx.unit }}" // TODO(bwplotka): Add Unit as one of the supported options. + }, []string{ +{%- for label in ctx.attributes %} + "{{label.tag}}", +{%- endfor %} + }) +} + +/* +TODO(bwplotka): Add more type safety e.g. for CustomElementsCounterVec: + +type CustomElementsCounterVec struct { + prometheus.CounterVec +} + +func (v *CustomElementsCounterVec) WithLabelValues(integer int, category CustomElementsCategory, fraction float64) prometheus.Counter { + // This is not ideal as we do, potentially expensive stringifying on the hot path. + // Fix might require internals to completely differ in the client_golang for the efficient solution. + return v.CounterVec.WithLabelValues(fmt.Sprintf("%v", integer), string(category), fmt.Sprintf("%v", fraction)) +} +*/ + + + + diff --git a/client_golang/semconv/registry/go/weaver.yaml b/client_golang/semconv/registry/go/weaver.yaml new file mode 100644 index 0000000..941f018 --- /dev/null +++ b/client_golang/semconv/registry/go/weaver.yaml @@ -0,0 +1,17 @@ +templates: +- template: metric.go.j2 + filter: > + .groups + | map(select(.type == "metric")) + | sort_by(.metric_name) + application_mode: each + +comment_formats: + go: + format: markdown + prefix: "// " + indent_first_level_list_items: true + shortcut_reference_link: true + trim: true + remove_trailing_dots: true +default_comment_format: go diff --git a/go/my-app/main.go b/go/my-app/main.go index 71d6934..8833d24 100644 --- a/go/my-app/main.go +++ b/go/my-app/main.go @@ -3,11 +3,13 @@ package main import ( "context" "flag" + "log" "log/slog" "net/http" "os" "syscall" + semconv "github.com/bwplotka/metric-rename-demo/go/my-app/my-app/semconv/v0.1.0" "github.com/nelkinda/health-go" "github.com/oklog/run" "github.com/prometheus/client_golang/prometheus" @@ -18,6 +20,7 @@ import ( func main() { addrFlag := flag.String("listen-address", ":9011", "Address to listen on. Available HTTP paths: /metrics") + metricDefinition := flag.String("metric-source", "manual", "Metric definition source to use ['manual', 'generated@v0.1.0'") flag.Parse() reg := prometheus.NewRegistry() @@ -26,12 +29,16 @@ func main() { collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), ) - customStableMetric := promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ - Name: "my_app_custom_counter_total", - Help: "Custom counter metric for my app counts important things. It serves as an example " + - "of a very important metric that everyone is using.", - }, []string{"integer", "category", "fraction"}) - customStableMetric.WithLabelValues("101", "AType", "1.22314").Inc() + switch *metricDefinition { + case "manual": + m := mustNewCustomStableMetric(reg) + m.WithLabelValues("101", "a", "1.22314").Inc() + case "generated@v0.1.0": + m := semconv.MustNewCustomElementsCounterVec(reg) + m.WithLabelValues("101", string(semconv.ACustomElementsCategory), "1.22314").Inc() // TODO(bwplotka): Make it more type safe. + default: + log.Fatalf("unknown -metric-source source, got %v", *metricDefinition) + } var g run.Group { diff --git a/go/my-app/manual.go b/go/my-app/manual.go new file mode 100644 index 0000000..f7c139c --- /dev/null +++ b/go/my-app/manual.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +func mustNewCustomStableMetric(reg prometheus.Registerer) *prometheus.CounterVec { + return promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ + Name: "my_app_custom_elements_total", + Help: "Custom counter metric for my app counting important elements. It serves as an example " + + "of a very important metric that everyone is using.", + }, []string{"integer", "category", "fraction"}) +} diff --git a/go/my-app/semconv/v0.1.0/my_app_custom_elements_total.go b/go/my-app/semconv/v0.1.0/my_app_custom_elements_total.go new file mode 100644 index 0000000..86aa8ab --- /dev/null +++ b/go/my-app/semconv/v0.1.0/my_app_custom_elements_total.go @@ -0,0 +1,59 @@ + +// Copyright 2025 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. + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // TODO(bwplotka): Use id prefix or something more unique? + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +type CustomElementsCategory string + +const ( + ACustomElementsCategory CustomElementsCategory = "a" + BCustomElementsCategory CustomElementsCategory = "b" + OtherCustomElementsCategory CustomElementsCategory = "other" +) + +func MustNewCustomElementsCounterVec(reg prometheus.Registerer) *prometheus.CounterVec { + return promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ + Name: "my_app_custom_elements_total", + Help: "Custom counter metric for my app counting important elements. It serves as an example of a very important metric that everyone is using.", + // Unit: "{elements}" // TODO(bwplotka): Add Unit as one of the supported options. + }, []string{ + "integer", + "category", + "fraction", + }) +} + +/* +TODO(bwplotka): Add more type safety e.g. for CustomElementsCounterVec: + +type CustomElementsCounterVec struct { + prometheus.CounterVec +} + +func (v *CustomElementsCounterVec) WithLabelValues(integer int, category CustomElementsCategory, fraction float64) prometheus.Counter { + // This is not ideal as we do, potentially expensive stringifying on the hot path. + // Fix might require internals to completely differ in the client_golang for the efficient solution. + return v.CounterVec.WithLabelValues(fmt.Sprintf("%v", integer), string(category), fmt.Sprintf("%v", fraction)) +} +*/ + + + diff --git a/go/my-app/semconv/v0.1.0/my_app_some_elements_total.go b/go/my-app/semconv/v0.1.0/my_app_some_elements_total.go new file mode 100644 index 0000000..1c7bb4c --- /dev/null +++ b/go/my-app/semconv/v0.1.0/my_app_some_elements_total.go @@ -0,0 +1,48 @@ + +// Copyright 2025 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. + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // TODO(bwplotka): Use id prefix or something more unique? + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +func MustNewSomeElementsCounterVec(reg prometheus.Registerer) *prometheus.CounterVec { + return promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ + Name: "my_app_some_elements_total", + Help: "old metric", + // Unit: "{unknown}" // TODO(bwplotka): Add Unit as one of the supported options. + }, []string{ + }) +} + +/* +TODO(bwplotka): Add more type safety e.g. for CustomElementsCounterVec: + +type CustomElementsCounterVec struct { + prometheus.CounterVec +} + +func (v *CustomElementsCounterVec) WithLabelValues(integer int, category CustomElementsCategory, fraction float64) prometheus.Counter { + // This is not ideal as we do, potentially expensive stringifying on the hot path. + // Fix might require internals to completely differ in the client_golang for the efficient solution. + return v.CounterVec.WithLabelValues(fmt.Sprintf("%v", integer), string(category), fmt.Sprintf("%v", fraction)) +} +*/ + + + diff --git a/my-org/semconv/v0.1.0/my-app.yaml b/my-org/semconv/v0.1.0/my-app.yaml new file mode 100644 index 0000000..65b6769 --- /dev/null +++ b/my-org/semconv/v0.1.0/my-app.yaml @@ -0,0 +1,45 @@ +# Schema: https://github.com/open-telemetry/weaver/blob/main/schemas/semconv.schema.json +groups: +- id: metric.my-org.custom_elements + type: metric + metric_name: my_app_custom_elements_total + brief: "Custom counter metric for my app counting important elements. It serves as an example of a very important metric that everyone is using." + instrument: counter + # Unit schema: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/metrics.md#instrument-units + # It comes from https://unitsofmeasure.org/ucum standard. + unit: "{elements}" + attributes: + - id: metric.my-org.custom_elements.integer + tag: "integer" + type: int + brief: | + This is an important label that specifies the integer for this count. + - id: metric.my-org.custom_elements.category + tag: "category" + type: + members: + - id: a + value: "a" + stability: stable + - id: b + value: "b" + stability: stable + - id: other + value: "other" + stability: stable + brief: | + This is an important label that specifies the category for this count. + - id: metric.my-org.custom_elements.fraction + tag: "fraction" + type: double + brief: | + This is an important label that specifies the fraction for this count. + stability: stable +- id: metric.my-org.some_elements + type: metric + metric_name: my_app_some_elements_total + deprecated: "Deprecated, not needed anymore" + brief: "old metric" + instrument: counter + unit: "{unknown}" + stability: experimental