@cryptotaxi247 / netdata-1 / commits / c6d0e417a

chore(otel/netdataexporter): poc version (#20199)

Ilya Mashchenko committed May 2, 2025 at 11:57 UTC c6d0e417a3e5438e9ad981d2248d503faf230fbb
11 files changed +1892 -171
src/go/otel-collector/builder-config.yaml
+5 -5
@@ -8,13 +8,13 @@ dist:
8
9
10 receivers:
11 - - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.124.0
12 - - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver v0.124.0
11 + - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.125.0
12 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver v0.125.0
13
14 exporters:
15 - - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.124.0
16 - - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.124.0
17 - - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.124.0
15 + - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.125.0
16 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.125.0
17 + - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.125.0
18 - gomod: github.com/netdata/netdata/otel-collector/exporter/journaldexporter v0.0.0
19 - gomod: github.com/netdata/netdata/otel-collector/exporter/netdataexporter v0.0.0
20
src/go/otel-collector/exporter/journaldexporter/go.mod
+24 -23
@@ -5,14 +5,14 @@ go 1.24.0
5 require (
6 github.com/google/uuid v1.6.0
7 github.com/stretchr/testify v1.10.0
8 - go.opentelemetry.io/collector/component v1.30.0
9 - go.opentelemetry.io/collector/component/componenttest v0.124.0
10 - go.opentelemetry.io/collector/confmap v1.30.0
11 - go.opentelemetry.io/collector/consumer v1.30.0
12 - go.opentelemetry.io/collector/exporter v0.124.0
13 - go.opentelemetry.io/collector/exporter/exportertest v0.124.0
14 - go.opentelemetry.io/collector/exporter/xexporter v0.124.0
15 - go.opentelemetry.io/collector/pdata v1.30.0
8 + go.opentelemetry.io/collector/component v1.31.0
9 + go.opentelemetry.io/collector/component/componenttest v0.125.0
10 + go.opentelemetry.io/collector/confmap v1.31.0
11 + go.opentelemetry.io/collector/consumer v1.31.0
12 + go.opentelemetry.io/collector/exporter v0.125.0
13 + go.opentelemetry.io/collector/exporter/exportertest v0.125.0
14 + go.opentelemetry.io/collector/exporter/xexporter v0.125.0
15 + go.opentelemetry.io/collector/pdata v1.31.0
16 go.uber.org/goleak v1.3.0
17 go.uber.org/zap v1.27.0
18 )
@@ -35,19 +35,19 @@ require (
35 github.com/modern-go/reflect2 v1.0.2 // indirect
36 github.com/pmezard/go-difflib v1.0.0 // indirect
37 go.opentelemetry.io/auto/sdk v1.1.0 // indirect
38 - go.opentelemetry.io/collector/config/configretry v1.30.0 // indirect
39 - go.opentelemetry.io/collector/consumer/consumererror v0.124.0 // indirect
40 - go.opentelemetry.io/collector/consumer/consumertest v0.124.0 // indirect
41 - go.opentelemetry.io/collector/consumer/xconsumer v0.124.0 // indirect
42 - go.opentelemetry.io/collector/extension v1.30.0 // indirect
43 - go.opentelemetry.io/collector/extension/xextension v0.124.0 // indirect
44 - go.opentelemetry.io/collector/featuregate v1.30.0 // indirect
45 - go.opentelemetry.io/collector/internal/telemetry v0.124.0 // indirect
46 - go.opentelemetry.io/collector/pdata/pprofile v0.124.0 // indirect
47 - go.opentelemetry.io/collector/pipeline v0.124.0 // indirect
48 - go.opentelemetry.io/collector/receiver v1.30.0 // indirect
49 - go.opentelemetry.io/collector/receiver/receivertest v0.124.0 // indirect
50 - go.opentelemetry.io/collector/receiver/xreceiver v0.124.0 // indirect
38 + go.opentelemetry.io/collector/config/configretry v1.31.0 // indirect
39 + go.opentelemetry.io/collector/consumer/consumererror v0.125.0 // indirect
40 + go.opentelemetry.io/collector/consumer/consumertest v0.125.0 // indirect
41 + go.opentelemetry.io/collector/consumer/xconsumer v0.125.0 // indirect
42 + go.opentelemetry.io/collector/extension v1.31.0 // indirect
43 + go.opentelemetry.io/collector/extension/xextension v0.125.0 // indirect
44 + go.opentelemetry.io/collector/featuregate v1.31.0 // indirect
45 + go.opentelemetry.io/collector/internal/telemetry v0.125.0 // indirect
46 + go.opentelemetry.io/collector/pdata/pprofile v0.125.0 // indirect
47 + go.opentelemetry.io/collector/pipeline v0.125.0 // indirect
48 + go.opentelemetry.io/collector/receiver v1.31.0 // indirect
49 + go.opentelemetry.io/collector/receiver/receivertest v0.125.0 // indirect
50 + go.opentelemetry.io/collector/receiver/xreceiver v0.125.0 // indirect
51 go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 // indirect
52 go.opentelemetry.io/otel v1.35.0 // indirect
53 go.opentelemetry.io/otel/log v0.11.0 // indirect
@@ -59,8 +59,9 @@ require (
59 golang.org/x/net v0.39.0 // indirect
60 golang.org/x/sys v0.32.0 // indirect
61 golang.org/x/text v0.24.0 // indirect
62 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect
63 - google.golang.org/grpc v1.71.1 // indirect
62 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
63 + google.golang.org/grpc v1.72.0 // indirect
64 google.golang.org/protobuf v1.36.6 // indirect
65 gopkg.in/yaml.v3 v3.0.1 // indirect
66 + sigs.k8s.io/yaml v1.4.0 // indirect
67 )
src/go/otel-collector/exporter/journaldexporter/go.sum
+55 -50
@@ -14,6 +14,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
14 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
15 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
16 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
17 +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
18 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
19 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
20 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -56,52 +57,54 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
57 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
58 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
59 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
59 -go.opentelemetry.io/collector/component v1.30.0 h1:HXjqBHaQ47/EEuWdnkjr4Y3kRWvmyWIDvqa1Q262Fls=
60 -go.opentelemetry.io/collector/component v1.30.0/go.mod h1:vfM9kN+BM6oHBXWibquiprz8CVawxd4/aYy3nbhme3E=
61 -go.opentelemetry.io/collector/component/componenttest v0.124.0 h1:Wsc+DmDrWTFs/aEyjDA3slNwV+h/0NOyIR5Aywvr6Zw=
62 -go.opentelemetry.io/collector/component/componenttest v0.124.0/go.mod h1:NQ4ATOzMFc7QA06B993tq8o27DR0cu/JR/zK7slGJ3E=
63 -go.opentelemetry.io/collector/config/configretry v1.30.0 h1:sapni1tymwNiuI0PjqlRR5CvYxIQYT8tyjQGVJDkVPM=
64 -go.opentelemetry.io/collector/config/configretry v1.30.0/go.mod h1:QNnb+MCk7aS1k2EuGJMtlNCltzD7b8uC7Xel0Dxm1wQ=
65 -go.opentelemetry.io/collector/confmap v1.30.0 h1:Y0MXhjQCdMyJN9xZMWWdNPWs6ncMVf7YVnyAEN2dAcM=
66 -go.opentelemetry.io/collector/confmap v1.30.0/go.mod h1:9DdThVDIC3VsdtTb7DgT+HwusWOocoqDkd/TErEtQgA=
67 -go.opentelemetry.io/collector/consumer v1.30.0 h1:Nn6kFTH+EJbv13E0W+sNvWrTgbiFCRv8f6DaA2F1DQs=
68 -go.opentelemetry.io/collector/consumer v1.30.0/go.mod h1:edRyfk61ugdhCQ93PBLRZfYMVWjdMPpKP8z5QLyESf0=
69 -go.opentelemetry.io/collector/consumer/consumererror v0.124.0 h1:OmeJex0C8jcwyILG+eJIGDe6rGaR15fip+Rj3XyMTRY=
70 -go.opentelemetry.io/collector/consumer/consumererror v0.124.0/go.mod h1:A6WL/E70dUh9oFdQsL+aVYGL+d6UqiAhi7rIW/6gvDM=
71 -go.opentelemetry.io/collector/consumer/consumertest v0.124.0 h1:2arChG4RPrHW3lfVWlK/KDF7Y7qkUm/YAiBXh8oTue0=
72 -go.opentelemetry.io/collector/consumer/consumertest v0.124.0/go.mod h1:Hlu+EXbINHxVAyIT1baKO2d0j5odR3fLlLAiaP+JqQg=
73 -go.opentelemetry.io/collector/consumer/xconsumer v0.124.0 h1:/cut96EWVNoz6lIeGI9+EzS6UClMtnZkx5YIpkD0Xe0=
74 -go.opentelemetry.io/collector/consumer/xconsumer v0.124.0/go.mod h1:fHH/MpzFCRNk/4foiYE6BoXQCAMf5sJTO35uvzVrrd4=
75 -go.opentelemetry.io/collector/exporter v0.124.0 h1:ii+9tU/iSrPl4+YDvqFVflksA9hUYEzwMIpmvP4JZ8w=
76 -go.opentelemetry.io/collector/exporter v0.124.0/go.mod h1:Q8tOEwFu3CN8VGjE4H2yZcCRG9Q60foQIyZGKPD/jig=
77 -go.opentelemetry.io/collector/exporter/exportertest v0.124.0 h1:IOxA/4CiVWGPlmA0JofK6W4DzvwW1YJes09r6osluIE=
78 -go.opentelemetry.io/collector/exporter/exportertest v0.124.0/go.mod h1:2EmU8IwVJV79MmFBFFW1LCN0Ob2UZsEkX/mSUB06lbI=
79 -go.opentelemetry.io/collector/exporter/xexporter v0.124.0 h1:Itfn2+F4ki8hObOtPCecWBwGpuxakUYSsTwwkB5iUns=
80 -go.opentelemetry.io/collector/exporter/xexporter v0.124.0/go.mod h1:dNK/PPY02gA9BawIKHyVk8kIFdYvqVZ2A+LlMZucIPY=
81 -go.opentelemetry.io/collector/extension v1.30.0 h1:AJqntAp1p40Q1az2Vze3OHiMURq56KWnUxaLzs1ghaA=
82 -go.opentelemetry.io/collector/extension v1.30.0/go.mod h1:a21WpypFQp9x0Go7yMOknYmIKvdIoWGzjz+h1WMjzLk=
83 -go.opentelemetry.io/collector/extension/extensiontest v0.124.0 h1:pWfKxEqvq5vVdQy+UIerw5j7ZrXezxsfQfuUe8g/xVo=
84 -go.opentelemetry.io/collector/extension/extensiontest v0.124.0/go.mod h1:DLVRyW7tJt8TtYq0Wr5BUsM494YqDiIjN8YCmbVKqjs=
85 -go.opentelemetry.io/collector/extension/xextension v0.124.0 h1:Yzf11HXaiMHfS50Zy/CYKfJjoi+/w/tgRZdDQ2VIdW0=
86 -go.opentelemetry.io/collector/extension/xextension v0.124.0/go.mod h1:GeM0aSgwVSba3Bvvspuy1E+1aa/Q1CDxoK+e/xcJFVg=
87 -go.opentelemetry.io/collector/featuregate v1.30.0 h1:mx7+iP/FQnY7KO8qw/xE3Qd1MQkWcU8VgcqLNrJ8EU8=
88 -go.opentelemetry.io/collector/featuregate v1.30.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc=
89 -go.opentelemetry.io/collector/internal/telemetry v0.124.0 h1:kzd1/ZYhLj4bt2pDB529mL4rIRrRacemXodFNxfhdWk=
90 -go.opentelemetry.io/collector/internal/telemetry v0.124.0/go.mod h1:ZjXjqV0dJ+6D4XGhTOxg/WHjnhdmXsmwmUSgALea66Y=
91 -go.opentelemetry.io/collector/pdata v1.30.0 h1:j3jyq9um436r6WzWySzexP2nLnFdmL5uVBYAlyr9nDM=
92 -go.opentelemetry.io/collector/pdata v1.30.0/go.mod h1:0Bxu1ktuj4wE7PIASNSvd0SdBscQ1PLtYasymJ13/Cs=
93 -go.opentelemetry.io/collector/pdata/pprofile v0.124.0 h1:ZjL9wKqzP4BHj0/F1jfGxs1Va8B7xmYayipZeNVoWJE=
94 -go.opentelemetry.io/collector/pdata/pprofile v0.124.0/go.mod h1:1EN3Gw5LSI4fSVma/Yfv/6nqeuYgRTm1/kmG5nE5Oyo=
95 -go.opentelemetry.io/collector/pdata/testdata v0.124.0 h1:vY+pWG7CQfzzGSB5+zGYHQOltRQr59Ek9QiPe+rI+NY=
96 -go.opentelemetry.io/collector/pdata/testdata v0.124.0/go.mod h1:lNH48lGhGv4CYk27fJecpsR1zYHmZjKgNrAprwjym0o=
97 -go.opentelemetry.io/collector/pipeline v0.124.0 h1:hKvhDyH2GPnNO8LGL34ugf36sY7EOXPjBvlrvBhsOdw=
98 -go.opentelemetry.io/collector/pipeline v0.124.0/go.mod h1:TO02zju/K6E+oFIOdi372Wk0MXd+Szy72zcTsFQwXl4=
99 -go.opentelemetry.io/collector/receiver v1.30.0 h1:XbgU4yT3Ld+hL9+jHcD/Kctcr3gXjpiFxKO+50pSayg=
100 -go.opentelemetry.io/collector/receiver v1.30.0/go.mod h1:U3cApz9PHiRMgN0WkZaz4o8mvj1+cVQYsyj2Nl1v3FQ=
101 -go.opentelemetry.io/collector/receiver/receivertest v0.124.0 h1:mx0290aXAo+wfjm4NgbKUodjT5SbS306zmk+AeqeVxE=
102 -go.opentelemetry.io/collector/receiver/receivertest v0.124.0/go.mod h1:3RpopRmIzx5T4zTStHJC0HHfd8YFWm8e9bia1HiuDtY=
103 -go.opentelemetry.io/collector/receiver/xreceiver v0.124.0 h1:YigTUKk8p/aIfqaT0ST7teT9KbLThWD5n2km83byftw=
104 -go.opentelemetry.io/collector/receiver/xreceiver v0.124.0/go.mod h1:NkTpmpAEDT17Dko4gpHUnRztrSkdSd6B0+Y4gfuCWIA=
60 +go.opentelemetry.io/collector/client v1.31.0 h1:PdmUJSx8FgFcrqm12pMwvdVp98aYSdaKjMqJandFIgE=
61 +go.opentelemetry.io/collector/client v1.31.0/go.mod h1:pSyJ1+XhsLP6nqJDP7uj3AFTw26z9mqCnRGyMw0Im8o=
62 +go.opentelemetry.io/collector/component v1.31.0 h1:9LzU8X1RhV3h8/QsAoTX23aFUfoJ3EUc9O/vK+hFpSI=
63 +go.opentelemetry.io/collector/component v1.31.0/go.mod h1:JbZl/KywXJxpUXPbt96qlEXJSym1zQ2hauMxYMuvlxM=
64 +go.opentelemetry.io/collector/component/componenttest v0.125.0 h1:E2mpnMQbkMpYoZ3Q8pHx4kod7kedjwRs1xqDpzCe/84=
65 +go.opentelemetry.io/collector/component/componenttest v0.125.0/go.mod h1:pQtsE1u/SPZdTphP5BZP64XbjXSq6wc+mDut5Ws/JDI=
66 +go.opentelemetry.io/collector/config/configretry v1.31.0 h1:GWl/UM7+xNCmXBz5lvaMxBIQTcNn1EcCvMjVvUwgOLg=
67 +go.opentelemetry.io/collector/config/configretry v1.31.0/go.mod h1:QNnb+MCk7aS1k2EuGJMtlNCltzD7b8uC7Xel0Dxm1wQ=
68 +go.opentelemetry.io/collector/confmap v1.31.0 h1:+AW5VJc1rCtgEyGd+1J5uSNw/kVZ98+lKO/pqXEwVvU=
69 +go.opentelemetry.io/collector/confmap v1.31.0/go.mod h1:TdutQlIoHDPXcZ2xZ0QWGRkSFC8oTKO61zTx569dvrY=
70 +go.opentelemetry.io/collector/consumer v1.31.0 h1:L+y66ywxLHnAxnUxv0JDwUf5bFj53kMxCCyEfRKlM7s=
71 +go.opentelemetry.io/collector/consumer v1.31.0/go.mod h1:rPsqy5ni+c6xNMUkOChleZYO/nInVY6eaBNZ1FmWJVk=
72 +go.opentelemetry.io/collector/consumer/consumererror v0.125.0 h1:Qq9SgbxlJoRn0952dj4lPJhcuBiqKzD1aNxCfa+Bz00=
73 +go.opentelemetry.io/collector/consumer/consumererror v0.125.0/go.mod h1:t/YDU7G2TxG27LbcUvgKo/l75TI5VApnnqC7FgKZds0=
74 +go.opentelemetry.io/collector/consumer/consumertest v0.125.0 h1:TUkxomGS4DAtjBvcWQd2UY4FDLLEKMQD6iOIDUr/5dM=
75 +go.opentelemetry.io/collector/consumer/consumertest v0.125.0/go.mod h1:vkHf3y85cFLDHARO/cTREVjLjOPAV+cQg7lkC44DWOY=
76 +go.opentelemetry.io/collector/consumer/xconsumer v0.125.0 h1:oTreUlk1KpMSWwuHFnstW+orrjGTyvs2xd3o/Dpy+hI=
77 +go.opentelemetry.io/collector/consumer/xconsumer v0.125.0/go.mod h1:FX0G37r0W+wXRgxxFtwEJ4rlsCB+p0cIaxtU3C4hskw=
78 +go.opentelemetry.io/collector/exporter v0.125.0 h1:GJxeCuOQVPndQzvzpZUFEv5njHuVnzBEuXgddRShiCM=
79 +go.opentelemetry.io/collector/exporter v0.125.0/go.mod h1:x+FFBRxWGk+GGaP7u+Bi+OcZoG5qK5vZmgFwI/t4ZwM=
80 +go.opentelemetry.io/collector/exporter/exportertest v0.125.0 h1:FFCEHKlTct0vl/657E1papmL3NicdcxY4ChZbHI3nWk=
81 +go.opentelemetry.io/collector/exporter/exportertest v0.125.0/go.mod h1:UQ6ETigK5yTASXGvjSlE7RcM8C6Ro+TcR8iqDHpXa2U=
82 +go.opentelemetry.io/collector/exporter/xexporter v0.125.0 h1:be5oK6+QOXvyXiCxfiTKxdo/TmjewvciCbC8iMKi/1s=
83 +go.opentelemetry.io/collector/exporter/xexporter v0.125.0/go.mod h1:ebfTFVtKR2eUiAQQc/cS1aBTV2OoFDvFjLiX+thE23s=
84 +go.opentelemetry.io/collector/extension v1.31.0 h1:DaqSl50jOA3BGtqPfPtSGJy4XwyXtQwvemVl/L9fDb4=
85 +go.opentelemetry.io/collector/extension v1.31.0/go.mod h1:SiRNOZIJ6R0JbHEvs3g84hPEmiys5CZyIlMOE1RQ85s=
86 +go.opentelemetry.io/collector/extension/extensiontest v0.125.0 h1:NYtQ42HBjfS5lnfmZdT8+ucq+i70JGLdvDN8V/drvIw=
87 +go.opentelemetry.io/collector/extension/extensiontest v0.125.0/go.mod h1:HABANc94xQmUtOSZokG5E6Z02GrHfKYSkQqOz+oCpPQ=
88 +go.opentelemetry.io/collector/extension/xextension v0.125.0 h1:4BOzKuI5jtNP74X7LMeezFidSYdg8ny3Epv4GzV7LO4=
89 +go.opentelemetry.io/collector/extension/xextension v0.125.0/go.mod h1:JNx5t/UnzxJiH2piNtko7Nj3E4x6YNkWxrVlpvpbm6w=
90 +go.opentelemetry.io/collector/featuregate v1.31.0 h1:20q7plPQZwmAiaYAa6l1m/i2qDITZuWlhjr4EkmeQls=
91 +go.opentelemetry.io/collector/featuregate v1.31.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc=
92 +go.opentelemetry.io/collector/internal/telemetry v0.125.0 h1:6lcGOxw3dAg7LfXTKdN8ZjR+l7KvzLdEiPMhhLwG4r4=
93 +go.opentelemetry.io/collector/internal/telemetry v0.125.0/go.mod h1:5GyFslLqjZgq1DZTtFiluxYhhXrCofHgOOOybodDPGE=
94 +go.opentelemetry.io/collector/pdata v1.31.0 h1:P5WuLr1l2JcIvr6Dw2hl01ltp2ZafPnC4Isv+BLTBqU=
95 +go.opentelemetry.io/collector/pdata v1.31.0/go.mod h1:m41io9nWpy7aCm/uD1L9QcKiZwOP0ldj83JEA34dmlk=
96 +go.opentelemetry.io/collector/pdata/pprofile v0.125.0 h1:Qqlx8w1HpiYZ9RQqjmMQIysI0cHNO1nh3E/fCTeFysA=
97 +go.opentelemetry.io/collector/pdata/pprofile v0.125.0/go.mod h1:p/yK023VxAp8hm27/1G5DPTcMIpnJy3cHGAFUQZGyaQ=
98 +go.opentelemetry.io/collector/pdata/testdata v0.125.0 h1:due1Hl0EEVRVwfCkiamRy5E8lS6yalv0lo8Zl/SJtGw=
99 +go.opentelemetry.io/collector/pdata/testdata v0.125.0/go.mod h1:1GpEWlgdMrd+fWsBk37ZC2YmOP5YU3gFQ4rWuCu9g24=
100 +go.opentelemetry.io/collector/pipeline v0.125.0 h1:oitBgcAFqntDB4ihQJUHJSQ8IHqKFpPkaTVbTYdIUzM=
101 +go.opentelemetry.io/collector/pipeline v0.125.0/go.mod h1:TO02zju/K6E+oFIOdi372Wk0MXd+Szy72zcTsFQwXl4=
102 +go.opentelemetry.io/collector/receiver v1.31.0 h1:OSRrCWclb1QmGPnxFMxQsdegua4vlKpZESOtDKSzKeQ=
103 +go.opentelemetry.io/collector/receiver v1.31.0/go.mod h1:zPUiv3jgJGQSY01nx500cYJiEz6JfaR53BAvCW2tgGs=
104 +go.opentelemetry.io/collector/receiver/receivertest v0.125.0 h1:xV3Jm3OT7SfDpJ5mXhNmK/Nch7f41whA8k0q4XkaWT8=
105 +go.opentelemetry.io/collector/receiver/receivertest v0.125.0/go.mod h1:CoSPPn3kwVcAhdLOK5NVjMCVVjmCsFpBXU9h9I6Kdh8=
106 +go.opentelemetry.io/collector/receiver/xreceiver v0.125.0 h1:wQ1ZgCA0Y3EFWWZ6gZDGDXT7qV5IrqkisccUWhvmmTg=
107 +go.opentelemetry.io/collector/receiver/xreceiver v0.125.0/go.mod h1:5Kl/mtf6oIy+rizFcElkUpp3LEVTJnjAyos6z+FVsGc=
108 go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 h1:ojdSRDvjrnm30beHOmwsSvLpoRF40MlwNCA+Oo93kXU=
109 go.opentelemetry.io/contrib/bridges/otelzap v0.10.0/go.mod h1:oTTm4g7NEtHSV2i/0FeVdPaPgUIZPfQkFbq0vbzqnv0=
110 go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
@@ -153,10 +156,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
156 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
157 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
158 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
156 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA=
157 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
158 -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
159 -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
159 +google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
160 +google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
161 +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
162 +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
163 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
164 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
165 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -164,3 +167,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
167 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
168 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
169 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
170 +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
171 +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
src/go/otel-collector/exporter/netdataexporter/convert.go new
+821
@@ -0,0 +1,821 @@
1 +package netdataexporter
2 +
3 +import (
4 + "fmt"
5 + "hash/fnv"
6 + "sort"
7 + "strconv"
8 + "strings"
9 + "unicode"
10 +
11 + "go.opentelemetry.io/collector/pdata/pcommon"
12 + "go.opentelemetry.io/collector/pdata/pmetric"
13 +)
14 +
15 +type ChartDefinition struct {
16 + ID string // must be globally unique
17 + Title string
18 + Units string
19 + Family string
20 + Context string // metric name
21 + Type string // line, area, stacked
22 + Options string // set to "obsolete" to delete the chart, otherwise empty string
23 + Labels []LabelDefinition
24 + Dimensions []DimensionDefinition
25 + IsNew bool // Indicates if this chart is new and needs to be sent to Netdata
26 +}
27 +
28 +// LabelDefinition represents a Netdata chart label
29 +type LabelDefinition struct {
30 + Name string
31 + Value string
32 +}
33 +
34 +// DimensionDefinition represents a Netdata chart dimension
35 +type DimensionDefinition struct {
36 + ID string // must be unique within the chart
37 + Name string // replaces ID in UI
38 + Algo string // absolute (Gauge), incremental (Counter)
39 + Value float64
40 +}
41 +
42 +// Maximum length for chart IDs to prevent excessively long IDs
43 +const maxChartIDLength = 1000
44 +
45 +// Convert transforms OTLP metrics into Netdata charts
46 +func (e *netdataExporter) Convert(pms pmetric.Metrics) map[string]*ChartDefinition {
47 + // Track new and updated charts in this batch
48 + currentCharts := make(map[string]bool, len(e.charts))
49 +
50 + for _, rm := range pms.ResourceMetrics().All() {
51 + resAttrs := rm.Resource().Attributes()
52 +
53 + for _, sm := range rm.ScopeMetrics().All() {
54 + for _, metric := range sm.Metrics().All() {
55 + switch metric.Type() {
56 + case pmetric.MetricTypeGauge:
57 + e.processGauge(metric, resAttrs, currentCharts)
58 + case pmetric.MetricTypeSum:
59 + e.processSum(metric, resAttrs, currentCharts)
60 + case pmetric.MetricTypeHistogram:
61 + e.processHistogram(metric, resAttrs, currentCharts)
62 + case pmetric.MetricTypeExponentialHistogram:
63 + e.processExponentialHistogram(metric, resAttrs, currentCharts)
64 + case pmetric.MetricTypeSummary:
65 + e.processSummary(metric, resAttrs, currentCharts)
66 + default:
67 + }
68 + }
69 + }
70 + }
71 +
72 + for id, chart := range e.charts {
73 + if !currentCharts[id] {
74 + chart.IsNew = false
75 + }
76 + }
77 +
78 + return e.charts
79 +}
80 +
81 +// processGauge handles gauge metric types
82 +func (e *netdataExporter) processGauge(metric pmetric.Metric, resourceAttrs pcommon.Map, currentCharts map[string]bool) {
83 + gauge := metric.Gauge()
84 + metricName := metric.Name()
85 +
86 + // Group data points by their attributes to create separate charts
87 + attributeGroups := groupDataPointsByAttributes(gauge.DataPoints())
88 +
89 + for attrHash, points := range attributeGroups {
90 + // Generate a unique chart ID
91 + chartID := generateChartID(metricName, attrHash)
92 + currentCharts[chartID] = true
93 +
94 + // Get or create chart
95 + chart, exists := e.charts[chartID]
96 + if !exists {
97 + chart = &ChartDefinition{
98 + ID: chartID,
99 + Title: getTitle(metric),
100 + Units: getUnits(metric),
101 + Family: getFamily(metricName, resourceAttrs),
102 + Context: metricName,
103 + Type: "line", // Default to line chart
104 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
105 + IsNew: true,
106 + }
107 + e.charts[chartID] = chart
108 + }
109 +
110 + // Update chart with resource attributes as labels
111 + updateChartLabels(chart, resourceAttrs)
112 +
113 + // Process data points
114 + dimensions := make([]DimensionDefinition, 0, len(points))
115 +
116 + for _, dp := range points {
117 + if dp.Flags().NoRecordedValue() {
118 + continue
119 + }
120 +
121 + dimID := generateDimensionID(dp.Attributes())
122 + dimName := getDimensionName(dp.Attributes())
123 +
124 + value := 0.0
125 + if dp.ValueType() == pmetric.NumberDataPointValueTypeDouble {
126 + value = dp.DoubleValue()
127 + } else {
128 + value = float64(dp.IntValue())
129 + }
130 +
131 + dimensions = append(dimensions, DimensionDefinition{
132 + ID: dimID,
133 + Name: dimName,
134 + Algo: "absolute", // Gauge uses absolute algorithm
135 + Value: value,
136 + })
137 +
138 + // Add data point attributes as labels
139 + addDataPointAttributesAsLabels(chart, dp.Attributes())
140 + }
141 +
142 + chart.Dimensions = append(chart.Dimensions[:0], dimensions...)
143 + }
144 +}
145 +
146 +// processSum handles sum metric types
147 +func (e *netdataExporter) processSum(metric pmetric.Metric, resourceAttrs pcommon.Map, currentCharts map[string]bool) {
148 + sum := metric.Sum()
149 + metricName := metric.Name()
150 +
151 + // Determine algorithm based on aggregation temporality
152 + algo := "absolute" // Default for CUMULATIVE and UNSPECIFIED
153 + if sum.AggregationTemporality() == pmetric.AggregationTemporalityDelta {
154 + algo = "incremental"
155 + }
156 +
157 + // Group data points by their attributes to create separate charts
158 + attributeGroups := groupDataPointsByAttributes(sum.DataPoints())
159 +
160 + for attrHash, points := range attributeGroups {
161 + // Generate a unique chart ID
162 + chartID := generateChartID(metricName, attrHash)
163 + currentCharts[chartID] = true
164 +
165 + // Get or create chart
166 + chart, exists := e.charts[chartID]
167 + if !exists {
168 + chart = &ChartDefinition{
169 + ID: chartID,
170 + Title: getTitle(metric),
171 + Units: getUnits(metric),
172 + Family: getFamily(metricName, resourceAttrs),
173 + Context: metricName,
174 + Type: "line", // Default to line chart
175 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
176 + IsNew: true,
177 + }
178 + e.charts[chartID] = chart
179 + }
180 +
181 + // Update chart with resource attributes as labels
182 + updateChartLabels(chart, resourceAttrs)
183 +
184 + // Process data points
185 + dimensions := make([]DimensionDefinition, 0, len(points))
186 +
187 + for _, dp := range points {
188 + if dp.Flags().NoRecordedValue() {
189 + continue
190 + }
191 +
192 + dimID := generateDimensionID(dp.Attributes())
193 + dimName := getDimensionName(dp.Attributes())
194 +
195 + value := 0.0
196 + if dp.ValueType() == pmetric.NumberDataPointValueTypeDouble {
197 + value = dp.DoubleValue()
198 + } else {
199 + value = float64(dp.IntValue())
200 + }
201 +
202 + dimensions = append(dimensions, DimensionDefinition{
203 + ID: dimID,
204 + Name: dimName,
205 + Algo: algo,
206 + Value: value,
207 + })
208 +
209 + // Add data point attributes as labels
210 + addDataPointAttributesAsLabels(chart, dp.Attributes())
211 + }
212 +
213 + chart.Dimensions = dimensions
214 + }
215 +}
216 +
217 +// processHistogram handles histogram metric types
218 +func (e *netdataExporter) processHistogram(metric pmetric.Metric, resourceAttrs pcommon.Map, currentCharts map[string]bool) {
219 + histogram := metric.Histogram()
220 + metricName := metric.Name()
221 +
222 + // Determine algorithm based on aggregation temporality
223 + algo := "absolute" // Default for CUMULATIVE and UNSPECIFIED
224 + if histogram.AggregationTemporality() == pmetric.AggregationTemporalityDelta {
225 + algo = "incremental"
226 + }
227 +
228 + for _, dp := range histogram.DataPoints().All() {
229 + if dp.Flags().NoRecordedValue() {
230 + continue
231 + }
232 +
233 + attrHash := attributesHash(dp.Attributes())
234 + dimID := generateDimensionID(dp.Attributes())
235 + dimName := getDimensionName(dp.Attributes())
236 +
237 + // 1. Count chart
238 + countChartID := generateChartID(metricName+".count", attrHash)
239 + currentCharts[countChartID] = true
240 +
241 + countChart, exists := e.charts[countChartID]
242 + if !exists {
243 + countChart = &ChartDefinition{
244 + ID: countChartID,
245 + Title: getTitle(metric) + " (Count)",
246 + Units: "count",
247 + Family: getFamily(metricName, resourceAttrs),
248 + Context: metricName + ".count",
249 + Type: "line",
250 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
251 + IsNew: true,
252 + }
253 + e.charts[countChartID] = countChart
254 + }
255 +
256 + updateChartLabels(countChart, resourceAttrs)
257 + addDataPointAttributesAsLabels(countChart, dp.Attributes())
258 +
259 + countChart.Dimensions = []DimensionDefinition{
260 + {
261 + ID: dimID,
262 + Name: dimName,
263 + Algo: algo,
264 + Value: float64(dp.Count()),
265 + },
266 + }
267 +
268 + // 2. Sum chart (if sum exists)
269 + if dp.HasSum() {
270 + sumChartID := generateChartID(metricName+".sum", attrHash)
271 + currentCharts[sumChartID] = true
272 +
273 + sumChart, exists := e.charts[sumChartID]
274 + if !exists {
275 + sumChart = &ChartDefinition{
276 + ID: sumChartID,
277 + Title: getTitle(metric) + " (Sum)",
278 + Units: getUnits(metric),
279 + Family: getFamily(metricName, resourceAttrs),
280 + Context: metricName + ".sum",
281 + Type: "line",
282 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
283 + IsNew: true,
284 + }
285 + e.charts[sumChartID] = sumChart
286 + }
287 +
288 + updateChartLabels(sumChart, resourceAttrs)
289 + addDataPointAttributesAsLabels(sumChart, dp.Attributes())
290 +
291 + sumChart.Dimensions = []DimensionDefinition{
292 + {
293 + ID: dimID,
294 + Name: dimName,
295 + Algo: algo,
296 + Value: dp.Sum(),
297 + },
298 + }
299 + }
300 +
301 + // 3. Buckets chart (if buckets exist)
302 + bucketCounts := dp.BucketCounts()
303 + explicitBounds := dp.ExplicitBounds()
304 +
305 + if bucketCounts.Len() > 0 && explicitBounds.Len() > 0 {
306 + bucketsChartID := generateChartID(metricName+".buckets", attrHash)
307 + currentCharts[bucketsChartID] = true
308 +
309 + bucketsChart, exists := e.charts[bucketsChartID]
310 + if !exists {
311 + bucketsChart = &ChartDefinition{
312 + ID: bucketsChartID,
313 + Title: getTitle(metric) + " (Buckets)",
314 + Units: "count",
315 + Family: getFamily(metricName, resourceAttrs),
316 + Context: metricName + ".buckets",
317 + Type: "stacked", // Histogram buckets should be stacked
318 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
319 + IsNew: true,
320 + }
321 + e.charts[bucketsChartID] = bucketsChart
322 + }
323 +
324 + updateChartLabels(bucketsChart, resourceAttrs)
325 + addDataPointAttributesAsLabels(bucketsChart, dp.Attributes())
326 +
327 + dimensions := make([]DimensionDefinition, 0, bucketCounts.Len())
328 +
329 + // Add dimensions for each bucket
330 + for j := 0; j < bucketCounts.Len(); j++ {
331 + var upperBound string
332 + if j < explicitBounds.Len() {
333 + upperBound = fmt.Sprintf("%.2f", explicitBounds.At(j))
334 + } else {
335 + upperBound = "inf"
336 + }
337 +
338 + bucketDimID := dimID + "_bucket_" + strconv.Itoa(j)
339 + bucketDimName := "≤ " + upperBound
340 +
341 + dimensions = append(dimensions, DimensionDefinition{
342 + ID: bucketDimID,
343 + Name: bucketDimName,
344 + Algo: algo,
345 + Value: float64(bucketCounts.At(j)),
346 + })
347 + }
348 +
349 + bucketsChart.Dimensions = dimensions
350 + }
351 + }
352 +}
353 +
354 +// processExponentialHistogram handles exponential histogram metric types
355 +func (e *netdataExporter) processExponentialHistogram(metric pmetric.Metric, resourceAttrs pcommon.Map, currentCharts map[string]bool) {
356 + expHistogram := metric.ExponentialHistogram()
357 + metricName := metric.Name()
358 +
359 + // Determine algorithm based on aggregation temporality
360 + algo := "absolute" // Default for CUMULATIVE and UNSPECIFIED
361 + if expHistogram.AggregationTemporality() == pmetric.AggregationTemporalityDelta {
362 + algo = "incremental"
363 + }
364 +
365 + for _, dp := range expHistogram.DataPoints().All() {
366 + if dp.Flags().NoRecordedValue() {
367 + continue
368 + }
369 +
370 + attrHash := attributesHash(dp.Attributes())
371 + dimID := generateDimensionID(dp.Attributes())
372 + dimName := getDimensionName(dp.Attributes())
373 +
374 + // 1. Count chart
375 + countChartID := generateChartID(metricName+".count", attrHash)
376 + currentCharts[countChartID] = true
377 +
378 + countChart, exists := e.charts[countChartID]
379 + if !exists {
380 + countChart = &ChartDefinition{
381 + ID: countChartID,
382 + Title: getTitle(metric) + " (Count)",
383 + Units: "count",
384 + Family: getFamily(metricName, resourceAttrs),
385 + Context: metricName + ".count",
386 + Type: "line",
387 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
388 + IsNew: true,
389 + }
390 + e.charts[countChartID] = countChart
391 + }
392 +
393 + updateChartLabels(countChart, resourceAttrs)
394 + addDataPointAttributesAsLabels(countChart, dp.Attributes())
395 +
396 + countChart.Dimensions = []DimensionDefinition{
397 + {
398 + ID: dimID,
399 + Name: dimName,
400 + Algo: algo,
401 + Value: float64(dp.Count()),
402 + },
403 + }
404 +
405 + // 2. Sum chart (if sum exists)
406 + if dp.HasSum() {
407 + sumChartID := generateChartID(metricName+".sum", attrHash)
408 + currentCharts[sumChartID] = true
409 +
410 + sumChart, exists := e.charts[sumChartID]
411 + if !exists {
412 + sumChart = &ChartDefinition{
413 + ID: sumChartID,
414 + Title: getTitle(metric) + " (Sum)",
415 + Units: getUnits(metric),
416 + Family: getFamily(metricName, resourceAttrs),
417 + Context: metricName + ".sum",
418 + Type: "line",
419 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
420 + IsNew: true,
421 + }
422 + e.charts[sumChartID] = sumChart
423 + }
424 +
425 + updateChartLabels(sumChart, resourceAttrs)
426 + addDataPointAttributesAsLabels(sumChart, dp.Attributes())
427 +
428 + sumChart.Dimensions = []DimensionDefinition{
429 + {
430 + ID: dimID,
431 + Name: dimName,
432 + Algo: algo,
433 + Value: dp.Sum(),
434 + },
435 + }
436 + }
437 +
438 + // 3. Simplified histogram buckets chart
439 + // For exponential histograms, we'll create a simplified view with positive, zero, and negative counts
440 + bucketsChartID := generateChartID(metricName+".exp_buckets", attrHash)
441 + currentCharts[bucketsChartID] = true
442 +
443 + bucketsChart, exists := e.charts[bucketsChartID]
444 + if !exists {
445 + bucketsChart = &ChartDefinition{
446 + ID: bucketsChartID,
447 + Title: getTitle(metric) + " (Distribution)",
448 + Units: "count",
449 + Family: getFamily(metricName, resourceAttrs),
450 + Context: metricName + ".distribution",
451 + Type: "stacked",
452 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
453 + IsNew: true,
454 + }
455 + e.charts[bucketsChartID] = bucketsChart
456 + }
457 +
458 + updateChartLabels(bucketsChart, resourceAttrs)
459 + addDataPointAttributesAsLabels(bucketsChart, dp.Attributes())
460 +
461 + // Add dimensions for positive, negative, and zero buckets
462 + bucketsChart.Dimensions = []DimensionDefinition{
463 + {
464 + ID: dimID + "_positive",
465 + Name: "Positive Values",
466 + Algo: algo,
467 + Value: getTotalCountFromBuckets(dp.Positive()),
468 + },
469 + {
470 + ID: dimID + "_zero",
471 + Name: "Zero Values",
472 + Algo: algo,
473 + Value: float64(dp.ZeroCount()),
474 + },
475 + {
476 + ID: dimID + "_negative",
477 + Name: "Negative Values",
478 + Algo: algo,
479 + Value: getTotalCountFromBuckets(dp.Negative()),
480 + },
481 + }
482 + }
483 +}
484 +
485 +// processSummary handles summary metric types
486 +func (e *netdataExporter) processSummary(metric pmetric.Metric, resourceAttrs pcommon.Map, currentCharts map[string]bool) {
487 + summary := metric.Summary()
488 + metricName := metric.Name()
489 +
490 + for _, dp := range summary.DataPoints().All() {
491 + if dp.Flags().NoRecordedValue() {
492 + continue
493 + }
494 +
495 + attrHash := attributesHash(dp.Attributes())
496 + dimID := generateDimensionID(dp.Attributes())
497 + dimName := getDimensionName(dp.Attributes())
498 +
499 + // 1. Count chart
500 + countChartID := generateChartID(metricName+".count", attrHash)
501 + currentCharts[countChartID] = true
502 +
503 + countChart, exists := e.charts[countChartID]
504 + if !exists {
505 + countChart = &ChartDefinition{
506 + ID: countChartID,
507 + Title: getTitle(metric) + " (Count)",
508 + Units: "count",
509 + Family: getFamily(metricName, resourceAttrs),
510 + Context: metricName + ".count",
511 + Type: "line",
512 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
513 + IsNew: true,
514 + }
515 + e.charts[countChartID] = countChart
516 + }
517 +
518 + updateChartLabels(countChart, resourceAttrs)
519 + addDataPointAttributesAsLabels(countChart, dp.Attributes())
520 +
521 + countChart.Dimensions = []DimensionDefinition{
522 + {
523 + ID: dimID,
524 + Name: dimName,
525 + Algo: "absolute", // Summary count should be absolute
526 + Value: float64(dp.Count()),
527 + },
528 + }
529 +
530 + // 2. Sum chart
531 + sumChartID := generateChartID(metricName+".sum", attrHash)
532 + currentCharts[sumChartID] = true
533 +
534 + sumChart, exists := e.charts[sumChartID]
535 + if !exists {
536 + sumChart = &ChartDefinition{
537 + ID: sumChartID,
538 + Title: getTitle(metric) + " (Sum)",
539 + Units: getUnits(metric),
540 + Family: getFamily(metricName, resourceAttrs),
541 + Context: metricName + ".sum",
542 + Type: "line",
543 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
544 + IsNew: true,
545 + }
546 + e.charts[sumChartID] = sumChart
547 + }
548 +
549 + updateChartLabels(sumChart, resourceAttrs)
550 + addDataPointAttributesAsLabels(sumChart, dp.Attributes())
551 +
552 + sumChart.Dimensions = []DimensionDefinition{
553 + {
554 + ID: dimID,
555 + Name: dimName,
556 + Algo: "absolute", // Summary sum should be absolute
557 + Value: dp.Sum(),
558 + },
559 + }
560 +
561 + // 3. Quantiles chart
562 + qvs := dp.QuantileValues()
563 + if qvs.Len() > 0 {
564 + quantilesChartID := generateChartID(metricName+".quantiles", attrHash)
565 + currentCharts[quantilesChartID] = true
566 +
567 + quantilesChart, exists := e.charts[quantilesChartID]
568 + if !exists {
569 + quantilesChart = &ChartDefinition{
570 + ID: quantilesChartID,
571 + Title: getTitle(metric) + " (Quantiles)",
572 + Units: getUnits(metric),
573 + Family: getFamily(metricName, resourceAttrs),
574 + Context: metricName + ".quantiles",
575 + Type: "line",
576 + Labels: make([]LabelDefinition, 0, resourceAttrs.Len()),
577 + IsNew: true,
578 + }
579 + e.charts[quantilesChartID] = quantilesChart
580 + }
581 +
582 + updateChartLabels(quantilesChart, resourceAttrs)
583 + addDataPointAttributesAsLabels(quantilesChart, dp.Attributes())
584 +
585 + dimensions := make([]DimensionDefinition, 0, qvs.Len())
586 +
587 + // Add dimensions for each quantile
588 + for _, qv := range qvs.All() {
589 + quantileStr := fmt.Sprintf("%.2f", qv.Quantile())
590 +
591 + // Special names for min/max
592 + var quantileName string
593 + if qv.Quantile() == 0 {
594 + quantileName = "min"
595 + } else if qv.Quantile() == 1 {
596 + quantileName = "max"
597 + } else {
598 + quantileName = "p" + strings.ReplaceAll(quantileStr, "0.", "")
599 + }
600 +
601 + dimensions = append(dimensions, DimensionDefinition{
602 + ID: dimID + "_q" + strings.ReplaceAll(quantileStr, ".", "_"),
603 + Name: quantileName,
604 + Algo: "absolute", // Quantiles are absolute values
605 + Value: qv.Value(),
606 + })
607 + }
608 +
609 + quantilesChart.Dimensions = dimensions
610 + }
611 + }
612 +}
613 +
614 +// groupDataPointsByAttributes groups data points by attributes to create separate charts
615 +func groupDataPointsByAttributes(dataPoints pmetric.NumberDataPointSlice) map[string][]pmetric.NumberDataPoint {
616 + groups := make(map[string][]pmetric.NumberDataPoint)
617 +
618 + for _, dp := range dataPoints.All() {
619 + hash := attributesHash(dp.Attributes())
620 + groups[hash] = append(groups[hash], dp)
621 + }
622 +
623 + return groups
624 +}
625 +
626 +func generateChartID(metricName, attrHash string) string {
627 + // Sanitize and limit the length of the metric name to prevent overly long IDs
628 + sanitizedName := sanitizeID(metricName)
629 + if len(sanitizedName) > maxChartIDLength-1-len(attrHash) {
630 + sanitizedName = sanitizedName[:maxChartIDLength-1-len(attrHash)]
631 + }
632 + return sanitizedName + "_" + attrHash
633 +}
634 +
635 +func generateDimensionID(attrs pcommon.Map) string {
636 + if attrs.Len() == 0 {
637 + return "value"
638 + }
639 +
640 + return "value_" + attributesHash(attrs)
641 +}
642 +
643 +func getDimensionName(attrs pcommon.Map) string {
644 + if attrs.Len() == 0 {
645 + return "value"
646 + }
647 +
648 + // Try to find a good name from the attributes
649 + nameAttrs := []string{"name", "id", "key"}
650 + for _, nameAttr := range nameAttrs {
651 + if val, ok := attrs.Get(nameAttr); ok {
652 + return val.AsString()
653 + }
654 + }
655 +
656 + return "value"
657 +}
658 +
659 +func getTitle(metric pmetric.Metric) string {
660 + if metric.Description() != "" {
661 + return metric.Description()
662 + }
663 + return metric.Name()
664 +}
665 +
666 +func getUnits(metric pmetric.Metric) string {
667 + if metric.Unit() != "" {
668 + return metric.Unit()
669 + }
670 + return "count" // Default unit
671 +}
672 +
673 +func getFamily(metricName string, resourceAttrs pcommon.Map) string {
674 + var sb strings.Builder
675 +
676 + if val, ok := resourceAttrs.Get("service.name"); ok {
677 + sb.WriteString(sanitizeID(val.AsString()))
678 + } else if val, ok := resourceAttrs.Get("service.namespace"); ok {
679 + sb.WriteString(sanitizeID(val.AsString()))
680 + }
681 +
682 + // Split the metric name
683 + parts := strings.Split(metricName, ".")
684 + prefix := metricName
685 + if len(parts) > 1 {
686 + prefix = parts[0]
687 + }
688 +
689 + if sb.Len() > 0 {
690 + sb.WriteString("_")
691 + }
692 + sb.WriteString(sanitizeID(prefix))
693 +
694 + return sb.String()
695 +}
696 +
697 +func updateChartLabels(chart *ChartDefinition, resourceAttrs pcommon.Map) {
698 + if len(chart.Labels) == resourceAttrs.Len() {
699 + same := true
700 + resourceAttrs.Range(func(key string, value pcommon.Value) bool {
701 + found := false
702 + for _, label := range chart.Labels {
703 + if label.Name == key && label.Value == value.AsString() {
704 + found = true
705 + break
706 + }
707 + }
708 + if !found {
709 + same = false
710 + return false
711 + }
712 + return true
713 + })
714 + if same {
715 + return
716 + }
717 + }
718 +
719 + chart.Labels = make([]LabelDefinition, 0, resourceAttrs.Len())
720 +
721 + resourceAttrs.Range(func(key string, value pcommon.Value) bool {
722 + chart.Labels = append(chart.Labels, LabelDefinition{
723 + Name: key,
724 + Value: value.AsString(),
725 + })
726 + return true
727 + })
728 +
729 + sort.Slice(chart.Labels, func(i, j int) bool {
730 + return chart.Labels[i].Name < chart.Labels[j].Name
731 + })
732 +}
733 +
734 +func addDataPointAttributesAsLabels(chart *ChartDefinition, attrs pcommon.Map) {
735 + // Use a map to track existing labels by name for O(1) lookup
736 + existingLabels := make(map[string]bool, len(chart.Labels))
737 + for _, label := range chart.Labels {
738 + existingLabels[label.Name] = true
739 + }
740 +
741 + var newLabels []LabelDefinition
742 + attrs.Range(func(key string, value pcommon.Value) bool {
743 + if !existingLabels[key] {
744 + newLabels = append(newLabels, LabelDefinition{
745 + Name: key,
746 + Value: value.AsString(),
747 + })
748 + existingLabels[key] = true
749 + }
750 + return true
751 + })
752 +
753 + if len(newLabels) > 0 {
754 + chart.Labels = append(chart.Labels, newLabels...)
755 +
756 + // Sort labels by name for consistent output
757 + sort.Slice(chart.Labels, func(i, j int) bool {
758 + return chart.Labels[i].Name < chart.Labels[j].Name
759 + })
760 + }
761 +}
762 +
763 +func attributesHash(attrs pcommon.Map) string {
764 + if attrs.Len() == 0 {
765 + return "default"
766 + }
767 +
768 + hash := fnv.New64a()
769 +
770 + // Sort keys for consistent hashing
771 + keys := make([]string, 0, attrs.Len())
772 + attrs.Range(func(k string, v pcommon.Value) bool {
773 + keys = append(keys, k)
774 + return true
775 + })
776 + sort.Strings(keys)
777 +
778 + for _, k := range keys {
779 + v, _ := attrs.Get(k)
780 + _, _ = hash.Write([]byte(k))
781 + _, _ = hash.Write([]byte(":"))
782 + _, _ = hash.Write([]byte(v.AsString()))
783 + _, _ = hash.Write([]byte(";"))
784 + }
785 +
786 + return strconv.FormatUint(hash.Sum64(), 16)
787 +}
788 +
789 +func sanitizeID(name string) string {
790 + if name == "" {
791 + return "unknown"
792 + }
793 +
794 + var sb strings.Builder
795 + sb.Grow(len(name))
796 +
797 + // Ensure the ID doesn't start with a number
798 + if unicode.IsDigit(rune(name[0])) {
799 + sb.WriteString("n_")
800 + }
801 +
802 + for _, r := range name {
803 + switch {
804 + case unicode.IsLetter(r), unicode.IsDigit(r), r == '_', r == '.', r == '-':
805 + sb.WriteRune(r)
806 + default:
807 + sb.WriteRune('_')
808 + }
809 + }
810 +
811 + return sb.String()
812 +}
813 +
814 +func getTotalCountFromBuckets(buckets pmetric.ExponentialHistogramDataPointBuckets) float64 {
815 + counts := buckets.BucketCounts()
816 + var sum uint64
817 + for i := 0; i < counts.Len(); i++ {
818 + sum += counts.At(i)
819 + }
820 + return float64(sum)
821 +}
src/go/otel-collector/exporter/netdataexporter/convert_test.go new
+657
@@ -0,0 +1,657 @@
1 +package netdataexporter
2 +
3 +import (
4 + "strings"
5 + "testing"
6 +
7 + "github.com/stretchr/testify/assert"
8 + "github.com/stretchr/testify/require"
9 + "go.opentelemetry.io/collector/pdata/pcommon"
10 + "go.opentelemetry.io/collector/pdata/pmetric"
11 +)
12 +
13 +func TestNetdataExporter_Convert(t *testing.T) {
14 + tests := map[string]struct {
15 + inputMetrics func() pmetric.Metrics
16 + expectedChartCount int
17 + expectedCharts map[string]ChartDefinition
18 + }{
19 + "gauge_metric": {
20 + inputMetrics: func() pmetric.Metrics {
21 + metrics := pmetric.NewMetrics()
22 + rm := metrics.ResourceMetrics().AppendEmpty()
23 +
24 + // Add resource attributes
25 + rm.Resource().Attributes().PutStr("service.name", "test-service")
26 +
27 + sm := rm.ScopeMetrics().AppendEmpty()
28 + metric := sm.Metrics().AppendEmpty()
29 + metric.SetName("test.gauge")
30 + metric.SetDescription("Test Gauge")
31 + metric.SetUnit("bytes")
32 +
33 + gauge := metric.SetEmptyGauge()
34 + dp := gauge.DataPoints().AppendEmpty()
35 + dp.SetDoubleValue(42.0)
36 + dp.Attributes().PutStr("host", "test-host")
37 +
38 + return metrics
39 + },
40 + expectedChartCount: 1,
41 + expectedCharts: map[string]ChartDefinition{},
42 + },
43 + "sum_metric_delta": {
44 + inputMetrics: func() pmetric.Metrics {
45 + metrics := pmetric.NewMetrics()
46 + rm := metrics.ResourceMetrics().AppendEmpty()
47 +
48 + sm := rm.ScopeMetrics().AppendEmpty()
49 + metric := sm.Metrics().AppendEmpty()
50 + metric.SetName("test.sum")
51 + metric.SetDescription("Test Sum")
52 + metric.SetUnit("count")
53 +
54 + sum := metric.SetEmptySum()
55 + sum.SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
56 + sum.SetIsMonotonic(true)
57 +
58 + dp := sum.DataPoints().AppendEmpty()
59 + dp.SetIntValue(100)
60 +
61 + return metrics
62 + },
63 + expectedChartCount: 1,
64 + expectedCharts: map[string]ChartDefinition{},
65 + },
66 + "sum_metric_cumulative": {
67 + inputMetrics: func() pmetric.Metrics {
68 + metrics := pmetric.NewMetrics()
69 + rm := metrics.ResourceMetrics().AppendEmpty()
70 +
71 + sm := rm.ScopeMetrics().AppendEmpty()
72 + metric := sm.Metrics().AppendEmpty()
73 + metric.SetName("test.sum")
74 + metric.SetDescription("Test Sum")
75 + metric.SetUnit("count")
76 +
77 + sum := metric.SetEmptySum()
78 + sum.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
79 + sum.SetIsMonotonic(true)
80 +
81 + dp := sum.DataPoints().AppendEmpty()
82 + dp.SetIntValue(100)
83 +
84 + return metrics
85 + },
86 + expectedChartCount: 1,
87 + expectedCharts: map[string]ChartDefinition{},
88 + },
89 + "histogram_metric": {
90 + inputMetrics: func() pmetric.Metrics {
91 + metrics := pmetric.NewMetrics()
92 + rm := metrics.ResourceMetrics().AppendEmpty()
93 +
94 + sm := rm.ScopeMetrics().AppendEmpty()
95 + metric := sm.Metrics().AppendEmpty()
96 + metric.SetName("test.histogram")
97 + metric.SetDescription("Test Histogram")
98 + metric.SetUnit("ms")
99 +
100 + histogram := metric.SetEmptyHistogram()
101 + histogram.SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
102 +
103 + dp := histogram.DataPoints().AppendEmpty()
104 + dp.SetCount(30)
105 + dp.SetSum(200.0)
106 + dp.BucketCounts().FromRaw([]uint64{10, 15, 5})
107 + dp.ExplicitBounds().FromRaw([]float64{10.0, 20.0})
108 +
109 + return metrics
110 + },
111 + expectedChartCount: 3, // Count, Sum, and Buckets charts
112 + expectedCharts: map[string]ChartDefinition{},
113 + },
114 + "exponential_histogram_metric": {
115 + inputMetrics: func() pmetric.Metrics {
116 + metrics := pmetric.NewMetrics()
117 + rm := metrics.ResourceMetrics().AppendEmpty()
118 +
119 + sm := rm.ScopeMetrics().AppendEmpty()
120 + metric := sm.Metrics().AppendEmpty()
121 + metric.SetName("test.exponential_histogram")
122 + metric.SetDescription("Test Exponential Histogram")
123 + metric.SetUnit("ms")
124 +
125 + expHistogram := metric.SetEmptyExponentialHistogram()
126 + expHistogram.SetAggregationTemporality(pmetric.AggregationTemporalityDelta)
127 +
128 + dp := expHistogram.DataPoints().AppendEmpty()
129 + dp.SetCount(30)
130 + dp.SetSum(200.0)
131 + dp.SetZeroCount(5)
132 +
133 + // Set up positive buckets
134 + dp.Positive().SetOffset(0)
135 + dp.Positive().BucketCounts().FromRaw([]uint64{10, 5})
136 +
137 + // Set up negative buckets
138 + dp.Negative().SetOffset(0)
139 + dp.Negative().BucketCounts().FromRaw([]uint64{7, 3})
140 +
141 + return metrics
142 + },
143 + expectedChartCount: 3, // Count, Sum, and Distribution charts
144 + expectedCharts: map[string]ChartDefinition{},
145 + },
146 + "summary_metric": {
147 + inputMetrics: func() pmetric.Metrics {
148 + metrics := pmetric.NewMetrics()
149 + rm := metrics.ResourceMetrics().AppendEmpty()
150 +
151 + sm := rm.ScopeMetrics().AppendEmpty()
152 + metric := sm.Metrics().AppendEmpty()
153 + metric.SetName("test.summary")
154 + metric.SetDescription("Test Summary")
155 + metric.SetUnit("ms")
156 +
157 + summary := metric.SetEmptySummary()
158 +
159 + dp := summary.DataPoints().AppendEmpty()
160 + dp.SetCount(100)
161 + dp.SetSum(5000.0)
162 +
163 + // Add quantiles (0%, 50%, 95%, 99%, 100%)
164 + qv := dp.QuantileValues().AppendEmpty()
165 + qv.SetQuantile(0.0)
166 + qv.SetValue(1.0)
167 +
168 + qv = dp.QuantileValues().AppendEmpty()
169 + qv.SetQuantile(0.5)
170 + qv.SetValue(5.0)
171 +
172 + qv = dp.QuantileValues().AppendEmpty()
173 + qv.SetQuantile(0.95)
174 + qv.SetValue(9.5)
175 +
176 + qv = dp.QuantileValues().AppendEmpty()
177 + qv.SetQuantile(0.99)
178 + qv.SetValue(10.0)
179 +
180 + qv = dp.QuantileValues().AppendEmpty()
181 + qv.SetQuantile(1.0)
182 + qv.SetValue(100.0)
183 +
184 + return metrics
185 + },
186 + expectedChartCount: 3, // Count, Sum, and Quantiles charts
187 + expectedCharts: map[string]ChartDefinition{},
188 + },
189 + "multiple_data_points_by_attribute": {
190 + inputMetrics: func() pmetric.Metrics {
191 + metrics := pmetric.NewMetrics()
192 + rm := metrics.ResourceMetrics().AppendEmpty()
193 +
194 + sm := rm.ScopeMetrics().AppendEmpty()
195 + metric := sm.Metrics().AppendEmpty()
196 + metric.SetName("test.gauge.multi")
197 + metric.SetDescription("Test Gauge with Multiple Points")
198 + metric.SetUnit("bytes")
199 +
200 + gauge := metric.SetEmptyGauge()
201 +
202 + // First data point with host=server1
203 + dp1 := gauge.DataPoints().AppendEmpty()
204 + dp1.SetDoubleValue(100.0)
205 + dp1.Attributes().PutStr("host", "server1")
206 +
207 + // Second data point with host=server2
208 + dp2 := gauge.DataPoints().AppendEmpty()
209 + dp2.SetDoubleValue(200.0)
210 + dp2.Attributes().PutStr("host", "server2")
211 +
212 + return metrics
213 + },
214 + expectedChartCount: 2, // One chart per unique attribute set
215 + expectedCharts: map[string]ChartDefinition{},
216 + },
217 + "staleness_marker": {
218 + inputMetrics: func() pmetric.Metrics {
219 + metrics := pmetric.NewMetrics()
220 + rm := metrics.ResourceMetrics().AppendEmpty()
221 +
222 + sm := rm.ScopeMetrics().AppendEmpty()
223 + metric := sm.Metrics().AppendEmpty()
224 + metric.SetName("test.gauge.stale")
225 + metric.SetDescription("Test Gauge with Staleness Marker")
226 +
227 + gauge := metric.SetEmptyGauge()
228 +
229 + // Normal data point
230 + dp1 := gauge.DataPoints().AppendEmpty()
231 + dp1.SetDoubleValue(100.0)
232 +
233 + // Stale data point (with staleness flag set)
234 + dp2 := gauge.DataPoints().AppendEmpty()
235 + dp2.SetDoubleValue(0.0)
236 + dp2.SetFlags(1) // Set staleness marker flag
237 +
238 + return metrics
239 + },
240 + expectedChartCount: 1,
241 + expectedCharts: map[string]ChartDefinition{},
242 + },
243 + "resource_and_datapoint_attributes_as_labels": {
244 + inputMetrics: func() pmetric.Metrics {
245 + metrics := pmetric.NewMetrics()
246 + rm := metrics.ResourceMetrics().AppendEmpty()
247 +
248 + // Add resource attributes
249 + rm.Resource().Attributes().PutStr("service.name", "test-service")
250 + rm.Resource().Attributes().PutStr("deployment.environment", "production")
251 +
252 + sm := rm.ScopeMetrics().AppendEmpty()
253 + metric := sm.Metrics().AppendEmpty()
254 + metric.SetName("test.labels")
255 +
256 + gauge := metric.SetEmptyGauge()
257 + dp := gauge.DataPoints().AppendEmpty()
258 + dp.SetDoubleValue(42.0)
259 +
260 + // Add data point attributes
261 + dp.Attributes().PutStr("host", "host1")
262 + dp.Attributes().PutStr("region", "us-west")
263 +
264 + return metrics
265 + },
266 + expectedChartCount: 1,
267 + expectedCharts: map[string]ChartDefinition{},
268 + },
269 + }
270 +
271 + for name, tt := range tests {
272 + t.Run(name, func(t *testing.T) {
273 + // Create a new exporter for each test to avoid state between tests
274 + exporter := &netdataExporter{
275 + charts: make(map[string]*ChartDefinition),
276 + }
277 +
278 + // Convert the metrics
279 + inputMetrics := tt.inputMetrics()
280 + charts := exporter.Convert(inputMetrics)
281 +
282 + // Verify the chart count
283 + assert.Equal(t, tt.expectedChartCount, len(charts), "Wrong number of charts created")
284 +
285 + switch name {
286 + case "gauge_metric":
287 + // Find the chart for the gauge metric
288 + var gaugeChart *ChartDefinition
289 + for _, chart := range charts {
290 + if chart.Context == "test.gauge" {
291 + gaugeChart = chart
292 + break
293 + }
294 + }
295 + require.NotNil(t, gaugeChart, "Gauge chart should exist")
296 + assert.Equal(t, "Test Gauge", gaugeChart.Title)
297 + assert.Equal(t, "bytes", gaugeChart.Units)
298 + assert.Equal(t, "test-service_test", gaugeChart.Family)
299 + assert.Equal(t, "line", gaugeChart.Type)
300 + assert.Len(t, gaugeChart.Dimensions, 1)
301 + assert.Equal(t, 42.0, gaugeChart.Dimensions[0].Value)
302 + assert.Equal(t, "absolute", gaugeChart.Dimensions[0].Algo)
303 +
304 + case "sum_metric_delta":
305 + // Find the chart for the delta sum metric
306 + var sumChart *ChartDefinition
307 + for _, chart := range charts {
308 + if chart.Context == "test.sum" {
309 + sumChart = chart
310 + break
311 + }
312 + }
313 + require.NotNil(t, sumChart, "Sum chart should exist")
314 + assert.Equal(t, "Test Sum", sumChart.Title)
315 + assert.Equal(t, "count", sumChart.Units)
316 + assert.Equal(t, "test", sumChart.Family)
317 + assert.Equal(t, "line", sumChart.Type)
318 + assert.Len(t, sumChart.Dimensions, 1)
319 + assert.Equal(t, 100.0, sumChart.Dimensions[0].Value)
320 + assert.Equal(t, "incremental", sumChart.Dimensions[0].Algo, "Delta temporality should use incremental algorithm")
321 +
322 + case "sum_metric_cumulative":
323 + // Find the chart for the cumulative sum metric
324 + var sumChart *ChartDefinition
325 + for _, chart := range charts {
326 + if chart.Context == "test.sum" {
327 + sumChart = chart
328 + break
329 + }
330 + }
331 + require.NotNil(t, sumChart, "Sum chart should exist")
332 + assert.Equal(t, "Test Sum", sumChart.Title)
333 + assert.Equal(t, "count", sumChart.Units)
334 + assert.Equal(t, "test", sumChart.Family)
335 + assert.Equal(t, "line", sumChart.Type)
336 + assert.Len(t, sumChart.Dimensions, 1)
337 + assert.Equal(t, 100.0, sumChart.Dimensions[0].Value)
338 + assert.Equal(t, "absolute", sumChart.Dimensions[0].Algo, "Cumulative temporality should use absolute algorithm")
339 +
340 + case "histogram_metric":
341 + // Check count chart
342 + var countChart *ChartDefinition
343 + for _, chart := range charts {
344 + if chart.Context == "test.histogram.count" {
345 + countChart = chart
346 + break
347 + }
348 + }
349 + require.NotNil(t, countChart, "Histogram count chart should exist")
350 + assert.Equal(t, "Test Histogram (Count)", countChart.Title)
351 + assert.Equal(t, "count", countChart.Units)
352 + assert.Equal(t, "line", countChart.Type)
353 + assert.Len(t, countChart.Dimensions, 1)
354 + assert.Equal(t, 30.0, countChart.Dimensions[0].Value)
355 + assert.Equal(t, "absolute", countChart.Dimensions[0].Algo)
356 +
357 + // Check sum chart
358 + var sumChart *ChartDefinition
359 + for _, chart := range charts {
360 + if chart.Context == "test.histogram.sum" {
361 + sumChart = chart
362 + break
363 + }
364 + }
365 + require.NotNil(t, sumChart, "Histogram sum chart should exist")
366 + assert.Equal(t, "Test Histogram (Sum)", sumChart.Title)
367 + assert.Equal(t, "ms", sumChart.Units)
368 + assert.Equal(t, "line", sumChart.Type)
369 + assert.Len(t, sumChart.Dimensions, 1)
370 + assert.Equal(t, 200.0, sumChart.Dimensions[0].Value)
371 +
372 + // Check buckets chart
373 + var bucketsChart *ChartDefinition
374 + for _, chart := range charts {
375 + if chart.Context == "test.histogram.buckets" {
376 + bucketsChart = chart
377 + break
378 + }
379 + }
380 + require.NotNil(t, bucketsChart, "Histogram buckets chart should exist")
381 + assert.Equal(t, "Test Histogram (Buckets)", bucketsChart.Title)
382 + assert.Equal(t, "count", bucketsChart.Units)
383 + assert.Equal(t, "stacked", bucketsChart.Type)
384 + assert.Len(t, bucketsChart.Dimensions, 3)
385 +
386 + // Verify bucket values (we don't know exact order, so check all values are present)
387 + bucketValues := []float64{10.0, 15.0, 5.0}
388 + for _, dim := range bucketsChart.Dimensions {
389 + assert.Contains(t, bucketValues, dim.Value)
390 + }
391 +
392 + case "exponential_histogram_metric":
393 + // Check count chart
394 + var countChart *ChartDefinition
395 + for _, chart := range charts {
396 + if chart.Context == "test.exponential_histogram.count" {
397 + countChart = chart
398 + break
399 + }
400 + }
401 + require.NotNil(t, countChart, "Exponential histogram count chart should exist")
402 + assert.Equal(t, "Test Exponential Histogram (Count)", countChart.Title)
403 + assert.Equal(t, "count", countChart.Units)
404 + assert.Equal(t, "line", countChart.Type)
405 + assert.Len(t, countChart.Dimensions, 1)
406 + assert.Equal(t, 30.0, countChart.Dimensions[0].Value)
407 + assert.Equal(t, "incremental", countChart.Dimensions[0].Algo, "Delta temporality should use incremental algorithm")
408 +
409 + // Check sum chart
410 + var sumChart *ChartDefinition
411 + for _, chart := range charts {
412 + if chart.Context == "test.exponential_histogram.sum" {
413 + sumChart = chart
414 + break
415 + }
416 + }
417 + require.NotNil(t, sumChart, "Exponential histogram sum chart should exist")
418 + assert.Equal(t, "Test Exponential Histogram (Sum)", sumChart.Title)
419 + assert.Equal(t, "ms", sumChart.Units)
420 + assert.Equal(t, "line", sumChart.Type)
421 + assert.Len(t, sumChart.Dimensions, 1)
422 + assert.Equal(t, 200.0, sumChart.Dimensions[0].Value)
423 +
424 + // Check distribution chart
425 + var distChart *ChartDefinition
426 + for _, chart := range charts {
427 + if chart.Context == "test.exponential_histogram.distribution" {
428 + distChart = chart
429 + break
430 + }
431 + }
432 + require.NotNil(t, distChart, "Exponential histogram distribution chart should exist")
433 + assert.Equal(t, "Test Exponential Histogram (Distribution)", distChart.Title)
434 + assert.Equal(t, "count", distChart.Units)
435 + assert.Equal(t, "stacked", distChart.Type)
436 + assert.Len(t, distChart.Dimensions, 3)
437 +
438 + // Find the dimensions for positive, zero, and negative values
439 + positiveValue := 0.0
440 + zeroValue := 0.0
441 + negativeValue := 0.0
442 +
443 + for _, dim := range distChart.Dimensions {
444 + if dim.Name == "Positive Values" {
445 + positiveValue = dim.Value
446 + } else if dim.Name == "Zero Values" {
447 + zeroValue = dim.Value
448 + } else if dim.Name == "Negative Values" {
449 + negativeValue = dim.Value
450 + }
451 + }
452 +
453 + assert.Equal(t, 15.0, positiveValue, "Positive bucket count should sum to 15")
454 + assert.Equal(t, 5.0, zeroValue, "Zero bucket count should be 5")
455 + assert.Equal(t, 10.0, negativeValue, "Negative bucket count should sum to 10")
456 +
457 + case "summary_metric":
458 + // Check count chart
459 + var countChart *ChartDefinition
460 + for _, chart := range charts {
461 + if chart.Context == "test.summary.count" {
462 + countChart = chart
463 + break
464 + }
465 + }
466 + require.NotNil(t, countChart, "Summary count chart should exist")
467 + assert.Equal(t, "Test Summary (Count)", countChart.Title)
468 + assert.Equal(t, "count", countChart.Units)
469 + assert.Equal(t, "line", countChart.Type)
470 + assert.Len(t, countChart.Dimensions, 1)
471 + assert.Equal(t, 100.0, countChart.Dimensions[0].Value)
472 +
473 + // Check sum chart
474 + var sumChart *ChartDefinition
475 + for _, chart := range charts {
476 + if chart.Context == "test.summary.sum" {
477 + sumChart = chart
478 + break
479 + }
480 + }
481 + require.NotNil(t, sumChart, "Summary sum chart should exist")
482 + assert.Equal(t, "Test Summary (Sum)", sumChart.Title)
483 + assert.Equal(t, "ms", sumChart.Units)
484 + assert.Equal(t, "line", sumChart.Type)
485 + assert.Len(t, sumChart.Dimensions, 1)
486 + assert.Equal(t, 5000.0, sumChart.Dimensions[0].Value)
487 +
488 + // Check quantiles chart
489 + var quantilesChart *ChartDefinition
490 + for _, chart := range charts {
491 + if chart.Context == "test.summary.quantiles" {
492 + quantilesChart = chart
493 + break
494 + }
495 + }
496 + require.NotNil(t, quantilesChart, "Summary quantiles chart should exist")
497 + assert.Equal(t, "Test Summary (Quantiles)", quantilesChart.Title)
498 + assert.Equal(t, "ms", quantilesChart.Units)
499 + assert.Equal(t, "line", quantilesChart.Type)
500 + assert.Len(t, quantilesChart.Dimensions, 5)
501 +
502 + // Find dimensions for min, max, and p50
503 + var minValue, maxValue, p50Value float64
504 + for _, dim := range quantilesChart.Dimensions {
505 + if dim.Name == "min" {
506 + minValue = dim.Value
507 + } else if dim.Name == "max" {
508 + maxValue = dim.Value
509 + } else if dim.Name == "p50" {
510 + p50Value = dim.Value
511 + }
512 + }
513 +
514 + assert.Equal(t, 1.0, minValue, "Minimum (p0) quantile should be 1.0")
515 + assert.Equal(t, 100.0, maxValue, "Maximum (p100) quantile should be 100.0")
516 + assert.Equal(t, 5.0, p50Value, "p50 quantile should be 5.0")
517 +
518 + case "multiple_data_points_by_attribute":
519 + assert.Len(t, charts, 2, "Should have two charts for the multiple data points test")
520 +
521 + // Collect all dimension values to ensure we have both 100.0 and 200.0
522 + values := make([]float64, 0, 2)
523 + for _, chart := range charts {
524 + assert.Equal(t, "test.gauge.multi", chart.Context, "Chart context should match metric name")
525 + assert.Equal(t, "Test Gauge with Multiple Points", chart.Title)
526 + assert.Equal(t, "bytes", chart.Units)
527 +
528 + for _, dim := range chart.Dimensions {
529 + values = append(values, dim.Value)
530 + }
531 + }
532 +
533 + assert.Contains(t, values, 100.0, "Should have a dimension with value 100.0")
534 + assert.Contains(t, values, 200.0, "Should have a dimension with value 200.0")
535 +
536 + case "staleness_marker":
537 + assert.Len(t, charts, 1, "Should have one chart for the staleness marker test")
538 +
539 + for _, chart := range charts {
540 + assert.Equal(t, "test.gauge.stale", chart.Context)
541 + assert.Equal(t, "Test Gauge with Staleness Marker", chart.Title)
542 + assert.Len(t, chart.Dimensions, 1, "Should have one dimension (non-stale point)")
543 + assert.Equal(t, 100.0, chart.Dimensions[0].Value, "Dimension value should be 100.0 (from non-stale point)")
544 + }
545 +
546 + case "resource_and_datapoint_attributes_as_labels":
547 + assert.Len(t, charts, 1, "Should have one chart for the labels test")
548 +
549 + for _, chart := range charts {
550 + assert.Equal(t, "test.labels", chart.Context)
551 + assert.Equal(t, "test-service_test", chart.Family, "Family should include service name")
552 +
553 + // Check for expected labels
554 + labelMap := make(map[string]string)
555 + for _, label := range chart.Labels {
556 + labelMap[label.Name] = label.Value
557 + }
558 +
559 + assert.Equal(t, "production", labelMap["deployment.environment"], "Should have deployment.environment label")
560 + assert.Equal(t, "test-service", labelMap["service.name"], "Should have service.name label")
561 + assert.Equal(t, "host1", labelMap["host"], "Should have host label")
562 + assert.Equal(t, "us-west", labelMap["region"], "Should have region label")
563 +
564 + // Check dimension
565 + assert.Len(t, chart.Dimensions, 1)
566 + assert.Equal(t, 42.0, chart.Dimensions[0].Value)
567 + }
568 + }
569 + })
570 + }
571 +}
572 +
573 +// Helper functions tests
574 +
575 +func TestHelperFunctions(t *testing.T) {
576 + // Test sanitizeID
577 + t.Run("sanitizeID", func(t *testing.T) {
578 + tests := map[string]struct {
579 + input string
580 + expected string
581 + }{
582 + "valid_id": {"valid_id", "valid_id"},
583 + "spaces": {"test metric", "test_metric"},
584 + "special_chars": {"test/metric:value", "test_metric_value"},
585 + "starts_with_number": {"123test", "n_123test"},
586 + "multiple_special_chars": {"test@metric#value", "test_metric_value"},
587 + "empty_string": {"", "unknown"},
588 + "very_long_id": {strings.Repeat("a", 150), strings.Repeat("a", 150)},
589 + }
590 +
591 + for name, tt := range tests {
592 + t.Run(name, func(t *testing.T) {
593 + result := sanitizeID(tt.input)
594 + assert.Equal(t, tt.expected, result)
595 + })
596 + }
597 + })
598 +
599 + // Test generateChartID
600 + t.Run("generateChartID", func(t *testing.T) {
601 + // Test that long metric names are truncated in the chart ID
602 + veryLongName := strings.Repeat("abcdefghij", 15) // 150 characters
603 + attrHash := "testhash"
604 + chartID := generateChartID(veryLongName, attrHash)
605 +
606 + // Chart ID should be truncated to maxChartIDLength
607 + assert.LessOrEqual(t, len(chartID), maxChartIDLength, "Chart ID should not exceed maxChartIDLength")
608 +
609 + // Chart ID should still end with the attribute hash
610 + assert.True(t, strings.HasSuffix(chartID, attrHash), "Chart ID should end with the attribute hash")
611 + })
612 +
613 + // Test attributesHash
614 + t.Run("attributesHash", func(t *testing.T) {
615 + // Test consistent hashing with different attribute orders
616 + attrs1 := pcommon.NewMap()
617 + attrs1.PutStr("a", "1")
618 + attrs1.PutStr("b", "2")
619 +
620 + attrs2 := pcommon.NewMap()
621 + attrs2.PutStr("b", "2")
622 + attrs2.PutStr("a", "1")
623 +
624 + hash1 := attributesHash(attrs1)
625 + hash2 := attributesHash(attrs2)
626 +
627 + assert.Equal(t, hash1, hash2, "Hash should be consistent regardless of attribute order")
628 +
629 + // Test empty attributes
630 + emptyAttrs := pcommon.NewMap()
631 + emptyHash := attributesHash(emptyAttrs)
632 + assert.Equal(t, "default", emptyHash, "Empty attributes should hash to 'default'")
633 + })
634 +
635 + // Test getFamily
636 + t.Run("getFamily", func(t *testing.T) {
637 + // Test with service name
638 + resourceAttrs := pcommon.NewMap()
639 + resourceAttrs.PutStr("service.name", "test-service")
640 +
641 + family := getFamily("test.metric", resourceAttrs)
642 + assert.Equal(t, "test-service_test", family, "Family should include service name")
643 +
644 + // Test without service name but with service namespace
645 + resourceAttrs = pcommon.NewMap()
646 + resourceAttrs.PutStr("service.namespace", "test-namespace")
647 +
648 + family = getFamily("test.metric", resourceAttrs)
649 + assert.Equal(t, "test_namespace_test", family, "Family should include service namespace")
650 +
651 + // Test without service name or namespace
652 + resourceAttrs = pcommon.NewMap()
653 +
654 + family = getFamily("test.metric", resourceAttrs)
655 + assert.Equal(t, "test", family, "Family should be based on metric name prefix")
656 + })
657 +}
src/go/otel-collector/exporter/netdataexporter/exporter.go
+75
@@ -11,16 +11,34 @@ import (
11 type netdataExporter struct {
12 log *zap.Logger
13 conf *Config
14 +
15 + api *netdataAPI
16 +
17 + //MaxLabelCount int // Maximum number of labels to include per chart (0 = unlimited)
18 + //UseShortIDs bool // Use shortened IDs for better performance
19 + //GroupSimilarPoints bool // Group similar data points into a single dimension
20 + //
21 + //// State management
22 + //charts map[string]*ChartDefinition // Map to store and track charts across iterations
23 + //chartsMu sync.RWMutex // Mutex to protect the charts map during concurrent access
24 + //lastUpdate time.Time // Track the last update time
25 +
26 + charts map[string]*ChartDefinition
27 }
28
29 func newNetdataExporter(cfg component.Config, logger *zap.Logger) *netdataExporter {
30 return &netdataExporter{
31 log: logger,
32 conf: cfg.(*Config),
33 + api: newNetdataStdoutApi(),
34 }
35 }
36
37 func (e *netdataExporter) consumeMetrics(ctx context.Context, pm pmetric.Metrics) error {
38 + e.convert(pm)
39 + e.sendCharts()
40 + e.updateCharts(0)
41 +
42 return nil
43 }
44
@@ -31,3 +49,60 @@ func (e *netdataExporter) Start(_ context.Context, _ component.Host) error {
49 func (e *netdataExporter) Shutdown(context.Context) error {
50 return nil
51 }
52 +
53 +func (e *netdataExporter) sendCharts() {
54 + for _, chart := range e.charts {
55 + if chart.IsNew {
56 + opts := ChartOpts{
57 + TypeID: "",
58 + ID: chart.ID,
59 + Title: chart.Title,
60 + Units: chart.Units,
61 + Family: chart.Family,
62 + Context: chart.Context,
63 + ChartType: chart.Type,
64 + Priority: 1000,
65 + UpdateEvery: 1,
66 + Options: chart.Options,
67 + Plugin: "otel",
68 + Module: "metrics",
69 + }
70 +
71 + e.api.chart(opts)
72 + for _, label := range chart.Labels {
73 + e.api.clabel(label.Name, label.Value)
74 + }
75 + e.api.clabelcommit()
76 +
77 + for _, dim := range chart.Dimensions {
78 + dimOpts := DimensionOpts{
79 + ID: dim.ID,
80 + Name: dim.Name,
81 + Algorithm: dim.Algo,
82 + Multiplier: 1,
83 + Divisor: 1,
84 + Options: "",
85 + }
86 + e.api.dimension(dimOpts)
87 + }
88 +
89 + chart.IsNew = false
90 + }
91 + }
92 +}
93 +
94 +func (e *netdataExporter) updateCharts(msSince int) {
95 + for id, chart := range e.charts {
96 + if len(chart.Dimensions) == 0 {
97 + continue
98 + }
99 +
100 + e.api.begin(id, msSince)
101 +
102 + for _, dim := range chart.Dimensions {
103 + value := int64(dim.Value)
104 + e.api.set(dim.ID, value)
105 + }
106 + e.api.end()
107 + }
108 +}
src/go/otel-collector/exporter/netdataexporter/go.mod
+29 -28
@@ -4,14 +4,14 @@ go 1.24.0
4
5 require (
6 github.com/stretchr/testify v1.10.0
7 - go.opentelemetry.io/collector/component v1.30.0
8 - go.opentelemetry.io/collector/component/componenttest v0.124.0
9 - go.opentelemetry.io/collector/confmap v1.30.0
10 - go.opentelemetry.io/collector/consumer v1.30.0
11 - go.opentelemetry.io/collector/exporter v0.124.0
12 - go.opentelemetry.io/collector/exporter/exportertest v0.124.0
13 - go.opentelemetry.io/collector/exporter/xexporter v0.124.0
14 - go.opentelemetry.io/collector/pdata v1.30.0
7 + go.opentelemetry.io/collector/component v1.31.0
8 + go.opentelemetry.io/collector/component/componenttest v0.125.0
9 + go.opentelemetry.io/collector/confmap v1.31.0
10 + go.opentelemetry.io/collector/consumer v1.31.0
11 + go.opentelemetry.io/collector/exporter v0.125.0
12 + go.opentelemetry.io/collector/exporter/exportertest v0.125.0
13 + go.opentelemetry.io/collector/exporter/xexporter v0.125.0
14 + go.opentelemetry.io/collector/pdata v1.31.0
15 go.uber.org/goleak v1.3.0
16 go.uber.org/zap v1.27.0
17 )
@@ -27,27 +27,27 @@ require (
27 github.com/hashicorp/go-version v1.7.0 // indirect
28 github.com/json-iterator/go v1.1.12 // indirect
29 github.com/knadh/koanf/maps v0.1.2 // indirect
30 - github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
31 - github.com/knadh/koanf/v2 v2.1.2 // indirect
30 + github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
31 + github.com/knadh/koanf/v2 v2.2.0 // indirect
32 github.com/mitchellh/copystructure v1.2.0 // indirect
33 github.com/mitchellh/reflectwalk v1.0.2 // indirect
34 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
35 github.com/modern-go/reflect2 v1.0.2 // indirect
36 github.com/pmezard/go-difflib v1.0.0 // indirect
37 go.opentelemetry.io/auto/sdk v1.1.0 // indirect
38 - go.opentelemetry.io/collector/config/configretry v1.30.0 // indirect
39 - go.opentelemetry.io/collector/consumer/consumererror v0.124.0 // indirect
40 - go.opentelemetry.io/collector/consumer/consumertest v0.124.0 // indirect
41 - go.opentelemetry.io/collector/consumer/xconsumer v0.124.0 // indirect
42 - go.opentelemetry.io/collector/extension v1.30.0 // indirect
43 - go.opentelemetry.io/collector/extension/xextension v0.124.0 // indirect
44 - go.opentelemetry.io/collector/featuregate v1.30.0 // indirect
45 - go.opentelemetry.io/collector/internal/telemetry v0.124.0 // indirect
46 - go.opentelemetry.io/collector/pdata/pprofile v0.124.0 // indirect
47 - go.opentelemetry.io/collector/pipeline v0.124.0 // indirect
48 - go.opentelemetry.io/collector/receiver v1.30.0 // indirect
49 - go.opentelemetry.io/collector/receiver/receivertest v0.124.0 // indirect
50 - go.opentelemetry.io/collector/receiver/xreceiver v0.124.0 // indirect
38 + go.opentelemetry.io/collector/config/configretry v1.31.0 // indirect
39 + go.opentelemetry.io/collector/consumer/consumererror v0.125.0 // indirect
40 + go.opentelemetry.io/collector/consumer/consumertest v0.125.0 // indirect
41 + go.opentelemetry.io/collector/consumer/xconsumer v0.125.0 // indirect
42 + go.opentelemetry.io/collector/extension v1.31.0 // indirect
43 + go.opentelemetry.io/collector/extension/xextension v0.125.0 // indirect
44 + go.opentelemetry.io/collector/featuregate v1.31.0 // indirect
45 + go.opentelemetry.io/collector/internal/telemetry v0.125.0 // indirect
46 + go.opentelemetry.io/collector/pdata/pprofile v0.125.0 // indirect
47 + go.opentelemetry.io/collector/pipeline v0.125.0 // indirect
48 + go.opentelemetry.io/collector/receiver v1.31.0 // indirect
49 + go.opentelemetry.io/collector/receiver/receivertest v0.125.0 // indirect
50 + go.opentelemetry.io/collector/receiver/xreceiver v0.125.0 // indirect
51 go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 // indirect
52 go.opentelemetry.io/otel v1.35.0 // indirect
53 go.opentelemetry.io/otel/log v0.11.0 // indirect
@@ -56,11 +56,12 @@ require (
56 go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
57 go.opentelemetry.io/otel/trace v1.35.0 // indirect
58 go.uber.org/multierr v1.11.0 // indirect
59 - golang.org/x/net v0.37.0 // indirect
60 - golang.org/x/sys v0.31.0 // indirect
61 - golang.org/x/text v0.23.0 // indirect
62 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
63 - google.golang.org/grpc v1.71.1 // indirect
59 + golang.org/x/net v0.39.0 // indirect
60 + golang.org/x/sys v0.32.0 // indirect
61 + golang.org/x/text v0.24.0 // indirect
62 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 // indirect
63 + google.golang.org/grpc v1.72.0 // indirect
64 google.golang.org/protobuf v1.36.6 // indirect
65 gopkg.in/yaml.v3 v3.0.1 // indirect
66 + sigs.k8s.io/yaml v1.4.0 // indirect
67 )
src/go/otel-collector/exporter/netdataexporter/go.sum
+65 -60
@@ -14,6 +14,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
14 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
15 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
16 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
17 +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
18 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
19 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
20 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -27,10 +28,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
28 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
29 github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
30 github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
30 -github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU=
31 -github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU=
32 -github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
33 -github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
31 +github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
32 +github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
33 +github.com/knadh/koanf/v2 v2.2.0 h1:FZFwd9bUjpb8DyCWARUBy5ovuhDs1lI87dOEn2K8UVU=
34 +github.com/knadh/koanf/v2 v2.2.0/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
35 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
36 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
37 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -56,52 +57,54 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
57 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
58 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
59 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
59 -go.opentelemetry.io/collector/component v1.30.0 h1:HXjqBHaQ47/EEuWdnkjr4Y3kRWvmyWIDvqa1Q262Fls=
60 -go.opentelemetry.io/collector/component v1.30.0/go.mod h1:vfM9kN+BM6oHBXWibquiprz8CVawxd4/aYy3nbhme3E=
61 -go.opentelemetry.io/collector/component/componenttest v0.124.0 h1:Wsc+DmDrWTFs/aEyjDA3slNwV+h/0NOyIR5Aywvr6Zw=
62 -go.opentelemetry.io/collector/component/componenttest v0.124.0/go.mod h1:NQ4ATOzMFc7QA06B993tq8o27DR0cu/JR/zK7slGJ3E=
63 -go.opentelemetry.io/collector/config/configretry v1.30.0 h1:sapni1tymwNiuI0PjqlRR5CvYxIQYT8tyjQGVJDkVPM=
64 -go.opentelemetry.io/collector/config/configretry v1.30.0/go.mod h1:QNnb+MCk7aS1k2EuGJMtlNCltzD7b8uC7Xel0Dxm1wQ=
65 -go.opentelemetry.io/collector/confmap v1.30.0 h1:Y0MXhjQCdMyJN9xZMWWdNPWs6ncMVf7YVnyAEN2dAcM=
66 -go.opentelemetry.io/collector/confmap v1.30.0/go.mod h1:9DdThVDIC3VsdtTb7DgT+HwusWOocoqDkd/TErEtQgA=
67 -go.opentelemetry.io/collector/consumer v1.30.0 h1:Nn6kFTH+EJbv13E0W+sNvWrTgbiFCRv8f6DaA2F1DQs=
68 -go.opentelemetry.io/collector/consumer v1.30.0/go.mod h1:edRyfk61ugdhCQ93PBLRZfYMVWjdMPpKP8z5QLyESf0=
69 -go.opentelemetry.io/collector/consumer/consumererror v0.124.0 h1:OmeJex0C8jcwyILG+eJIGDe6rGaR15fip+Rj3XyMTRY=
70 -go.opentelemetry.io/collector/consumer/consumererror v0.124.0/go.mod h1:A6WL/E70dUh9oFdQsL+aVYGL+d6UqiAhi7rIW/6gvDM=
71 -go.opentelemetry.io/collector/consumer/consumertest v0.124.0 h1:2arChG4RPrHW3lfVWlK/KDF7Y7qkUm/YAiBXh8oTue0=
72 -go.opentelemetry.io/collector/consumer/consumertest v0.124.0/go.mod h1:Hlu+EXbINHxVAyIT1baKO2d0j5odR3fLlLAiaP+JqQg=
73 -go.opentelemetry.io/collector/consumer/xconsumer v0.124.0 h1:/cut96EWVNoz6lIeGI9+EzS6UClMtnZkx5YIpkD0Xe0=
74 -go.opentelemetry.io/collector/consumer/xconsumer v0.124.0/go.mod h1:fHH/MpzFCRNk/4foiYE6BoXQCAMf5sJTO35uvzVrrd4=
75 -go.opentelemetry.io/collector/exporter v0.124.0 h1:ii+9tU/iSrPl4+YDvqFVflksA9hUYEzwMIpmvP4JZ8w=
76 -go.opentelemetry.io/collector/exporter v0.124.0/go.mod h1:Q8tOEwFu3CN8VGjE4H2yZcCRG9Q60foQIyZGKPD/jig=
77 -go.opentelemetry.io/collector/exporter/exportertest v0.124.0 h1:IOxA/4CiVWGPlmA0JofK6W4DzvwW1YJes09r6osluIE=
78 -go.opentelemetry.io/collector/exporter/exportertest v0.124.0/go.mod h1:2EmU8IwVJV79MmFBFFW1LCN0Ob2UZsEkX/mSUB06lbI=
79 -go.opentelemetry.io/collector/exporter/xexporter v0.124.0 h1:Itfn2+F4ki8hObOtPCecWBwGpuxakUYSsTwwkB5iUns=
80 -go.opentelemetry.io/collector/exporter/xexporter v0.124.0/go.mod h1:dNK/PPY02gA9BawIKHyVk8kIFdYvqVZ2A+LlMZucIPY=
81 -go.opentelemetry.io/collector/extension v1.30.0 h1:AJqntAp1p40Q1az2Vze3OHiMURq56KWnUxaLzs1ghaA=
82 -go.opentelemetry.io/collector/extension v1.30.0/go.mod h1:a21WpypFQp9x0Go7yMOknYmIKvdIoWGzjz+h1WMjzLk=
83 -go.opentelemetry.io/collector/extension/extensiontest v0.124.0 h1:pWfKxEqvq5vVdQy+UIerw5j7ZrXezxsfQfuUe8g/xVo=
84 -go.opentelemetry.io/collector/extension/extensiontest v0.124.0/go.mod h1:DLVRyW7tJt8TtYq0Wr5BUsM494YqDiIjN8YCmbVKqjs=
85 -go.opentelemetry.io/collector/extension/xextension v0.124.0 h1:Yzf11HXaiMHfS50Zy/CYKfJjoi+/w/tgRZdDQ2VIdW0=
86 -go.opentelemetry.io/collector/extension/xextension v0.124.0/go.mod h1:GeM0aSgwVSba3Bvvspuy1E+1aa/Q1CDxoK+e/xcJFVg=
87 -go.opentelemetry.io/collector/featuregate v1.30.0 h1:mx7+iP/FQnY7KO8qw/xE3Qd1MQkWcU8VgcqLNrJ8EU8=
88 -go.opentelemetry.io/collector/featuregate v1.30.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc=
89 -go.opentelemetry.io/collector/internal/telemetry v0.124.0 h1:kzd1/ZYhLj4bt2pDB529mL4rIRrRacemXodFNxfhdWk=
90 -go.opentelemetry.io/collector/internal/telemetry v0.124.0/go.mod h1:ZjXjqV0dJ+6D4XGhTOxg/WHjnhdmXsmwmUSgALea66Y=
91 -go.opentelemetry.io/collector/pdata v1.30.0 h1:j3jyq9um436r6WzWySzexP2nLnFdmL5uVBYAlyr9nDM=
92 -go.opentelemetry.io/collector/pdata v1.30.0/go.mod h1:0Bxu1ktuj4wE7PIASNSvd0SdBscQ1PLtYasymJ13/Cs=
93 -go.opentelemetry.io/collector/pdata/pprofile v0.124.0 h1:ZjL9wKqzP4BHj0/F1jfGxs1Va8B7xmYayipZeNVoWJE=
94 -go.opentelemetry.io/collector/pdata/pprofile v0.124.0/go.mod h1:1EN3Gw5LSI4fSVma/Yfv/6nqeuYgRTm1/kmG5nE5Oyo=
95 -go.opentelemetry.io/collector/pdata/testdata v0.124.0 h1:vY+pWG7CQfzzGSB5+zGYHQOltRQr59Ek9QiPe+rI+NY=
96 -go.opentelemetry.io/collector/pdata/testdata v0.124.0/go.mod h1:lNH48lGhGv4CYk27fJecpsR1zYHmZjKgNrAprwjym0o=
97 -go.opentelemetry.io/collector/pipeline v0.124.0 h1:hKvhDyH2GPnNO8LGL34ugf36sY7EOXPjBvlrvBhsOdw=
98 -go.opentelemetry.io/collector/pipeline v0.124.0/go.mod h1:TO02zju/K6E+oFIOdi372Wk0MXd+Szy72zcTsFQwXl4=
99 -go.opentelemetry.io/collector/receiver v1.30.0 h1:XbgU4yT3Ld+hL9+jHcD/Kctcr3gXjpiFxKO+50pSayg=
100 -go.opentelemetry.io/collector/receiver v1.30.0/go.mod h1:U3cApz9PHiRMgN0WkZaz4o8mvj1+cVQYsyj2Nl1v3FQ=
101 -go.opentelemetry.io/collector/receiver/receivertest v0.124.0 h1:mx0290aXAo+wfjm4NgbKUodjT5SbS306zmk+AeqeVxE=
102 -go.opentelemetry.io/collector/receiver/receivertest v0.124.0/go.mod h1:3RpopRmIzx5T4zTStHJC0HHfd8YFWm8e9bia1HiuDtY=
103 -go.opentelemetry.io/collector/receiver/xreceiver v0.124.0 h1:YigTUKk8p/aIfqaT0ST7teT9KbLThWD5n2km83byftw=
104 -go.opentelemetry.io/collector/receiver/xreceiver v0.124.0/go.mod h1:NkTpmpAEDT17Dko4gpHUnRztrSkdSd6B0+Y4gfuCWIA=
60 +go.opentelemetry.io/collector/client v1.31.0 h1:PdmUJSx8FgFcrqm12pMwvdVp98aYSdaKjMqJandFIgE=
61 +go.opentelemetry.io/collector/client v1.31.0/go.mod h1:pSyJ1+XhsLP6nqJDP7uj3AFTw26z9mqCnRGyMw0Im8o=
62 +go.opentelemetry.io/collector/component v1.31.0 h1:9LzU8X1RhV3h8/QsAoTX23aFUfoJ3EUc9O/vK+hFpSI=
63 +go.opentelemetry.io/collector/component v1.31.0/go.mod h1:JbZl/KywXJxpUXPbt96qlEXJSym1zQ2hauMxYMuvlxM=
64 +go.opentelemetry.io/collector/component/componenttest v0.125.0 h1:E2mpnMQbkMpYoZ3Q8pHx4kod7kedjwRs1xqDpzCe/84=
65 +go.opentelemetry.io/collector/component/componenttest v0.125.0/go.mod h1:pQtsE1u/SPZdTphP5BZP64XbjXSq6wc+mDut5Ws/JDI=
66 +go.opentelemetry.io/collector/config/configretry v1.31.0 h1:GWl/UM7+xNCmXBz5lvaMxBIQTcNn1EcCvMjVvUwgOLg=
67 +go.opentelemetry.io/collector/config/configretry v1.31.0/go.mod h1:QNnb+MCk7aS1k2EuGJMtlNCltzD7b8uC7Xel0Dxm1wQ=
68 +go.opentelemetry.io/collector/confmap v1.31.0 h1:+AW5VJc1rCtgEyGd+1J5uSNw/kVZ98+lKO/pqXEwVvU=
69 +go.opentelemetry.io/collector/confmap v1.31.0/go.mod h1:TdutQlIoHDPXcZ2xZ0QWGRkSFC8oTKO61zTx569dvrY=
70 +go.opentelemetry.io/collector/consumer v1.31.0 h1:L+y66ywxLHnAxnUxv0JDwUf5bFj53kMxCCyEfRKlM7s=
71 +go.opentelemetry.io/collector/consumer v1.31.0/go.mod h1:rPsqy5ni+c6xNMUkOChleZYO/nInVY6eaBNZ1FmWJVk=
72 +go.opentelemetry.io/collector/consumer/consumererror v0.125.0 h1:Qq9SgbxlJoRn0952dj4lPJhcuBiqKzD1aNxCfa+Bz00=
73 +go.opentelemetry.io/collector/consumer/consumererror v0.125.0/go.mod h1:t/YDU7G2TxG27LbcUvgKo/l75TI5VApnnqC7FgKZds0=
74 +go.opentelemetry.io/collector/consumer/consumertest v0.125.0 h1:TUkxomGS4DAtjBvcWQd2UY4FDLLEKMQD6iOIDUr/5dM=
75 +go.opentelemetry.io/collector/consumer/consumertest v0.125.0/go.mod h1:vkHf3y85cFLDHARO/cTREVjLjOPAV+cQg7lkC44DWOY=
76 +go.opentelemetry.io/collector/consumer/xconsumer v0.125.0 h1:oTreUlk1KpMSWwuHFnstW+orrjGTyvs2xd3o/Dpy+hI=
77 +go.opentelemetry.io/collector/consumer/xconsumer v0.125.0/go.mod h1:FX0G37r0W+wXRgxxFtwEJ4rlsCB+p0cIaxtU3C4hskw=
78 +go.opentelemetry.io/collector/exporter v0.125.0 h1:GJxeCuOQVPndQzvzpZUFEv5njHuVnzBEuXgddRShiCM=
79 +go.opentelemetry.io/collector/exporter v0.125.0/go.mod h1:x+FFBRxWGk+GGaP7u+Bi+OcZoG5qK5vZmgFwI/t4ZwM=
80 +go.opentelemetry.io/collector/exporter/exportertest v0.125.0 h1:FFCEHKlTct0vl/657E1papmL3NicdcxY4ChZbHI3nWk=
81 +go.opentelemetry.io/collector/exporter/exportertest v0.125.0/go.mod h1:UQ6ETigK5yTASXGvjSlE7RcM8C6Ro+TcR8iqDHpXa2U=
82 +go.opentelemetry.io/collector/exporter/xexporter v0.125.0 h1:be5oK6+QOXvyXiCxfiTKxdo/TmjewvciCbC8iMKi/1s=
83 +go.opentelemetry.io/collector/exporter/xexporter v0.125.0/go.mod h1:ebfTFVtKR2eUiAQQc/cS1aBTV2OoFDvFjLiX+thE23s=
84 +go.opentelemetry.io/collector/extension v1.31.0 h1:DaqSl50jOA3BGtqPfPtSGJy4XwyXtQwvemVl/L9fDb4=
85 +go.opentelemetry.io/collector/extension v1.31.0/go.mod h1:SiRNOZIJ6R0JbHEvs3g84hPEmiys5CZyIlMOE1RQ85s=
86 +go.opentelemetry.io/collector/extension/extensiontest v0.125.0 h1:NYtQ42HBjfS5lnfmZdT8+ucq+i70JGLdvDN8V/drvIw=
87 +go.opentelemetry.io/collector/extension/extensiontest v0.125.0/go.mod h1:HABANc94xQmUtOSZokG5E6Z02GrHfKYSkQqOz+oCpPQ=
88 +go.opentelemetry.io/collector/extension/xextension v0.125.0 h1:4BOzKuI5jtNP74X7LMeezFidSYdg8ny3Epv4GzV7LO4=
89 +go.opentelemetry.io/collector/extension/xextension v0.125.0/go.mod h1:JNx5t/UnzxJiH2piNtko7Nj3E4x6YNkWxrVlpvpbm6w=
90 +go.opentelemetry.io/collector/featuregate v1.31.0 h1:20q7plPQZwmAiaYAa6l1m/i2qDITZuWlhjr4EkmeQls=
91 +go.opentelemetry.io/collector/featuregate v1.31.0/go.mod h1:Y/KsHbvREENKvvN9RlpiWk/IGBK+CATBYzIIpU7nccc=
92 +go.opentelemetry.io/collector/internal/telemetry v0.125.0 h1:6lcGOxw3dAg7LfXTKdN8ZjR+l7KvzLdEiPMhhLwG4r4=
93 +go.opentelemetry.io/collector/internal/telemetry v0.125.0/go.mod h1:5GyFslLqjZgq1DZTtFiluxYhhXrCofHgOOOybodDPGE=
94 +go.opentelemetry.io/collector/pdata v1.31.0 h1:P5WuLr1l2JcIvr6Dw2hl01ltp2ZafPnC4Isv+BLTBqU=
95 +go.opentelemetry.io/collector/pdata v1.31.0/go.mod h1:m41io9nWpy7aCm/uD1L9QcKiZwOP0ldj83JEA34dmlk=
96 +go.opentelemetry.io/collector/pdata/pprofile v0.125.0 h1:Qqlx8w1HpiYZ9RQqjmMQIysI0cHNO1nh3E/fCTeFysA=
97 +go.opentelemetry.io/collector/pdata/pprofile v0.125.0/go.mod h1:p/yK023VxAp8hm27/1G5DPTcMIpnJy3cHGAFUQZGyaQ=
98 +go.opentelemetry.io/collector/pdata/testdata v0.125.0 h1:due1Hl0EEVRVwfCkiamRy5E8lS6yalv0lo8Zl/SJtGw=
99 +go.opentelemetry.io/collector/pdata/testdata v0.125.0/go.mod h1:1GpEWlgdMrd+fWsBk37ZC2YmOP5YU3gFQ4rWuCu9g24=
100 +go.opentelemetry.io/collector/pipeline v0.125.0 h1:oitBgcAFqntDB4ihQJUHJSQ8IHqKFpPkaTVbTYdIUzM=
101 +go.opentelemetry.io/collector/pipeline v0.125.0/go.mod h1:TO02zju/K6E+oFIOdi372Wk0MXd+Szy72zcTsFQwXl4=
102 +go.opentelemetry.io/collector/receiver v1.31.0 h1:OSRrCWclb1QmGPnxFMxQsdegua4vlKpZESOtDKSzKeQ=
103 +go.opentelemetry.io/collector/receiver v1.31.0/go.mod h1:zPUiv3jgJGQSY01nx500cYJiEz6JfaR53BAvCW2tgGs=
104 +go.opentelemetry.io/collector/receiver/receivertest v0.125.0 h1:xV3Jm3OT7SfDpJ5mXhNmK/Nch7f41whA8k0q4XkaWT8=
105 +go.opentelemetry.io/collector/receiver/receivertest v0.125.0/go.mod h1:CoSPPn3kwVcAhdLOK5NVjMCVVjmCsFpBXU9h9I6Kdh8=
106 +go.opentelemetry.io/collector/receiver/xreceiver v0.125.0 h1:wQ1ZgCA0Y3EFWWZ6gZDGDXT7qV5IrqkisccUWhvmmTg=
107 +go.opentelemetry.io/collector/receiver/xreceiver v0.125.0/go.mod h1:5Kl/mtf6oIy+rizFcElkUpp3LEVTJnjAyos6z+FVsGc=
108 go.opentelemetry.io/contrib/bridges/otelzap v0.10.0 h1:ojdSRDvjrnm30beHOmwsSvLpoRF40MlwNCA+Oo93kXU=
109 go.opentelemetry.io/contrib/bridges/otelzap v0.10.0/go.mod h1:oTTm4g7NEtHSV2i/0FeVdPaPgUIZPfQkFbq0vbzqnv0=
110 go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
@@ -131,20 +134,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
134 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
135 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
136 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
134 -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
135 -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
137 +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
138 +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
139 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
140 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
141 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
142 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
143 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
144 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
142 -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
143 -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
145 +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
146 +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
147 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
148 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
146 -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
147 -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
149 +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
150 +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
151 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
152 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
153 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -153,10 +156,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
156 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
157 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
158 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
156 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
157 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
158 -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
159 -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
159 +google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34 h1:h6p3mQqrmT1XkHVTfzLdNz1u7IhINeZkz67/xTbOuWs=
160 +google.golang.org/genproto/googleapis/rpc v0.0.0-20250428153025-10db94c68c34/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
161 +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
162 +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
163 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
164 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
165 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -164,3 +167,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
167 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
168 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
169 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
170 +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
171 +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
src/go/otel-collector/exporter/netdataexporter/netdataapi.go new
+128
@@ -0,0 +1,128 @@
1 +package netdataexporter
2 +
3 +import (
4 + "io"
5 + "os"
6 + "strconv"
7 + "sync"
8 +)
9 +
10 +type ChartOpts struct {
11 + TypeID string
12 + ID string
13 + Name string
14 + Title string
15 + Units string
16 + Family string
17 + Context string
18 + ChartType string
19 + Priority int
20 + UpdateEvery int
21 + Options string
22 + Plugin string
23 + Module string
24 +}
25 +
26 +type DimensionOpts struct {
27 + ID string
28 + Name string
29 + Algorithm string
30 + Multiplier int
31 + Divisor int
32 + Options string
33 +}
34 +
35 +type safeWriter struct {
36 + mx *sync.Mutex
37 + w io.Writer
38 +}
39 +
40 +func (w *safeWriter) Write(p []byte) (n int, err error) {
41 + w.mx.Lock()
42 + n, err = w.w.Write(p)
43 + w.mx.Unlock()
44 + return n, err
45 +}
46 +
47 +func newNetdataStdoutApi() *netdataAPI {
48 + w := &safeWriter{
49 + mx: &sync.Mutex{},
50 + w: os.Stdout,
51 + }
52 + return newNetdataApi(w)
53 +}
54 +
55 +func newNetdataApi(w io.Writer) *netdataAPI {
56 + if w == nil {
57 + panic("writer cannot be nil")
58 + }
59 + return &netdataAPI{w}
60 +}
61 +
62 +type netdataAPI struct {
63 + io.Writer
64 +}
65 +
66 +const quotes = "' '"
67 +
68 +var (
69 + newLine = []byte("\n")
70 +)
71 +
72 +func (a *netdataAPI) chart(opts ChartOpts) {
73 + _, _ = a.Write([]byte("CHART " + "'" +
74 + opts.TypeID + "." + opts.ID + quotes +
75 + opts.Name + quotes +
76 + opts.Title + quotes +
77 + opts.Units + quotes +
78 + opts.Family + quotes +
79 + opts.Context + quotes +
80 + opts.ChartType + quotes +
81 + strconv.Itoa(opts.Priority) + quotes +
82 + strconv.Itoa(opts.UpdateEvery) + quotes +
83 + opts.Options + quotes +
84 + opts.Plugin + quotes +
85 + opts.Module + "'\n"))
86 +}
87 +
88 +func (a *netdataAPI) dimension(opts DimensionOpts) {
89 + _, _ = a.Write([]byte("DIMENSION '" +
90 + opts.ID + quotes +
91 + opts.Name + quotes +
92 + opts.Algorithm + quotes +
93 + strconv.Itoa(opts.Multiplier) + quotes +
94 + strconv.Itoa(opts.Divisor) + quotes +
95 + opts.Options + "'\n"))
96 +}
97 +
98 +func (a *netdataAPI) clabel(key, value string) {
99 + _, _ = a.Write([]byte("CLABEL '" +
100 + key + quotes +
101 + value + " '0'\n"))
102 +}
103 +
104 +// CLABELCOMMIT adds labels to the chart. Should be called after one or more CLABEL.
105 +func (a *netdataAPI) clabelcommit() {
106 + _, _ = a.Write([]byte("CLABELCOMMIT\n"))
107 +}
108 +
109 +func (a *netdataAPI) begin(chartId string, msSince int) {
110 + if msSince > 0 {
111 + _, _ = a.Write([]byte("BEGIN " + "'" + chartId + "' " + strconv.Itoa(msSince) + "\n"))
112 + } else {
113 + _, _ = a.Write([]byte("BEGIN " + "'" + chartId + "'\n"))
114 + }
115 +}
116 +
117 +func (a *netdataAPI) set(dimensionId string, value int64) {
118 + _, _ = a.Write([]byte("SET '" + dimensionId + "' = " + strconv.FormatInt(value, 10) + "\n"))
119 +}
120 +
121 +// SETEMPTY sets an empty value for a dimension in the initialized chart.
122 +func (a *netdataAPI) setempty(id string) {
123 + _, _ = a.Write([]byte("SET '" + id + "' = \n"))
124 +}
125 +
126 +func (a *netdataAPI) end() {
127 + _, _ = a.Write([]byte("END\n\n"))
128 +}
src/go/otel-collector/exporter/netdataexporter/netdatachart.go new
+28
@@ -0,0 +1,28 @@
1 +package netdataexporter
2 +
3 +//type ChartDefinition struct {
4 +// ID string // must be globally unique
5 +// Title string
6 +// Units string
7 +// Family string
8 +// Context string // metric name
9 +// Type string // line, area, stacked
10 +// Options string // set to "obsolete" to delete the chart, otherwise empty string
11 +// Labels []LabelDefinition
12 +// Dimensions []DimensionDefinition
13 +// IsNew bool // Indicates if this chart is new and needs to be sent to Netdata
14 +//}
15 +//
16 +//// LabelDefinition represents a Netdata chart label
17 +//type LabelDefinition struct {
18 +// Name string
19 +// Value string
20 +//}
21 +//
22 +//// DimensionDefinition represents a Netdata chart dimension
23 +//type DimensionDefinition struct {
24 +// ID string // must be unique within the chart
25 +// Name string // replaces ID in UI
26 +// Algo string // absolute (Gauge), incremental (Counter)
27 +// Value float64
28 +//}
src/go/otel-collector/release-config.yaml.in
+5 -5
@@ -8,13 +8,13 @@ dist:
8 debug_compilation: @DEBUG_BUILD@
9
10 receivers:
11 - - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.124.0
12 - - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver v0.124.0
11 + - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.125.0
12 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver v0.125.0
13
14 exporters:
15 - - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.124.0
16 - - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.124.0
17 - - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.124.0
15 + - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.125.0
16 + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.125.0
17 + - gomod: go.opentelemetry.io/collector/exporter/otlpexporter v0.125.0
18 - gomod: github.com/netdata/netdata/otel-collector/exporter/journaldexporter v0.0.0
19
20 replaces: