mirror of
https://github.com/kubernetes/node-problem-detector.git
synced 2026-08-28 01:47:20 +00:00
chore(deps): pre-vendor OpenTelemetry dependencies
This commit is contained in:
Generated
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Generated
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
# OpenTelemetry Google Cloud Monitoring Exporter
|
||||
|
||||
[](https://pkg.go.dev/github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric)
|
||||
[![Apache License][license-image]][license-url]
|
||||
|
||||
OpenTelemetry Google Cloud Monitoring Exporter allows the user to send collected metrics to Google Cloud.
|
||||
|
||||
To get started with instrumentation in Google Cloud, see [Generate traces and metrics with
|
||||
Go](https://cloud.google.com/stackdriver/docs/instrumentation/setup/go).
|
||||
|
||||
To learn more about instrumentation and observability, including opinionated recommendations
|
||||
for Google Cloud Observability, visit [Instrumentation and
|
||||
observability](https://cloud.google.com/stackdriver/docs/instrumentation/overview).
|
||||
|
||||
[Google Cloud Monitoring](https://cloud.google.com/monitoring) provides visibility into the performance, uptime, and overall health of cloud-powered applications. It collects metrics, events, and metadata from Google Cloud, Amazon Web Services, hosted uptime probes, application instrumentation, and a variety of common application components including Cassandra, Nginx, Apache Web Server, Elasticsearch, and many others. Operations ingests that data and generates insights via dashboards, charts, and alerts. Cloud Monitoring alerting helps you collaborate by integrating with Slack, PagerDuty, and more.
|
||||
|
||||
## Setup
|
||||
|
||||
Google Cloud Monitoring is a managed service provided by Google Cloud Platform. Google Cloud Monitoring requires to set up "Workspace" in advance. The guide to create a new Workspace is available on [the official document](https://cloud.google.com/monitoring/workspaces/create).
|
||||
|
||||
## Authentication
|
||||
|
||||
The Google Cloud Monitoring exporter depends upon [`google.FindDefaultCredentials`](https://pkg.go.dev/golang.org/x/oauth2/google?tab=doc#FindDefaultCredentials), so the service account is automatically detected by default, but also the custom credential file (so called `service_account_key.json`) can be detected with specific conditions. Quoting from the document of `google.FindDefaultCredentials`:
|
||||
|
||||
* A JSON file whose path is specified by the `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
|
||||
* A JSON file in a location known to the gcloud command-line tool. On Windows, this is `%APPDATA%/gcloud/application_default_credentials.json`. On other systems, `$HOME/.config/gcloud/application_default_credentials.json`.
|
||||
|
||||
When running code locally, you may need to specify a Google Project ID in addition to `GOOGLE_APPLICATION_CREDENTIALS`. This is best done using an environment variable (e.g. `GOOGLE_CLOUD_PROJECT`) and the `metric.WithProjectID` method, e.g.:
|
||||
|
||||
```golang
|
||||
projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
|
||||
opts := []mexporter.Option{
|
||||
mexporter.WithProjectID(projectID),
|
||||
}
|
||||
```
|
||||
|
||||
## Useful links
|
||||
|
||||
* For more information on OpenTelemetry, visit: https://opentelemetry.io/
|
||||
* For more about OpenTelemetry Go, visit: https://github.com/open-telemetry/opentelemetry-go
|
||||
* Learn more about Google Cloud Monitoring at https://cloud.google.com/monitoring
|
||||
|
||||
[license-url]: https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/main/LICENSE
|
||||
[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
|
||||
vendor/github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric/cloudmonitoring.go
Generated
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
|
||||
monitoring "cloud.google.com/go/monitoring/apiv3/v2"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
// New creates a new Exporter thats implements metric.Exporter.
|
||||
func New(opts ...Option) (sdkmetric.Exporter, error) {
|
||||
o := options{
|
||||
context: context.Background(),
|
||||
resourceAttributeFilter: DefaultResourceAttributesFilter,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
if o.projectID == "" {
|
||||
creds, err := google.FindDefaultCredentials(o.context, monitoring.DefaultAuthScopes()...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find Google Cloud credentials: %v", err)
|
||||
}
|
||||
if creds.ProjectID == "" {
|
||||
return nil, errors.New("google cloud monitoring: no project found with application default credentials")
|
||||
}
|
||||
o.projectID = creds.ProjectID
|
||||
}
|
||||
return newMetricExporter(&o)
|
||||
}
|
||||
Generated
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
// TODO: remove this file when the constants are ready in the Go SDK
|
||||
|
||||
// Mappings for the well-known OpenTelemetry resource label keys
|
||||
// to applicable Monitored Resource label keys.
|
||||
// A uniquely identifying name for the Kubernetes cluster. Kubernetes
|
||||
// does not have cluster names as an internal concept so this may be
|
||||
// set to any meaningful value within the environment. For example,
|
||||
// GKE clusters have a name which can be used for this label.
|
||||
const (
|
||||
// Deprecated: use semconv.CloudProviderKey instead.
|
||||
CloudKeyProvider = "cloud.provider"
|
||||
// Deprecated: use semconv.CloudAccountIDKey instead.
|
||||
CloudKeyAccountID = "cloud.account.id"
|
||||
// Deprecated: use semconv.CloudRegionKey instead.
|
||||
CloudKeyRegion = "cloud.region"
|
||||
// Deprecated: use semconv.CloudAvailabilityZoneKey instead.
|
||||
CloudKeyZone = "cloud.availability_zone"
|
||||
|
||||
// Deprecated: use semconv.ServiceNamespaceKey instead.
|
||||
ServiceKeyNamespace = "service.namespace"
|
||||
// Deprecated: use semconv.ServiceInstanceIDKey instead.
|
||||
ServiceKeyInstanceID = "service.instance.id"
|
||||
// Deprecated: use semconv.ServiceNameKey instead.
|
||||
ServiceKeyName = "service.name"
|
||||
|
||||
// Deprecated: HostType is not needed.
|
||||
HostType = "host"
|
||||
// A uniquely identifying name for the host.
|
||||
// Deprecated: use semconv.HostNameKey instead.
|
||||
HostKeyName = "host.name"
|
||||
// A hostname as returned by the 'hostname' command on host machine.
|
||||
// Deprecated: HostKeyHostName is not needed.
|
||||
HostKeyHostName = "host.hostname"
|
||||
// Deprecated: use semconv.HostIDKey instead.
|
||||
HostKeyID = "host.id"
|
||||
// Deprecated: use semconv.HostTypeKey instead.
|
||||
HostKeyType = "host.type"
|
||||
|
||||
// A uniquely identifying name for the Container.
|
||||
// Deprecated: use semconv.ContainerNameKey instead.
|
||||
ContainerKeyName = "container.name"
|
||||
// Deprecated: use semconv.ContainerImageNameKey instead.
|
||||
ContainerKeyImageName = "container.image.name"
|
||||
// Deprecated: use semconv.ContainerImageTagKey instead.
|
||||
ContainerKeyImageTag = "container.image.tag"
|
||||
|
||||
// Cloud Providers
|
||||
// Deprecated: use semconv.CloudProviderAWS instead.
|
||||
CloudProviderAWS = "aws"
|
||||
// Deprecated: use semconv.CloudProviderGCP instead.
|
||||
CloudProviderGCP = "gcp"
|
||||
// Deprecated: use semconv.CloudProviderAzure instead.
|
||||
CloudProviderAZURE = "azure"
|
||||
|
||||
// Deprecated: Use "k8s" instead. This should not be needed.
|
||||
K8S = "k8s"
|
||||
// Deprecated: use semconv.K8SClusterNameKey instead.
|
||||
K8SKeyClusterName = "k8s.cluster.name"
|
||||
// Deprecated: use semconv.K8SNamespaceNameKey instead.
|
||||
K8SKeyNamespaceName = "k8s.namespace.name"
|
||||
// Deprecated: use semconv.K8SPodNameKey instead.
|
||||
K8SKeyPodName = "k8s.pod.name"
|
||||
// Deprecated: use semconv.K8SDeploymentNameKey instead.
|
||||
K8SKeyDeploymentName = "k8s.deployment.name"
|
||||
|
||||
// Monitored Resources types
|
||||
// Deprecated: Use "k8s_container" instead.
|
||||
K8SContainer = "k8s_container"
|
||||
// Deprecated: Use "k8s_node" instead.
|
||||
K8SNode = "k8s_node"
|
||||
// Deprecated: Use "k8s_pod" instead.
|
||||
K8SPod = "k8s_pod"
|
||||
// Deprecated: Use "k8s_cluster" instead.
|
||||
K8SCluster = "k8s_cluster"
|
||||
// Deprecated: Use "gce_instance" instead.
|
||||
GCEInstance = "gce_instance"
|
||||
// Deprecated: Use "aws_ec2_instance" instead.
|
||||
AWSEC2Instance = "aws_ec2_instance"
|
||||
// Deprecated: Use "generic_task" instead.
|
||||
GenericTask = "generic_task"
|
||||
)
|
||||
Generated
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
errBlankProjectID = errors.New("expecting a non-blank ProjectID")
|
||||
)
|
||||
|
||||
type errUnexpectedAggregationKind struct {
|
||||
kind string
|
||||
}
|
||||
|
||||
func (e errUnexpectedAggregationKind) Error() string {
|
||||
return fmt.Sprintf("the metric kind is unexpected: %v", e.kind)
|
||||
}
|
||||
Generated
Vendored
+898
@@ -0,0 +1,898 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
monitoring "cloud.google.com/go/monitoring/apiv3/v2"
|
||||
"cloud.google.com/go/monitoring/apiv3/v2/monitoringpb"
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/genproto/googleapis/api/distribution"
|
||||
"google.golang.org/genproto/googleapis/api/label"
|
||||
googlemetricpb "google.golang.org/genproto/googleapis/api/metric"
|
||||
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping"
|
||||
)
|
||||
|
||||
const (
|
||||
// The number of timeserieses to send to GCM in a single request. This
|
||||
// is a hard limit in the GCM API, so we never want to exceed 200.
|
||||
sendBatchSize = 200
|
||||
|
||||
cloudMonitoringMetricDescriptorNameFormat = "workload.googleapis.com/%s"
|
||||
platformMappingMonitoredResourceKey = "gcp.resource_type"
|
||||
)
|
||||
|
||||
// key is used to judge the uniqueness of the record descriptor.
|
||||
type key struct {
|
||||
name string
|
||||
libraryname string
|
||||
}
|
||||
|
||||
func keyOf(metrics metricdata.Metrics, library instrumentation.Scope) key {
|
||||
return key{
|
||||
name: metrics.Name,
|
||||
libraryname: library.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// metricExporter is the implementation of OpenTelemetry metric exporter for
|
||||
// Google Cloud Monitoring.
|
||||
type metricExporter struct {
|
||||
o *options
|
||||
shutdown chan struct{}
|
||||
// mdCache is the cache to hold MetricDescriptor to avoid creating duplicate MD.
|
||||
mdCache map[key]*googlemetricpb.MetricDescriptor
|
||||
client *monitoring.MetricClient
|
||||
mdLock sync.RWMutex
|
||||
shutdownOnce sync.Once
|
||||
}
|
||||
|
||||
// ForceFlush does nothing, the exporter holds no state.
|
||||
func (e *metricExporter) ForceFlush(ctx context.Context) error { return ctx.Err() }
|
||||
|
||||
// Shutdown shuts down the client connections.
|
||||
func (e *metricExporter) Shutdown(ctx context.Context) error {
|
||||
err := errShutdown
|
||||
e.shutdownOnce.Do(func() {
|
||||
close(e.shutdown)
|
||||
err = errors.Join(ctx.Err(), e.client.Close())
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// newMetricExporter returns an exporter that uploads OTel metric data to Google Cloud Monitoring.
|
||||
func newMetricExporter(o *options) (*metricExporter, error) {
|
||||
if strings.TrimSpace(o.projectID) == "" {
|
||||
return nil, errBlankProjectID
|
||||
}
|
||||
|
||||
client := o.monitoringClient
|
||||
if client == nil {
|
||||
clientOpts := append([]option.ClientOption{option.WithGRPCDialOption(grpc.WithUserAgent(userAgent))}, o.monitoringClientOptions...)
|
||||
ctx := o.context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
var err error
|
||||
client, err = monitoring.NewMetricClient(ctx, clientOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.compression == "gzip" {
|
||||
client.CallOptions.GetMetricDescriptor = append(client.CallOptions.GetMetricDescriptor,
|
||||
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
|
||||
client.CallOptions.CreateMetricDescriptor = append(client.CallOptions.CreateMetricDescriptor,
|
||||
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
|
||||
client.CallOptions.CreateTimeSeries = append(client.CallOptions.CreateTimeSeries,
|
||||
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
|
||||
client.CallOptions.CreateServiceTimeSeries = append(client.CallOptions.CreateServiceTimeSeries,
|
||||
gax.WithGRPCOptions(grpc.UseCompressor(gzip.Name)))
|
||||
}
|
||||
}
|
||||
|
||||
cache := map[key]*googlemetricpb.MetricDescriptor{}
|
||||
e := &metricExporter{
|
||||
o: o,
|
||||
mdCache: cache,
|
||||
client: client,
|
||||
shutdown: make(chan struct{}),
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
var errShutdown = fmt.Errorf("exporter is shutdown")
|
||||
|
||||
// Export exports OpenTelemetry Metrics to Google Cloud Monitoring.
|
||||
func (me *metricExporter) Export(ctx context.Context, rm *metricdata.ResourceMetrics) error {
|
||||
select {
|
||||
case <-me.shutdown:
|
||||
return errShutdown
|
||||
default:
|
||||
}
|
||||
|
||||
if me.o.destinationProjectQuota {
|
||||
ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{"x-goog-user-project": strings.TrimPrefix(me.o.projectID, "projects/")}))
|
||||
}
|
||||
return errors.Join(
|
||||
me.exportMetricDescriptor(ctx, rm),
|
||||
me.exportTimeSeries(ctx, rm),
|
||||
)
|
||||
}
|
||||
|
||||
// Temporality returns the Temporality to use for an instrument kind.
|
||||
func (me *metricExporter) Temporality(ik metric.InstrumentKind) metricdata.Temporality {
|
||||
return metric.DefaultTemporalitySelector(ik)
|
||||
}
|
||||
|
||||
// Aggregation returns the Aggregation to use for an instrument kind.
|
||||
func (me *metricExporter) Aggregation(ik metric.InstrumentKind) metric.Aggregation {
|
||||
return metric.DefaultAggregationSelector(ik)
|
||||
}
|
||||
|
||||
// exportMetricDescriptor create MetricDescriptor from the record
|
||||
// if the descriptor is not registered in Cloud Monitoring yet.
|
||||
func (me *metricExporter) exportMetricDescriptor(ctx context.Context, rm *metricdata.ResourceMetrics) error {
|
||||
// We only send metric descriptors if we're configured *and* we're not sending service timeseries.
|
||||
if me.o.disableCreateMetricDescriptors {
|
||||
return nil
|
||||
}
|
||||
|
||||
me.mdLock.Lock()
|
||||
defer me.mdLock.Unlock()
|
||||
mds := make(map[key]*googlemetricpb.MetricDescriptor)
|
||||
extraLabels := me.extraLabelsFromResource(rm.Resource)
|
||||
for _, scope := range rm.ScopeMetrics {
|
||||
for _, metrics := range scope.Metrics {
|
||||
k := keyOf(metrics, scope.Scope)
|
||||
|
||||
if _, ok := me.mdCache[k]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, localok := mds[k]; !localok {
|
||||
md := me.recordToMdpb(metrics, extraLabels)
|
||||
mds[k] = md
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This process is synchronous and blocks longer time if records in cps
|
||||
// have many different descriptors. In the cps.ForEach above, it should spawn
|
||||
// goroutines to send CreateMetricDescriptorRequest asynchronously in the case
|
||||
// the descriptor does not exist in global cache (me.mdCache).
|
||||
// See details in #26.
|
||||
var errs []error
|
||||
for kmd, md := range mds {
|
||||
err := me.createMetricDescriptorIfNeeded(ctx, md)
|
||||
if err == nil {
|
||||
me.mdCache[kmd] = md
|
||||
}
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (me *metricExporter) createMetricDescriptorIfNeeded(ctx context.Context, md *googlemetricpb.MetricDescriptor) error {
|
||||
mdReq := &monitoringpb.GetMetricDescriptorRequest{
|
||||
Name: fmt.Sprintf("projects/%s/metricDescriptors/%s", me.o.projectID, md.Type),
|
||||
}
|
||||
_, err := me.client.GetMetricDescriptor(ctx, mdReq)
|
||||
if err == nil {
|
||||
// If the metric descriptor already exists, skip the CreateMetricDescriptor call.
|
||||
// Metric descriptors cannot be updated without deleting them first, so there
|
||||
// isn't anything we can do here:
|
||||
// https://cloud.google.com/monitoring/custom-metrics/creating-metrics#md-modify
|
||||
return nil
|
||||
}
|
||||
req := &monitoringpb.CreateMetricDescriptorRequest{
|
||||
Name: fmt.Sprintf("projects/%s", me.o.projectID),
|
||||
MetricDescriptor: md,
|
||||
}
|
||||
_, err = me.client.CreateMetricDescriptor(ctx, req)
|
||||
return err
|
||||
}
|
||||
|
||||
// exportTimeSeries create TimeSeries from the records in cps.
|
||||
// res should be the common resource among all TimeSeries, such as instance id, application name and so on.
|
||||
func (me *metricExporter) exportTimeSeries(ctx context.Context, rm *metricdata.ResourceMetrics) error {
|
||||
tss, err := me.recordsToTspbs(rm)
|
||||
if len(tss) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("projects/%s", me.o.projectID)
|
||||
|
||||
errs := []error{err}
|
||||
for i := 0; i < len(tss); i += sendBatchSize {
|
||||
j := i + sendBatchSize
|
||||
if j >= len(tss) {
|
||||
j = len(tss)
|
||||
}
|
||||
|
||||
// TODO: When this exporter is rewritten, support writing to multiple
|
||||
// projects based on the "gcp.project.id" resource.
|
||||
req := &monitoringpb.CreateTimeSeriesRequest{
|
||||
Name: name,
|
||||
TimeSeries: tss[i:j],
|
||||
}
|
||||
if me.o.createServiceTimeSeries {
|
||||
errs = append(errs, me.client.CreateServiceTimeSeries(ctx, req))
|
||||
} else {
|
||||
errs = append(errs, me.client.CreateTimeSeries(ctx, req))
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (me *metricExporter) extraLabelsFromResource(res *resource.Resource) *attribute.Set {
|
||||
set, _ := attribute.NewSetWithFiltered(res.Attributes(), me.o.resourceAttributeFilter)
|
||||
return &set
|
||||
}
|
||||
|
||||
// descToMetricType converts descriptor to MetricType proto type.
|
||||
// Basically this returns default value ("workload.googleapis.com/[metric type]").
|
||||
func (me *metricExporter) descToMetricType(desc metricdata.Metrics) string {
|
||||
if formatter := me.o.metricDescriptorTypeFormatter; formatter != nil {
|
||||
return formatter(desc)
|
||||
}
|
||||
return fmt.Sprintf(cloudMonitoringMetricDescriptorNameFormat, desc.Name)
|
||||
}
|
||||
|
||||
// metricTypeToDisplayName takes a GCM metric type, like (workload.googleapis.com/MyCoolMetric) and returns the display name.
|
||||
func metricTypeToDisplayName(mURL string) string {
|
||||
// strip domain, keep path after domain.
|
||||
u, err := url.Parse(fmt.Sprintf("metrics://%s", mURL))
|
||||
if err != nil || u.Path == "" {
|
||||
return mURL
|
||||
}
|
||||
return strings.TrimLeft(u.Path, "/")
|
||||
}
|
||||
|
||||
// recordToMdpb extracts data and converts them to googlemetricpb.MetricDescriptor.
|
||||
func (me *metricExporter) recordToMdpb(metrics metricdata.Metrics, extraLabels *attribute.Set) *googlemetricpb.MetricDescriptor {
|
||||
name := metrics.Name
|
||||
typ := me.descToMetricType(metrics)
|
||||
kind, valueType := recordToMdpbKindType(metrics.Data)
|
||||
|
||||
// Detailed explanations on MetricDescriptor proto is not documented on
|
||||
// generated Go packages. Refer to the original proto file.
|
||||
// https://github.com/googleapis/googleapis/blob/50af053/google/api/metric.proto#L33
|
||||
return &googlemetricpb.MetricDescriptor{
|
||||
Name: name,
|
||||
DisplayName: metricTypeToDisplayName(typ),
|
||||
Type: typ,
|
||||
MetricKind: kind,
|
||||
ValueType: valueType,
|
||||
Unit: string(metrics.Unit),
|
||||
Description: metrics.Description,
|
||||
Labels: labelDescriptors(metrics, extraLabels),
|
||||
}
|
||||
}
|
||||
|
||||
func labelDescriptors(metrics metricdata.Metrics, extraLabels *attribute.Set) []*label.LabelDescriptor {
|
||||
labels := []*label.LabelDescriptor{}
|
||||
seenKeys := map[string]struct{}{}
|
||||
addAttributes := func(attr *attribute.Set) {
|
||||
iter := attr.Iter()
|
||||
for iter.Next() {
|
||||
kv := iter.Attribute()
|
||||
// Skip keys that have already been set
|
||||
if _, ok := seenKeys[normalizeLabelKey(string(kv.Key))]; ok {
|
||||
continue
|
||||
}
|
||||
labels = append(labels, &label.LabelDescriptor{
|
||||
Key: normalizeLabelKey(string(kv.Key)),
|
||||
})
|
||||
seenKeys[normalizeLabelKey(string(kv.Key))] = struct{}{}
|
||||
}
|
||||
}
|
||||
addAttributes(extraLabels)
|
||||
switch a := metrics.Data.(type) {
|
||||
case metricdata.Gauge[int64]:
|
||||
for _, pt := range a.DataPoints {
|
||||
addAttributes(&pt.Attributes)
|
||||
}
|
||||
case metricdata.Gauge[float64]:
|
||||
for _, pt := range a.DataPoints {
|
||||
addAttributes(&pt.Attributes)
|
||||
}
|
||||
case metricdata.Sum[int64]:
|
||||
for _, pt := range a.DataPoints {
|
||||
addAttributes(&pt.Attributes)
|
||||
}
|
||||
case metricdata.Sum[float64]:
|
||||
for _, pt := range a.DataPoints {
|
||||
addAttributes(&pt.Attributes)
|
||||
}
|
||||
case metricdata.Histogram[float64]:
|
||||
for _, pt := range a.DataPoints {
|
||||
addAttributes(&pt.Attributes)
|
||||
}
|
||||
case metricdata.Histogram[int64]:
|
||||
for _, pt := range a.DataPoints {
|
||||
addAttributes(&pt.Attributes)
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
type attributes struct {
|
||||
attrs attribute.Set
|
||||
}
|
||||
|
||||
func (attrs *attributes) GetString(key string) (string, bool) {
|
||||
value, ok := attrs.attrs.Value(attribute.Key(key))
|
||||
return value.AsString(), ok
|
||||
}
|
||||
|
||||
// resourceToMonitoredResourcepb converts resource in OTel to MonitoredResource
|
||||
// proto type for Cloud Monitoring.
|
||||
//
|
||||
// https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.monitoredResourceDescriptors
|
||||
func (me *metricExporter) resourceToMonitoredResourcepb(res *resource.Resource) *monitoredrespb.MonitoredResource {
|
||||
platformMrType, platformMappingRequested := res.Set().Value(platformMappingMonitoredResourceKey)
|
||||
|
||||
// check if platform mapping is requested and possible
|
||||
if platformMappingRequested && platformMrType.AsString() == me.o.monitoredResourceDescription.mrType {
|
||||
// assemble attributes required to construct this MR
|
||||
attributeMap := make(map[string]string)
|
||||
for expectedLabel := range me.o.monitoredResourceDescription.mrLabels {
|
||||
value, found := res.Set().Value(attribute.Key(expectedLabel))
|
||||
if found {
|
||||
attributeMap[expectedLabel] = value.AsString()
|
||||
}
|
||||
}
|
||||
return &monitoredrespb.MonitoredResource{
|
||||
Type: platformMrType.AsString(),
|
||||
Labels: attributeMap,
|
||||
}
|
||||
}
|
||||
|
||||
gmr := resourcemapping.ResourceAttributesToMonitoringMonitoredResource(&attributes{
|
||||
attrs: attribute.NewSet(res.Attributes()...),
|
||||
})
|
||||
newLabels := make(map[string]string, len(gmr.Labels))
|
||||
for k, v := range gmr.Labels {
|
||||
newLabels[k] = sanitizeUTF8(v)
|
||||
}
|
||||
mr := &monitoredrespb.MonitoredResource{
|
||||
Type: gmr.Type,
|
||||
Labels: newLabels,
|
||||
}
|
||||
return mr
|
||||
}
|
||||
|
||||
// recordToMdpbKindType return the mapping from OTel's record descriptor to
|
||||
// Cloud Monitoring's MetricKind and ValueType.
|
||||
func recordToMdpbKindType(a metricdata.Aggregation) (googlemetricpb.MetricDescriptor_MetricKind, googlemetricpb.MetricDescriptor_ValueType) {
|
||||
switch agg := a.(type) {
|
||||
case metricdata.Gauge[int64]:
|
||||
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_INT64
|
||||
case metricdata.Gauge[float64]:
|
||||
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_DOUBLE
|
||||
case metricdata.Sum[int64]:
|
||||
if agg.IsMonotonic {
|
||||
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_INT64
|
||||
}
|
||||
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_INT64
|
||||
case metricdata.Sum[float64]:
|
||||
if agg.IsMonotonic {
|
||||
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_DOUBLE
|
||||
}
|
||||
return googlemetricpb.MetricDescriptor_GAUGE, googlemetricpb.MetricDescriptor_DOUBLE
|
||||
case metricdata.Histogram[int64], metricdata.Histogram[float64]:
|
||||
return googlemetricpb.MetricDescriptor_CUMULATIVE, googlemetricpb.MetricDescriptor_DISTRIBUTION
|
||||
default:
|
||||
return googlemetricpb.MetricDescriptor_METRIC_KIND_UNSPECIFIED, googlemetricpb.MetricDescriptor_VALUE_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
// recordToMpb converts data from records to Metric proto type for Cloud Monitoring.
|
||||
func (me *metricExporter) recordToMpb(metrics metricdata.Metrics, attributes attribute.Set, library instrumentation.Scope, extraLabels *attribute.Set) *googlemetricpb.Metric {
|
||||
me.mdLock.RLock()
|
||||
defer me.mdLock.RUnlock()
|
||||
k := keyOf(metrics, library)
|
||||
md, ok := me.mdCache[k]
|
||||
if !ok {
|
||||
md = me.recordToMdpb(metrics, extraLabels)
|
||||
}
|
||||
|
||||
labels := make(map[string]string)
|
||||
addAttributes := func(attr *attribute.Set) {
|
||||
iter := attr.Iter()
|
||||
for iter.Next() {
|
||||
kv := iter.Attribute()
|
||||
labels[normalizeLabelKey(string(kv.Key))] = sanitizeUTF8(kv.Value.Emit())
|
||||
}
|
||||
}
|
||||
addAttributes(extraLabels)
|
||||
addAttributes(&attributes)
|
||||
|
||||
return &googlemetricpb.Metric{
|
||||
Type: md.Type,
|
||||
Labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
// recordToTspb converts record to TimeSeries proto type with common resource.
|
||||
// ref. https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries
|
||||
func (me *metricExporter) recordToTspb(m metricdata.Metrics, mr *monitoredrespb.MonitoredResource, library instrumentation.Scope, extraLabels *attribute.Set) ([]*monitoringpb.TimeSeries, error) {
|
||||
var tss []*monitoringpb.TimeSeries
|
||||
var errs []error
|
||||
if m.Data == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch a := m.Data.(type) {
|
||||
case metricdata.Gauge[int64]:
|
||||
for _, point := range a.DataPoints {
|
||||
ts, err := gaugeToTimeSeries[int64](point, m, mr)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
case metricdata.Gauge[float64]:
|
||||
for _, point := range a.DataPoints {
|
||||
ts, err := gaugeToTimeSeries[float64](point, m, mr)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
case metricdata.Sum[int64]:
|
||||
for _, point := range a.DataPoints {
|
||||
var ts *monitoringpb.TimeSeries
|
||||
var err error
|
||||
if a.IsMonotonic {
|
||||
ts, err = sumToTimeSeries[int64](point, m, mr)
|
||||
} else {
|
||||
// Send non-monotonic sums as gauges
|
||||
ts, err = gaugeToTimeSeries[int64](point, m, mr)
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
case metricdata.Sum[float64]:
|
||||
for _, point := range a.DataPoints {
|
||||
var ts *monitoringpb.TimeSeries
|
||||
var err error
|
||||
if a.IsMonotonic {
|
||||
ts, err = sumToTimeSeries[float64](point, m, mr)
|
||||
} else {
|
||||
// Send non-monotonic sums as gauges
|
||||
ts, err = gaugeToTimeSeries[float64](point, m, mr)
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
case metricdata.Histogram[int64]:
|
||||
for _, point := range a.DataPoints {
|
||||
ts, err := histogramToTimeSeries(point, m, mr, me.o.enableSumOfSquaredDeviation, me.o.projectID)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
case metricdata.Histogram[float64]:
|
||||
for _, point := range a.DataPoints {
|
||||
ts, err := histogramToTimeSeries(point, m, mr, me.o.enableSumOfSquaredDeviation, me.o.projectID)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
case metricdata.ExponentialHistogram[int64]:
|
||||
for _, point := range a.DataPoints {
|
||||
ts, err := expHistogramToTimeSeries(point, m, mr, me.o.enableSumOfSquaredDeviation, me.o.projectID)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
case metricdata.ExponentialHistogram[float64]:
|
||||
for _, point := range a.DataPoints {
|
||||
ts, err := expHistogramToTimeSeries(point, m, mr, me.o.enableSumOfSquaredDeviation, me.o.projectID)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
ts.Metric = me.recordToMpb(m, point.Attributes, library, extraLabels)
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
default:
|
||||
errs = append(errs, errUnexpectedAggregationKind{kind: reflect.TypeOf(m.Data).String()})
|
||||
}
|
||||
return tss, errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (me *metricExporter) recordsToTspbs(rm *metricdata.ResourceMetrics) ([]*monitoringpb.TimeSeries, error) {
|
||||
mr := me.resourceToMonitoredResourcepb(rm.Resource)
|
||||
extraLabels := me.extraLabelsFromResource(rm.Resource)
|
||||
|
||||
var (
|
||||
tss []*monitoringpb.TimeSeries
|
||||
errs []error
|
||||
)
|
||||
for _, scope := range rm.ScopeMetrics {
|
||||
for _, metrics := range scope.Metrics {
|
||||
ts, err := me.recordToTspb(metrics, mr, scope.Scope, extraLabels)
|
||||
errs = append(errs, err)
|
||||
tss = append(tss, ts...)
|
||||
}
|
||||
}
|
||||
|
||||
return tss, errors.Join(errs...)
|
||||
}
|
||||
|
||||
func sanitizeUTF8(s string) string {
|
||||
return strings.ToValidUTF8(s, "�")
|
||||
}
|
||||
|
||||
func gaugeToTimeSeries[N int64 | float64](point metricdata.DataPoint[N], metrics metricdata.Metrics, mr *monitoredrespb.MonitoredResource) (*monitoringpb.TimeSeries, error) {
|
||||
value, valueType := numberDataPointToValue(point)
|
||||
timestamp := timestamppb.New(point.Time)
|
||||
if err := timestamp.CheckValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &monitoringpb.TimeSeries{
|
||||
Resource: mr,
|
||||
Unit: metrics.Unit,
|
||||
Description: metrics.Description,
|
||||
MetricKind: googlemetricpb.MetricDescriptor_GAUGE,
|
||||
ValueType: valueType,
|
||||
Points: []*monitoringpb.Point{{
|
||||
Interval: &monitoringpb.TimeInterval{
|
||||
EndTime: timestamp,
|
||||
},
|
||||
Value: value,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sumToTimeSeries[N int64 | float64](point metricdata.DataPoint[N], metrics metricdata.Metrics, mr *monitoredrespb.MonitoredResource) (*monitoringpb.TimeSeries, error) {
|
||||
interval, err := toNonemptyTimeIntervalpb(point.StartTime, point.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, valueType := numberDataPointToValue[N](point)
|
||||
return &monitoringpb.TimeSeries{
|
||||
Resource: mr,
|
||||
Unit: metrics.Unit,
|
||||
Description: metrics.Description,
|
||||
MetricKind: googlemetricpb.MetricDescriptor_CUMULATIVE,
|
||||
ValueType: valueType,
|
||||
Points: []*monitoringpb.Point{{
|
||||
Interval: interval,
|
||||
Value: value,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO(@dashpole): Refactor to pass control-coupling lint check.
|
||||
//
|
||||
//nolint:revive
|
||||
func histogramToTimeSeries[N int64 | float64](point metricdata.HistogramDataPoint[N], metrics metricdata.Metrics, mr *monitoredrespb.MonitoredResource, enableSOSD bool, projectID string) (*monitoringpb.TimeSeries, error) {
|
||||
interval, err := toNonemptyTimeIntervalpb(point.StartTime, point.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
distributionValue := histToDistribution(point, projectID)
|
||||
if enableSOSD {
|
||||
setSumOfSquaredDeviation(point, distributionValue)
|
||||
}
|
||||
return &monitoringpb.TimeSeries{
|
||||
Resource: mr,
|
||||
Unit: metrics.Unit,
|
||||
Description: metrics.Description,
|
||||
MetricKind: googlemetricpb.MetricDescriptor_CUMULATIVE,
|
||||
ValueType: googlemetricpb.MetricDescriptor_DISTRIBUTION,
|
||||
Points: []*monitoringpb.Point{{
|
||||
Interval: interval,
|
||||
Value: &monitoringpb.TypedValue{
|
||||
Value: &monitoringpb.TypedValue_DistributionValue{
|
||||
DistributionValue: distributionValue,
|
||||
},
|
||||
},
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func expHistogramToTimeSeries[N int64 | float64](point metricdata.ExponentialHistogramDataPoint[N], metrics metricdata.Metrics, mr *monitoredrespb.MonitoredResource, enableSOSD bool, projectID string) (*monitoringpb.TimeSeries, error) {
|
||||
interval, err := toNonemptyTimeIntervalpb(point.StartTime, point.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
distributionValue := expHistToDistribution(point, projectID)
|
||||
// TODO: Implement "setSumOfSquaredDeviationExpHist" for parameter "enableSOSD" functionality.
|
||||
return &monitoringpb.TimeSeries{
|
||||
Resource: mr,
|
||||
Unit: metrics.Unit,
|
||||
Description: metrics.Description,
|
||||
MetricKind: googlemetricpb.MetricDescriptor_CUMULATIVE,
|
||||
ValueType: googlemetricpb.MetricDescriptor_DISTRIBUTION,
|
||||
Points: []*monitoringpb.Point{{
|
||||
Interval: interval,
|
||||
Value: &monitoringpb.TypedValue{
|
||||
Value: &monitoringpb.TypedValue_DistributionValue{
|
||||
DistributionValue: distributionValue,
|
||||
},
|
||||
},
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toNonemptyTimeIntervalpb(start, end time.Time) (*monitoringpb.TimeInterval, error) {
|
||||
// The end time of a new interval must be at least a millisecond after the end time of the
|
||||
// previous interval, for all non-gauge types.
|
||||
// https://cloud.google.com/monitoring/api/ref_v3/rpc/google.monitoring.v3#timeinterval
|
||||
if end.Sub(start).Milliseconds() <= 1 {
|
||||
end = start.Add(time.Millisecond)
|
||||
}
|
||||
startpb := timestamppb.New(start)
|
||||
endpb := timestamppb.New(end)
|
||||
err := errors.Join(
|
||||
startpb.CheckValid(),
|
||||
endpb.CheckValid(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &monitoringpb.TimeInterval{
|
||||
StartTime: startpb,
|
||||
EndTime: endpb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func histToDistribution[N int64 | float64](hist metricdata.HistogramDataPoint[N], projectID string) *distribution.Distribution {
|
||||
counts := make([]int64, len(hist.BucketCounts))
|
||||
for i, v := range hist.BucketCounts {
|
||||
counts[i] = int64(v)
|
||||
}
|
||||
var mean float64
|
||||
if !math.IsNaN(float64(hist.Sum)) && hist.Count > 0 { // Avoid divide-by-zero
|
||||
mean = float64(hist.Sum) / float64(hist.Count)
|
||||
}
|
||||
return &distribution.Distribution{
|
||||
Count: int64(hist.Count),
|
||||
Mean: mean,
|
||||
BucketCounts: counts,
|
||||
BucketOptions: &distribution.Distribution_BucketOptions{
|
||||
Options: &distribution.Distribution_BucketOptions_ExplicitBuckets{
|
||||
ExplicitBuckets: &distribution.Distribution_BucketOptions_Explicit{
|
||||
Bounds: hist.Bounds,
|
||||
},
|
||||
},
|
||||
},
|
||||
Exemplars: toDistributionExemplar[N](hist.Exemplars, projectID),
|
||||
}
|
||||
}
|
||||
|
||||
func expHistToDistribution[N int64 | float64](hist metricdata.ExponentialHistogramDataPoint[N], projectID string) *distribution.Distribution {
|
||||
// First calculate underflow bucket with all negatives + zeros.
|
||||
underflow := hist.ZeroCount
|
||||
negativeBuckets := hist.NegativeBucket.Counts
|
||||
for i := 0; i < len(negativeBuckets); i++ {
|
||||
underflow += negativeBuckets[i]
|
||||
}
|
||||
|
||||
// Next, pull in remaining buckets.
|
||||
counts := make([]int64, len(hist.PositiveBucket.Counts)+2)
|
||||
bucketOptions := &distribution.Distribution_BucketOptions{}
|
||||
counts[0] = int64(underflow)
|
||||
positiveBuckets := hist.PositiveBucket.Counts
|
||||
for i := 0; i < len(positiveBuckets); i++ {
|
||||
counts[i+1] = int64(positiveBuckets[i])
|
||||
}
|
||||
// Overflow bucket is always empty
|
||||
counts[len(counts)-1] = 0
|
||||
|
||||
if len(hist.PositiveBucket.Counts) == 0 {
|
||||
// We cannot send exponential distributions with no positive buckets,
|
||||
// instead we send a simple overflow/underflow histogram.
|
||||
bucketOptions.Options = &distribution.Distribution_BucketOptions_ExplicitBuckets{
|
||||
ExplicitBuckets: &distribution.Distribution_BucketOptions_Explicit{
|
||||
Bounds: []float64{0},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Exponential histogram
|
||||
growth := math.Exp2(math.Exp2(-float64(hist.Scale)))
|
||||
scale := math.Pow(growth, float64(hist.PositiveBucket.Offset))
|
||||
bucketOptions.Options = &distribution.Distribution_BucketOptions_ExponentialBuckets{
|
||||
ExponentialBuckets: &distribution.Distribution_BucketOptions_Exponential{
|
||||
GrowthFactor: growth,
|
||||
Scale: scale,
|
||||
NumFiniteBuckets: int32(len(counts) - 2),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var mean float64
|
||||
if !math.IsNaN(float64(hist.Sum)) && hist.Count > 0 { // Avoid divide-by-zero
|
||||
mean = float64(hist.Sum) / float64(hist.Count)
|
||||
}
|
||||
|
||||
return &distribution.Distribution{
|
||||
Count: int64(hist.Count),
|
||||
Mean: mean,
|
||||
BucketCounts: counts,
|
||||
BucketOptions: bucketOptions,
|
||||
Exemplars: toDistributionExemplar[N](hist.Exemplars, projectID),
|
||||
}
|
||||
}
|
||||
|
||||
func toDistributionExemplar[N int64 | float64](Exemplars []metricdata.Exemplar[N], projectID string) []*distribution.Distribution_Exemplar {
|
||||
var exemplars []*distribution.Distribution_Exemplar
|
||||
for _, e := range Exemplars {
|
||||
attachments := []*anypb.Any{}
|
||||
if hasValidSpanContext(e) {
|
||||
sctx, err := anypb.New(&monitoringpb.SpanContext{
|
||||
SpanName: fmt.Sprintf("projects/%s/traces/%s/spans/%s", projectID, hex.EncodeToString(e.TraceID[:]), hex.EncodeToString(e.SpanID[:])),
|
||||
})
|
||||
if err == nil {
|
||||
attachments = append(attachments, sctx)
|
||||
}
|
||||
}
|
||||
if len(e.FilteredAttributes) > 0 {
|
||||
attr, err := anypb.New(&monitoringpb.DroppedLabels{
|
||||
Label: attributesToLabels(e.FilteredAttributes),
|
||||
})
|
||||
if err == nil {
|
||||
attachments = append(attachments, attr)
|
||||
}
|
||||
}
|
||||
exemplars = append(exemplars, &distribution.Distribution_Exemplar{
|
||||
Value: float64(e.Value),
|
||||
Timestamp: timestamppb.New(e.Time),
|
||||
Attachments: attachments,
|
||||
})
|
||||
}
|
||||
sort.Slice(exemplars, func(i, j int) bool {
|
||||
return exemplars[i].Value < exemplars[j].Value
|
||||
})
|
||||
return exemplars
|
||||
}
|
||||
|
||||
func attributesToLabels(attrs []attribute.KeyValue) map[string]string {
|
||||
labels := make(map[string]string, len(attrs))
|
||||
for _, attr := range attrs {
|
||||
labels[normalizeLabelKey(string(attr.Key))] = sanitizeUTF8(attr.Value.Emit())
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
var (
|
||||
nilTraceID trace.TraceID
|
||||
nilSpanID trace.SpanID
|
||||
)
|
||||
|
||||
func hasValidSpanContext[N int64 | float64](e metricdata.Exemplar[N]) bool {
|
||||
return !bytes.Equal(e.TraceID[:], nilTraceID[:]) && !bytes.Equal(e.SpanID[:], nilSpanID[:])
|
||||
}
|
||||
|
||||
func setSumOfSquaredDeviation[N int64 | float64](hist metricdata.HistogramDataPoint[N], dist *distribution.Distribution) {
|
||||
var prevBound float64
|
||||
// Calculate the sum of squared deviation.
|
||||
for i := 0; i < len(hist.Bounds); i++ {
|
||||
// Assume all points in the bucket occur at the middle of the bucket range
|
||||
middleOfBucket := (prevBound + hist.Bounds[i]) / 2
|
||||
dist.SumOfSquaredDeviation += float64(dist.BucketCounts[i]) * (middleOfBucket - dist.Mean) * (middleOfBucket - dist.Mean)
|
||||
prevBound = hist.Bounds[i]
|
||||
}
|
||||
// The infinity bucket is an implicit +Inf bound after the list of explicit bounds.
|
||||
// Assume points in the infinity bucket are at the top of the previous bucket
|
||||
middleOfInfBucket := prevBound
|
||||
if len(dist.BucketCounts) > 0 {
|
||||
dist.SumOfSquaredDeviation += float64(dist.BucketCounts[len(dist.BucketCounts)-1]) * (middleOfInfBucket - dist.Mean) * (middleOfInfBucket - dist.Mean)
|
||||
}
|
||||
}
|
||||
|
||||
func numberDataPointToValue[N int64 | float64](
|
||||
point metricdata.DataPoint[N],
|
||||
) (*monitoringpb.TypedValue, googlemetricpb.MetricDescriptor_ValueType) {
|
||||
switch v := any(point.Value).(type) {
|
||||
case int64:
|
||||
return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_Int64Value{
|
||||
Int64Value: v,
|
||||
}},
|
||||
googlemetricpb.MetricDescriptor_INT64
|
||||
case float64:
|
||||
return &monitoringpb.TypedValue{Value: &monitoringpb.TypedValue_DoubleValue{
|
||||
DoubleValue: v,
|
||||
}},
|
||||
googlemetricpb.MetricDescriptor_DOUBLE
|
||||
}
|
||||
// It is impossible to reach this statement
|
||||
return nil, googlemetricpb.MetricDescriptor_INT64
|
||||
}
|
||||
|
||||
// https://github.com/googleapis/googleapis/blob/c4c562f89acce603fb189679836712d08c7f8584/google/api/metric.proto#L149
|
||||
//
|
||||
// > The label key name must follow:
|
||||
// >
|
||||
// > * Only upper and lower-case letters, digits and underscores (_) are
|
||||
// > allowed.
|
||||
// > * Label name must start with a letter or digit.
|
||||
// > * The maximum length of a label name is 100 characters.
|
||||
//
|
||||
// Note: this does not truncate if a label is too long.
|
||||
func normalizeLabelKey(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
s = strings.Map(sanitizeRune, s)
|
||||
if unicode.IsDigit(rune(s[0])) {
|
||||
s = "key_" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// converts anything that is not a letter or digit to an underscore.
|
||||
func sanitizeRune(r rune) rune {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
||||
return r
|
||||
}
|
||||
// Everything else turns into an underscore
|
||||
return '_'
|
||||
}
|
||||
Generated
Vendored
+215
@@ -0,0 +1,215 @@
|
||||
// Copyright 2020-2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/metric/metricdata"
|
||||
|
||||
monitoring "cloud.google.com/go/monitoring/apiv3/v2"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
apioption "google.golang.org/api/option"
|
||||
)
|
||||
|
||||
var userAgent = fmt.Sprintf("opentelemetry-go %s; google-cloud-metric-exporter %s", otel.Version(), Version())
|
||||
|
||||
// MonitoredResourceDescription is the struct which holds information required to map OTel resource to specific
|
||||
// Google Cloud MonitoredResource.
|
||||
type MonitoredResourceDescription struct {
|
||||
mrLabels map[string]struct{}
|
||||
mrType string
|
||||
}
|
||||
|
||||
// Option is function type that is passed to the exporter initialization function.
|
||||
type Option func(*options)
|
||||
|
||||
// options is the struct to hold options for metricExporter and its client instance.
|
||||
type options struct {
|
||||
// context allows you to provide a custom context for API calls.
|
||||
//
|
||||
// This context will be used several times: first, to create Cloud Monitoring
|
||||
// clients, and then every time a new batch of metrics needs to be uploaded.
|
||||
//
|
||||
// If unset, context.Background() will be used.
|
||||
context context.Context
|
||||
// metricDescriptorTypeFormatter is the custom formtter for the MetricDescriptor.Type.
|
||||
// By default, the format string is "workload.googleapis.com/[metric name]".
|
||||
metricDescriptorTypeFormatter func(metricdata.Metrics) string
|
||||
// resourceAttributeFilter determinies which resource attributes to
|
||||
// add to metrics as metric labels. By default, it adds service.name,
|
||||
// service.namespace, and service.instance.id.
|
||||
resourceAttributeFilter attribute.Filter
|
||||
// monitoredResourceDescription sets whether to attempt mapping the OTel Resource to a specific
|
||||
// Google Cloud Monitored Resource. When provided, the exporter attempts to map only to the provided
|
||||
// monitored resource type.
|
||||
monitoredResourceDescription MonitoredResourceDescription
|
||||
// projectID is the identifier of the Cloud Monitoring
|
||||
// project the user is uploading the stats data to.
|
||||
// If not set, this will default to your "Application Default Credentials".
|
||||
// For details see: https://developers.google.com/accounts/docs/application-default-credentials.
|
||||
//
|
||||
// It will be used in the project_id label of a Google Cloud Monitoring monitored
|
||||
// resource if the resource does not inherently belong to a specific
|
||||
// project, e.g. on-premise resource like k8s_container or generic_task.
|
||||
projectID string
|
||||
// compression enables gzip compression on gRPC calls.
|
||||
compression string
|
||||
// monitoringClient is used as the default client when not nil. If
|
||||
// monitoringClient is nil, a client is created instead.
|
||||
monitoringClient *monitoring.MetricClient
|
||||
// monitoringClientOptions are additional options to be passed
|
||||
// to the underlying Cloud Monitoring API client.
|
||||
// Optional.
|
||||
monitoringClientOptions []apioption.ClientOption
|
||||
// destinationProjectQuota sets whether the request should use quota from
|
||||
// the destination project for the request.
|
||||
destinationProjectQuota bool
|
||||
|
||||
// disableCreateMetricDescriptors disables automatic MetricDescriptor creation
|
||||
disableCreateMetricDescriptors bool
|
||||
|
||||
// enableSumOfSquaredDeviation enables calculation of an estimated sum of squared
|
||||
// deviation. It isn't correct, so we don't send it by default.
|
||||
enableSumOfSquaredDeviation bool
|
||||
|
||||
// createServiceTimeSeries sets whether to create timeseries using `CreateServiceTimeSeries`.
|
||||
// Implicitly, this sets `disableCreateMetricDescriptors` to true.
|
||||
createServiceTimeSeries bool
|
||||
}
|
||||
|
||||
// WithProjectID sets Google Cloud Platform project as projectID.
|
||||
// Without using this option, it automatically detects the project ID
|
||||
// from the default credential detection process.
|
||||
// Please find the detailed order of the default credential detection process on the doc:
|
||||
// https://godoc.org/golang.org/x/oauth2/google#FindDefaultCredentials
|
||||
func WithProjectID(id string) func(o *options) {
|
||||
return func(o *options) {
|
||||
o.projectID = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithDestinationProjectQuota enables per-request usage of the destination
|
||||
// project's quota. For example, when setting gcp.project.id on a metric.
|
||||
func WithDestinationProjectQuota() func(o *options) {
|
||||
return func(o *options) {
|
||||
o.destinationProjectQuota = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitoringClient configures the client used by the exporter to write
|
||||
// metrics to Cloud Monitoring. This option is mutually exclusive with
|
||||
// WithMonitoringClientOptions. If both options are provided,
|
||||
// WithMonitoringClient is used and WithMonitoringClientOptions is ignored.
|
||||
func WithMonitoringClient(cl *monitoring.MetricClient) func(o *options) {
|
||||
return func(o *options) {
|
||||
o.monitoringClient = cl
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitoringClientOptions add the options for Cloud Monitoring client instance.
|
||||
// Available options are defined in.
|
||||
func WithMonitoringClientOptions(opts ...apioption.ClientOption) func(o *options) {
|
||||
return func(o *options) {
|
||||
o.monitoringClientOptions = append(o.monitoringClientOptions, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithMetricDescriptorTypeFormatter sets the custom formatter for MetricDescriptor.
|
||||
// Note that the format has to follow the convention defined in the official document.
|
||||
// The default is "workload.googleapis.com/[metric name]".
|
||||
// ref. https://cloud.google.com/monitoring/custom-metrics/creating-metrics#custom_metric_names
|
||||
func WithMetricDescriptorTypeFormatter(f func(metricdata.Metrics) string) func(o *options) {
|
||||
return func(o *options) {
|
||||
o.metricDescriptorTypeFormatter = f
|
||||
}
|
||||
}
|
||||
|
||||
// WithFilteredResourceAttributes determinies which resource attributes to
|
||||
// add to metrics as metric labels. By default, it adds service.name,
|
||||
// service.namespace, and service.instance.id. This is recommended to avoid
|
||||
// writing duplicate timeseries against the same monitored resource. Use
|
||||
// WithFilteredResourceAttributes(NoAttributes()) to disable the addition of
|
||||
// resource attributes to metric labels.
|
||||
func WithFilteredResourceAttributes(filter attribute.Filter) func(o *options) {
|
||||
return func(o *options) {
|
||||
o.resourceAttributeFilter = filter
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultResourceAttributesFilter is the default filter applied to resource
|
||||
// attributes.
|
||||
func DefaultResourceAttributesFilter(kv attribute.KeyValue) bool {
|
||||
return (kv.Key == semconv.ServiceNameKey ||
|
||||
kv.Key == semconv.ServiceNamespaceKey ||
|
||||
kv.Key == semconv.ServiceInstanceIDKey) && len(kv.Value.AsString()) > 0
|
||||
}
|
||||
|
||||
// NoAttributes can be passed to WithFilteredResourceAttributes to disable
|
||||
// adding resource attributes as metric labels.
|
||||
func NoAttributes(attribute.KeyValue) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// WithDisableCreateMetricDescriptors will disable the automatic creation of
|
||||
// MetricDescriptors when an unknown metric is set to be exported.
|
||||
func WithDisableCreateMetricDescriptors() func(o *options) {
|
||||
return func(o *options) {
|
||||
o.disableCreateMetricDescriptors = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithCompression sets the compression to use for gRPC requests.
|
||||
func WithCompression(c string) func(o *options) {
|
||||
return func(o *options) {
|
||||
o.compression = c
|
||||
}
|
||||
}
|
||||
|
||||
// WithSumOfSquaredDeviation sets the SumOfSquaredDeviation field on histograms.
|
||||
// It is an estimate, and is not the actual sum of squared deviations.
|
||||
func WithSumOfSquaredDeviation() func(o *options) {
|
||||
return func(o *options) {
|
||||
o.enableSumOfSquaredDeviation = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithCreateServiceTimeSeries configures the exporter to use `CreateServiceTimeSeries` for creating timeseries.
|
||||
// If this is used, metric descriptors are not exported.
|
||||
func WithCreateServiceTimeSeries() func(o *options) {
|
||||
return func(o *options) {
|
||||
o.createServiceTimeSeries = true
|
||||
o.disableCreateMetricDescriptors = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitoredResourceDescription configures the exporter to attempt to map the OpenTelemetry Resource to the provided
|
||||
// Google MonitoredResource. The provided mrLabels would be searched for in the OpenTelemetry Resource Attributes and if
|
||||
// found, would be included in the MonitoredResource labels.
|
||||
func WithMonitoredResourceDescription(mrType string, mrLabels []string) func(o *options) {
|
||||
return func(o *options) {
|
||||
mrLabelSet := make(map[string]struct{})
|
||||
for _, label := range mrLabels {
|
||||
mrLabelSet[label] = struct{}{}
|
||||
}
|
||||
o.monitoredResourceDescription = MonitoredResourceDescription{
|
||||
mrType: mrType,
|
||||
mrLabels: mrLabelSet,
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
// Version is the current release version of the OpenTelemetry
|
||||
// Operations Metric Exporter in use.
|
||||
func Version() string {
|
||||
return "0.55.0"
|
||||
}
|
||||
Generated
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Generated
Vendored
+285
@@ -0,0 +1,285 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package resourcemapping
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
|
||||
)
|
||||
|
||||
const (
|
||||
ProjectIDAttributeKey = "gcp.project.id"
|
||||
|
||||
awsAccount = "aws_account"
|
||||
awsEc2Instance = "aws_ec2_instance"
|
||||
clusterName = "cluster_name"
|
||||
containerName = "container_name"
|
||||
gceInstance = "gce_instance"
|
||||
genericNode = "generic_node"
|
||||
genericTask = "generic_task"
|
||||
instanceID = "instance_id"
|
||||
job = "job"
|
||||
k8sCluster = "k8s_cluster"
|
||||
k8sContainer = "k8s_container"
|
||||
k8sNode = "k8s_node"
|
||||
k8sPod = "k8s_pod"
|
||||
location = "location"
|
||||
namespace = "namespace"
|
||||
namespaceName = "namespace_name"
|
||||
nodeID = "node_id"
|
||||
nodeName = "node_name"
|
||||
podName = "pod_name"
|
||||
region = "region"
|
||||
taskID = "task_id"
|
||||
zone = "zone"
|
||||
gaeInstance = "gae_instance"
|
||||
gaeApp = "gae_app"
|
||||
gaeModuleID = "module_id"
|
||||
gaeVersionID = "version_id"
|
||||
cloudRunRevision = "cloud_run_revision"
|
||||
cloudFunction = "cloud_function"
|
||||
cloudFunctionName = "function_name"
|
||||
serviceName = "service_name"
|
||||
configurationName = "configuration_name"
|
||||
revisionName = "revision_name"
|
||||
bmsInstance = "baremetalsolution.googleapis.com/Instance"
|
||||
unknownServicePrefix = "unknown_service"
|
||||
)
|
||||
|
||||
var (
|
||||
// monitoredResourceMappings contains mappings of GCM resource label keys onto mapping config from OTel
|
||||
// resource for a given monitored resource type.
|
||||
monitoredResourceMappings = map[string]map[string]struct {
|
||||
// If none of the otelKeys are present in the Resource, fallback to this literal value
|
||||
fallbackLiteral string
|
||||
// OTel resource keys to try and populate the resource label from. For entries with
|
||||
// multiple OTel resource keys, the keys' values will be coalesced in order until there
|
||||
// is a non-empty value.
|
||||
otelKeys []string
|
||||
}{
|
||||
gceInstance: {
|
||||
zone: {otelKeys: []string{string(semconv.CloudAvailabilityZoneKey)}},
|
||||
instanceID: {otelKeys: []string{string(semconv.HostIDKey)}},
|
||||
},
|
||||
k8sContainer: {
|
||||
location: {otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
}},
|
||||
clusterName: {otelKeys: []string{string(semconv.K8SClusterNameKey)}},
|
||||
namespaceName: {otelKeys: []string{string(semconv.K8SNamespaceNameKey)}},
|
||||
podName: {otelKeys: []string{string(semconv.K8SPodNameKey)}},
|
||||
containerName: {otelKeys: []string{string(semconv.K8SContainerNameKey)}},
|
||||
},
|
||||
k8sPod: {
|
||||
location: {otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
}},
|
||||
clusterName: {otelKeys: []string{string(semconv.K8SClusterNameKey)}},
|
||||
namespaceName: {otelKeys: []string{string(semconv.K8SNamespaceNameKey)}},
|
||||
podName: {otelKeys: []string{string(semconv.K8SPodNameKey)}},
|
||||
},
|
||||
k8sNode: {
|
||||
location: {otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
}},
|
||||
clusterName: {otelKeys: []string{string(semconv.K8SClusterNameKey)}},
|
||||
nodeName: {otelKeys: []string{string(semconv.K8SNodeNameKey)}},
|
||||
},
|
||||
k8sCluster: {
|
||||
location: {otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
}},
|
||||
clusterName: {otelKeys: []string{string(semconv.K8SClusterNameKey)}},
|
||||
},
|
||||
gaeInstance: {
|
||||
location: {otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
}},
|
||||
gaeModuleID: {otelKeys: []string{string(semconv.FaaSNameKey)}},
|
||||
gaeVersionID: {otelKeys: []string{string(semconv.FaaSVersionKey)}},
|
||||
instanceID: {otelKeys: []string{string(semconv.FaaSInstanceKey)}},
|
||||
},
|
||||
gaeApp: {
|
||||
location: {otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
}},
|
||||
gaeModuleID: {otelKeys: []string{string(semconv.FaaSNameKey)}},
|
||||
gaeVersionID: {otelKeys: []string{string(semconv.FaaSVersionKey)}},
|
||||
},
|
||||
awsEc2Instance: {
|
||||
instanceID: {otelKeys: []string{string(semconv.HostIDKey)}},
|
||||
region: {
|
||||
otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
},
|
||||
},
|
||||
awsAccount: {otelKeys: []string{string(semconv.CloudAccountIDKey)}},
|
||||
},
|
||||
bmsInstance: {
|
||||
location: {otelKeys: []string{string(semconv.CloudRegionKey)}},
|
||||
instanceID: {otelKeys: []string{string(semconv.HostIDKey)}},
|
||||
},
|
||||
genericTask: {
|
||||
location: {
|
||||
otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
},
|
||||
fallbackLiteral: "global",
|
||||
},
|
||||
namespace: {otelKeys: []string{string(semconv.ServiceNamespaceKey)}},
|
||||
job: {otelKeys: []string{string(semconv.ServiceNameKey), string(semconv.FaaSNameKey)}},
|
||||
taskID: {otelKeys: []string{string(semconv.ServiceInstanceIDKey), string(semconv.FaaSInstanceKey)}},
|
||||
},
|
||||
genericNode: {
|
||||
location: {
|
||||
otelKeys: []string{
|
||||
string(semconv.CloudAvailabilityZoneKey),
|
||||
string(semconv.CloudRegionKey),
|
||||
},
|
||||
fallbackLiteral: "global",
|
||||
},
|
||||
namespace: {otelKeys: []string{string(semconv.ServiceNamespaceKey)}},
|
||||
nodeID: {otelKeys: []string{string(semconv.HostIDKey), string(semconv.HostNameKey)}},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// ReadOnlyAttributes is an interface to abstract between pulling attributes from PData library or OTEL SDK.
|
||||
type ReadOnlyAttributes interface {
|
||||
GetString(string) (string, bool)
|
||||
}
|
||||
|
||||
// ResourceAttributesToLoggingMonitoredResource converts from a set of OTEL resource attributes into a
|
||||
// GCP monitored resource type and label set for Cloud Logging.
|
||||
// E.g.
|
||||
// This may output `gce_instance` type with appropriate labels.
|
||||
func ResourceAttributesToLoggingMonitoredResource(attrs ReadOnlyAttributes) *monitoredrespb.MonitoredResource {
|
||||
cloudPlatform, _ := attrs.GetString(string(semconv.CloudPlatformKey))
|
||||
switch cloudPlatform {
|
||||
case semconv.CloudPlatformGCPAppEngine.Value.AsString():
|
||||
return createMonitoredResource(gaeApp, attrs)
|
||||
default:
|
||||
return commonResourceAttributesToMonitoredResource(cloudPlatform, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceAttributesToMonitoringMonitoredResource converts from a set of OTEL resource attributes into a
|
||||
// GCP monitored resource type and label set for Cloud Monitoring
|
||||
// E.g.
|
||||
// This may output `gce_instance` type with appropriate labels.
|
||||
func ResourceAttributesToMonitoringMonitoredResource(attrs ReadOnlyAttributes) *monitoredrespb.MonitoredResource {
|
||||
cloudPlatform, _ := attrs.GetString(string(semconv.CloudPlatformKey))
|
||||
switch cloudPlatform {
|
||||
case semconv.CloudPlatformGCPAppEngine.Value.AsString():
|
||||
return createMonitoredResource(gaeInstance, attrs)
|
||||
default:
|
||||
return commonResourceAttributesToMonitoredResource(cloudPlatform, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func commonResourceAttributesToMonitoredResource(cloudPlatform string, attrs ReadOnlyAttributes) *monitoredrespb.MonitoredResource {
|
||||
switch cloudPlatform {
|
||||
case semconv.CloudPlatformGCPComputeEngine.Value.AsString():
|
||||
return createMonitoredResource(gceInstance, attrs)
|
||||
case semconv.CloudPlatformAWSEC2.Value.AsString():
|
||||
return createMonitoredResource(awsEc2Instance, attrs)
|
||||
// TODO(alex-basinov): replace this string literal with semconv.CloudPlatformGCPBareMetalSolution
|
||||
// once https://github.com/open-telemetry/semantic-conventions/pull/64 makes its way
|
||||
// into the semconv module.
|
||||
case "gcp_bare_metal_solution":
|
||||
return createMonitoredResource(bmsInstance, attrs)
|
||||
default:
|
||||
// if k8s.cluster.name is set, pattern match for various k8s resources.
|
||||
// this will also match non-cloud k8s platforms like minikube.
|
||||
if _, ok := attrs.GetString(string(semconv.K8SClusterNameKey)); ok {
|
||||
// Try for most to least specific k8s_container, k8s_pod, etc
|
||||
if _, ok := attrs.GetString(string(semconv.K8SContainerNameKey)); ok {
|
||||
return createMonitoredResource(k8sContainer, attrs)
|
||||
} else if _, ok := attrs.GetString(string(semconv.K8SPodNameKey)); ok {
|
||||
return createMonitoredResource(k8sPod, attrs)
|
||||
} else if _, ok := attrs.GetString(string(semconv.K8SNodeNameKey)); ok {
|
||||
return createMonitoredResource(k8sNode, attrs)
|
||||
}
|
||||
return createMonitoredResource(k8sCluster, attrs)
|
||||
}
|
||||
|
||||
// Fallback to generic_task
|
||||
_, hasServiceName := attrs.GetString(string(semconv.ServiceNameKey))
|
||||
_, hasFaaSName := attrs.GetString(string(semconv.FaaSNameKey))
|
||||
_, hasServiceInstanceID := attrs.GetString(string(semconv.ServiceInstanceIDKey))
|
||||
_, hasFaaSInstance := attrs.GetString(string(semconv.FaaSInstanceKey))
|
||||
if (hasServiceName && hasServiceInstanceID) || (hasFaaSInstance && hasFaaSName) {
|
||||
return createMonitoredResource(genericTask, attrs)
|
||||
}
|
||||
|
||||
// Everything else fallback to generic_node
|
||||
return createMonitoredResource(genericNode, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
func createMonitoredResource(
|
||||
monitoredResourceType string,
|
||||
resourceAttrs ReadOnlyAttributes,
|
||||
) *monitoredrespb.MonitoredResource {
|
||||
mappings := monitoredResourceMappings[monitoredResourceType]
|
||||
mrLabels := make(map[string]string, len(mappings))
|
||||
|
||||
for mrKey, mappingConfig := range mappings {
|
||||
mrValue := ""
|
||||
ok := false
|
||||
// Coalesce the possible keys in order
|
||||
for _, otelKey := range mappingConfig.otelKeys {
|
||||
mrValue, ok = resourceAttrs.GetString(otelKey)
|
||||
if mrValue != "" && !strings.HasPrefix(mrValue, unknownServicePrefix) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if mrValue == "" && contains(mappingConfig.otelKeys, string(semconv.ServiceNameKey)) {
|
||||
// the service name started with unknown_service, and was ignored above
|
||||
mrValue, ok = resourceAttrs.GetString(string(semconv.ServiceNameKey))
|
||||
}
|
||||
if !ok || mrValue == "" {
|
||||
mrValue = mappingConfig.fallbackLiteral
|
||||
}
|
||||
mrLabels[mrKey] = sanitizeUTF8(mrValue)
|
||||
}
|
||||
return &monitoredrespb.MonitoredResource{
|
||||
Type: monitoredResourceType,
|
||||
Labels: mrLabels,
|
||||
}
|
||||
}
|
||||
|
||||
func contains(list []string, element string) bool {
|
||||
for _, item := range list {
|
||||
if item == element {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func sanitizeUTF8(s string) string {
|
||||
return strings.ToValidUTF8(s, "�")
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofumpt
|
||||
settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/prometheus/otlptranslator)
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
linters:
|
||||
# Keep this list sorted alphabetically
|
||||
enable:
|
||||
- depguard
|
||||
- errorlint
|
||||
- exptostd
|
||||
- gocritic
|
||||
- godot
|
||||
- loggercheck
|
||||
- misspell
|
||||
- nilnesserr
|
||||
# TODO: Enable once https://github.com/golangci/golangci-lint/issues/3228 is fixed.
|
||||
# - nolintlint
|
||||
- perfsprint
|
||||
- predeclared
|
||||
- revive
|
||||
- sloglint
|
||||
- testifylint
|
||||
- unconvert
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- whitespace
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: sync/atomic
|
||||
desc: Use go.uber.org/atomic instead of sync/atomic
|
||||
- pkg: github.com/stretchr/testify/assert
|
||||
desc: Use github.com/stretchr/testify/require instead of github.com/stretchr/testify/assert
|
||||
- pkg: io/ioutil
|
||||
desc: Use corresponding 'os' or 'io' functions instead.
|
||||
- pkg: github.com/pkg/errors
|
||||
desc: Use 'errors' or 'fmt' instead of github.com/pkg/errors
|
||||
- pkg: golang.org/x/exp/slices
|
||||
desc: Use 'slices' instead.
|
||||
perfsprint:
|
||||
# Optimizes `fmt.Errorf`.
|
||||
errorf: true
|
||||
revive:
|
||||
# By default, revive will enable only the linting rules that are named in the configuration file.
|
||||
# So, it's needed to explicitly enable all required rules here.
|
||||
rules:
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
- name: blank-imports
|
||||
- name: comment-spacings
|
||||
- name: context-as-argument
|
||||
arguments:
|
||||
# Allow functions with test or bench signatures.
|
||||
- allowTypesBefore: '*testing.T,testing.TB'
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: early-return
|
||||
arguments:
|
||||
- preserveScope
|
||||
# A lot of false positives: incorrectly identifies channel draining as "empty code block".
|
||||
# See https://github.com/mgechev/revive/issues/386
|
||||
- name: empty-block
|
||||
disabled: true
|
||||
- name: error-naming
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: errorf
|
||||
- name: exported
|
||||
- name: increment-decrement
|
||||
- name: indent-error-flow
|
||||
arguments:
|
||||
- preserveScope
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: redefines-builtin-id
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- preserveScope
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: unreachable-code
|
||||
- name: unused-parameter
|
||||
- name: var-declaration
|
||||
- name: var-naming
|
||||
testifylint:
|
||||
disable:
|
||||
- float-compare
|
||||
- go-require
|
||||
enable-all: true
|
||||
run:
|
||||
timeout: 15m
|
||||
version: "2"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Prometheus Community Code of Conduct
|
||||
|
||||
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
* Arthur Silva Sens (arthursens2005@gmail.com / @ArthurSens)
|
||||
* Arve Knudsen (arve.knudsen@gmail.com / @aknuds1)
|
||||
* Jesús Vázquez (jesus.vazquez@grafana.com / @jesusvazquez)
|
||||
* Owen Williams (owen.williams@grafana.com / @ywwg)
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# OTLP Prometheus Translator
|
||||
|
||||
A Go library for converting [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/docs/specs/otlp/) metric and attribute names to [Prometheus](https://prometheus.io/)-compliant formats. This is an internal library for both Prometheus and Open Telemetry, without any stability guarantees for external usage.
|
||||
|
||||
Part of the [Prometheus](https://prometheus.io/) ecosystem, following the [OpenTelemetry to Prometheus compatibility specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/compatibility/prometheus_and_openmetrics.md).
|
||||
|
||||
## Features
|
||||
|
||||
- **Metric Name and Label Translation**: Convert OTLP metric names and attributes to Prometheus-compliant format
|
||||
- **Unit Handling**: Translate OTLP units to Prometheus unit conventions
|
||||
- **Type-Aware Suffixes**: Optionally append `_total`, `_ratio` based on metric type
|
||||
- **Namespace Support**: Add configurable namespace prefixes
|
||||
- **UTF-8 Support**: Choose between Prometheus legacy scheme compliant metric/label names (`[a-zA-Z0-9:_]`) or untranslated metric/label names
|
||||
- **Translation Strategy Configuration**: Select a translation strategy with a standard set of strings.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/prometheus/otlptranslator
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus/otlptranslator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a metric namer using traditional Prometheus name translation, with suffixes added and UTF-8 disallowed.
|
||||
strategy := otlptranslator.UnderscoreEscapingWithSuffixes
|
||||
namer := otlptranslator.NewMetricNamer("myapp", strategy)
|
||||
|
||||
// Translate OTLP metric to Prometheus format
|
||||
metric := otlptranslator.Metric{
|
||||
Name: "http.server.request.duration",
|
||||
Unit: "s",
|
||||
Type: otlptranslator.MetricTypeHistogram,
|
||||
}
|
||||
fmt.Println(namer.Build(metric)) // Output: myapp_http_server_request_duration_seconds
|
||||
|
||||
// Translate label names
|
||||
labelNamer := otlptranslator.LabelNamer{UTF8Allowed: false}
|
||||
fmt.Println(labelNamer.Build("http.method")) // Output: http_method
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Metric Name Translation
|
||||
|
||||
```go
|
||||
namer := otlptranslator.MetricNamer{WithMetricSuffixes: true, UTF8Allowed: false}
|
||||
|
||||
// Counter gets _total suffix
|
||||
counter := otlptranslator.Metric{
|
||||
Name: "requests.count", Unit: "1", Type: otlptranslator.MetricTypeMonotonicCounter,
|
||||
}
|
||||
fmt.Println(namer.Build(counter)) // requests_count_total
|
||||
|
||||
// Gauge with unit conversion
|
||||
gauge := otlptranslator.Metric{
|
||||
Name: "memory.usage", Unit: "By", Type: otlptranslator.MetricTypeGauge,
|
||||
}
|
||||
fmt.Println(namer.Build(gauge)) // memory_usage_bytes
|
||||
|
||||
// Dimensionless gauge gets _ratio suffix
|
||||
ratio := otlptranslator.Metric{
|
||||
Name: "cpu.utilization", Unit: "1", Type: otlptranslator.MetricTypeGauge,
|
||||
}
|
||||
fmt.Println(namer.Build(ratio)) // cpu_utilization_ratio
|
||||
```
|
||||
|
||||
### Label Translation
|
||||
|
||||
```go
|
||||
labelNamer := otlptranslator.LabelNamer{UTF8Allowed: false}
|
||||
|
||||
labelNamer.Build("http.method") // http_method
|
||||
labelNamer.Build("123invalid") // key_123invalid
|
||||
labelNamer.Build("_private") // key_private
|
||||
labelNamer.Build("__reserved__") // __reserved__ (preserved)
|
||||
labelNamer.Build("label@with$symbols") // label_with_symbols
|
||||
```
|
||||
|
||||
### Unit Translation
|
||||
|
||||
```go
|
||||
unitNamer := otlptranslator.UnitNamer{UTF8Allowed: false}
|
||||
|
||||
unitNamer.Build("s") // seconds
|
||||
unitNamer.Build("By") // bytes
|
||||
unitNamer.Build("requests/s") // requests_per_second
|
||||
unitNamer.Build("1") // "" (dimensionless)
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
```go
|
||||
// Prometheus-compliant mode - supports [a-zA-Z0-9:_]
|
||||
compliantNamer := otlptranslator.MetricNamer{UTF8Allowed: false, WithMetricSuffixes: true}
|
||||
|
||||
// Transparent pass-through mode, aka "NoTranslation"
|
||||
utf8Namer := otlptranslator.MetricNamer{UTF8Allowed: true, WithMetricSuffixes: false}
|
||||
utf8Namer = otlptranslator.NewMetricNamer("", otlpTranslator.NoTranslation)
|
||||
|
||||
// With namespace and suffixes
|
||||
productionNamer := otlptranslator.MetricNamer{
|
||||
Namespace: "myservice",
|
||||
WithMetricSuffixes: true,
|
||||
UTF8Allowed: false,
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Reporting a security issue
|
||||
|
||||
The Prometheus security policy, including how to report vulnerabilities, can be
|
||||
found here:
|
||||
|
||||
<https://prometheus.io/docs/operating/security/>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package otlptranslator
|
||||
|
||||
const (
|
||||
// ExemplarTraceIDKey is the key used to store the trace ID in Prometheus
|
||||
// exemplars:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#exemplars
|
||||
ExemplarTraceIDKey = "trace_id"
|
||||
// ExemplarSpanIDKey is the key used to store the Span ID in Prometheus
|
||||
// exemplars:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#exemplars
|
||||
ExemplarSpanIDKey = "span_id"
|
||||
// ScopeNameLabelKey is the name of the label key used to identify the name
|
||||
// of the OpenTelemetry scope which produced the metric:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope
|
||||
ScopeNameLabelKey = "otel_scope_name"
|
||||
// ScopeVersionLabelKey is the name of the label key used to identify the
|
||||
// version of the OpenTelemetry scope which produced the metric:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope
|
||||
ScopeVersionLabelKey = "otel_scope_version"
|
||||
// TargetInfoMetricName is the name of the metric used to preserve resource
|
||||
// attributes in Prometheus format:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#resource-attributes-1
|
||||
// It originates from OpenMetrics:
|
||||
// https://github.com/OpenObservability/OpenMetrics/blob/1386544931307dff279688f332890c31b6c5de36/specification/OpenMetrics.md#supporting-target-metadata-in-both-push-based-and-pull-based-systems
|
||||
TargetInfoMetricName = "target_info"
|
||||
)
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package otlptranslator provides utilities for converting OpenTelemetry Protocol (OTLP)
|
||||
// metric and attribute names to Prometheus-compliant formats.
|
||||
//
|
||||
// This package is designed to help users translate OpenTelemetry metrics to Prometheus
|
||||
// metrics while following the official OpenTelemetry to Prometheus compatibility specification.
|
||||
//
|
||||
// Main components:
|
||||
// - MetricNamer: Translates OTLP metric names to Prometheus metric names
|
||||
// - LabelNamer: Translates OTLP attribute names to Prometheus label names
|
||||
// - UnitNamer: Translates OTLP units to Prometheus unit conventions
|
||||
package otlptranslator
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// Provenance-includes-location: https://github.com/prometheus/prometheus/blob/93e991ef7ed19cc997a9360c8016cac3767b8057/storage/remote/otlptranslator/prometheus/normalize_label.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: Copyright The Prometheus Authors
|
||||
// Provenance-includes-location: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/95e8f8fdc2a9dc87230406c9a3cf02be4fd68bea/pkg/translator/prometheus/normalize_label.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: Copyright The OpenTelemetry Authors.
|
||||
|
||||
package otlptranslator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// LabelNamer is a helper struct to build label names.
|
||||
// It translates OpenTelemetry Protocol (OTLP) attribute names to Prometheus-compliant label names.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// namer := LabelNamer{UTF8Allowed: false}
|
||||
// result := namer.Build("http.method") // "http_method"
|
||||
type LabelNamer struct {
|
||||
UTF8Allowed bool
|
||||
// UnderscoreLabelSanitization, if true, enabled prepending 'key' to labels
|
||||
// starting with '_'. Reserved labels starting with `__` are not modified.
|
||||
//
|
||||
// Deprecated: This will be removed in a future version of otlptranslator.
|
||||
UnderscoreLabelSanitization bool
|
||||
// PreserveMultipleUnderscores enables preserving of multiple
|
||||
// consecutive underscores in label names when UTF8Allowed is false.
|
||||
// This option is discouraged as it violates the OpenTelemetry to Prometheus
|
||||
// specification https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus),
|
||||
// but may be needed for compatibility with legacy systems that rely on the old behavior.
|
||||
PreserveMultipleUnderscores bool
|
||||
}
|
||||
|
||||
// Build normalizes the specified label to follow Prometheus label names standard.
|
||||
//
|
||||
// Translation rules:
|
||||
// - Replaces invalid characters with underscores
|
||||
// - Prefixes labels with invalid start characters (numbers or `_`) with "key"
|
||||
// - Preserves double underscore labels (reserved names)
|
||||
// - If UTF8Allowed is true, returns label as-is
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// namer := LabelNamer{UTF8Allowed: false}
|
||||
// namer.Build("http.method") // "http_method"
|
||||
// namer.Build("123invalid") // "key_123invalid"
|
||||
// namer.Build("__reserved__") // "__reserved__" (preserved)
|
||||
func (ln *LabelNamer) Build(label string) (string, error) {
|
||||
if len(label) == 0 {
|
||||
return "", errors.New("label name is empty")
|
||||
}
|
||||
|
||||
if ln.UTF8Allowed {
|
||||
if hasUnderscoresOnly(label) {
|
||||
return "", fmt.Errorf("label name %q contains only underscores", label)
|
||||
}
|
||||
return label, nil
|
||||
}
|
||||
|
||||
normalizedName := sanitizeLabelName(label, ln.PreserveMultipleUnderscores)
|
||||
|
||||
// If label starts with a number, prepend with "key_".
|
||||
if unicode.IsDigit(rune(normalizedName[0])) {
|
||||
normalizedName = "key_" + normalizedName
|
||||
} else if ln.UnderscoreLabelSanitization && strings.HasPrefix(normalizedName, "_") && !strings.HasPrefix(normalizedName, "__") {
|
||||
normalizedName = "key" + normalizedName
|
||||
}
|
||||
|
||||
if hasUnderscoresOnly(normalizedName) {
|
||||
return "", fmt.Errorf("normalization for label name %q resulted in invalid name %q", label, normalizedName)
|
||||
}
|
||||
|
||||
return normalizedName, nil
|
||||
}
|
||||
|
||||
func hasUnderscoresOnly(label string) bool {
|
||||
for _, c := range label {
|
||||
if c != '_' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// Provenance-includes-location: https://github.com/prometheus/prometheus/blob/93e991ef7ed19cc997a9360c8016cac3767b8057/storage/remote/otlptranslator/prometheus/metric_name_builder.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: Copyright The Prometheus Authors
|
||||
// Provenance-includes-location: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/95e8f8fdc2a9dc87230406c9a3cf02be4fd68bea/pkg/translator/prometheus/normalize_name.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: Copyright The OpenTelemetry Authors.
|
||||
|
||||
package otlptranslator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// The map to translate OTLP units to Prometheus units
|
||||
// OTLP metrics use the c/s notation as specified at https://ucum.org/ucum.html
|
||||
// (See also https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/README.md#instrument-units)
|
||||
// Prometheus best practices for units: https://prometheus.io/docs/practices/naming/#base-units
|
||||
// OpenMetrics specification for units: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#units-and-base-units
|
||||
var unitMap = map[string]string{
|
||||
// Time
|
||||
"d": "days",
|
||||
"h": "hours",
|
||||
"min": "minutes",
|
||||
"s": "seconds",
|
||||
"ms": "milliseconds",
|
||||
"us": "microseconds",
|
||||
"ns": "nanoseconds",
|
||||
|
||||
// Bytes
|
||||
"By": "bytes",
|
||||
"KiBy": "kibibytes",
|
||||
"MiBy": "mebibytes",
|
||||
"GiBy": "gibibytes",
|
||||
"TiBy": "tibibytes",
|
||||
"KBy": "kilobytes",
|
||||
"MBy": "megabytes",
|
||||
"GBy": "gigabytes",
|
||||
"TBy": "terabytes",
|
||||
|
||||
// SI
|
||||
"m": "meters",
|
||||
"V": "volts",
|
||||
"A": "amperes",
|
||||
"J": "joules",
|
||||
"W": "watts",
|
||||
"g": "grams",
|
||||
|
||||
// Misc
|
||||
"Cel": "celsius",
|
||||
"Hz": "hertz",
|
||||
"1": "",
|
||||
"%": "percent",
|
||||
}
|
||||
|
||||
// The map that translates the "per" unit.
|
||||
// Example: s => per second (singular).
|
||||
var perUnitMap = map[string]string{
|
||||
"s": "second",
|
||||
"m": "minute",
|
||||
"h": "hour",
|
||||
"d": "day",
|
||||
"w": "week",
|
||||
"mo": "month",
|
||||
"y": "year",
|
||||
}
|
||||
|
||||
// MetricNamer is a helper struct to build metric names.
|
||||
// It converts OpenTelemetry Protocol (OTLP) metric names to Prometheus-compliant metric names.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// namer := MetricNamer{
|
||||
// WithMetricSuffixes: true,
|
||||
// UTF8Allowed: false,
|
||||
// }
|
||||
//
|
||||
// metric := Metric{
|
||||
// Name: "http.server.duration",
|
||||
// Unit: "s",
|
||||
// Type: MetricTypeHistogram,
|
||||
// }
|
||||
//
|
||||
// result := namer.Build(metric) // "http_server_duration_seconds"
|
||||
type MetricNamer struct {
|
||||
Namespace string
|
||||
WithMetricSuffixes bool
|
||||
UTF8Allowed bool
|
||||
}
|
||||
|
||||
// NewMetricNamer creates a MetricNamer with the specified namespace (can be
|
||||
// blank) and the requested Translation Strategy.
|
||||
func NewMetricNamer(namespace string, strategy TranslationStrategyOption) MetricNamer {
|
||||
return MetricNamer{
|
||||
Namespace: namespace,
|
||||
WithMetricSuffixes: strategy.ShouldAddSuffixes(),
|
||||
UTF8Allowed: !strategy.ShouldEscape(),
|
||||
}
|
||||
}
|
||||
|
||||
// Metric is a helper struct that holds information about a metric.
|
||||
// It represents an OpenTelemetry metric with its name, unit, and type.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// metric := Metric{
|
||||
// Name: "http.server.request.duration",
|
||||
// Unit: "s",
|
||||
// Type: MetricTypeHistogram,
|
||||
// }
|
||||
type Metric struct {
|
||||
Name string
|
||||
Unit string
|
||||
Type MetricType
|
||||
}
|
||||
|
||||
// Build builds a metric name for the specified metric.
|
||||
//
|
||||
// The method applies different transformations based on the MetricNamer configuration:
|
||||
// - If UTF8Allowed is true, doesn't translate names - all characters must be valid UTF-8, however.
|
||||
// - If UTF8Allowed is false, translates metric names to comply with legacy Prometheus name scheme by escaping invalid characters to `_`.
|
||||
// - If WithMetricSuffixes is true, adds appropriate suffixes based on type and unit.
|
||||
//
|
||||
// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// namer := MetricNamer{WithMetricSuffixes: true, UTF8Allowed: false}
|
||||
//
|
||||
// // Counter gets _total suffix
|
||||
// counter := Metric{Name: "requests.count", Unit: "1", Type: MetricTypeMonotonicCounter}
|
||||
// result := namer.Build(counter) // "requests_count_total"
|
||||
//
|
||||
// // Gauge with unit suffix
|
||||
// gauge := Metric{Name: "memory.usage", Unit: "By", Type: MetricTypeGauge}
|
||||
// result = namer.Build(gauge) // "memory_usage_bytes"
|
||||
func (mn *MetricNamer) Build(metric Metric) (string, error) {
|
||||
if mn.UTF8Allowed {
|
||||
return mn.buildMetricName(metric.Name, metric.Unit, metric.Type)
|
||||
}
|
||||
return mn.buildCompliantMetricName(metric.Name, metric.Unit, metric.Type)
|
||||
}
|
||||
|
||||
func (mn *MetricNamer) buildCompliantMetricName(name, unit string, metricType MetricType) (normalizedName string, err error) {
|
||||
defer func() {
|
||||
if len(normalizedName) == 0 {
|
||||
err = fmt.Errorf("normalization for metric %q resulted in empty name", name)
|
||||
return
|
||||
}
|
||||
|
||||
if normalizedName == name {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the resulting normalized name contains at least one non-underscore character
|
||||
for _, c := range normalizedName {
|
||||
if c != '_' {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("normalization for metric %q resulted in invalid name %q", name, normalizedName)
|
||||
normalizedName = ""
|
||||
}()
|
||||
|
||||
// Full normalization following standard Prometheus naming conventions
|
||||
if mn.WithMetricSuffixes {
|
||||
normalizedName = normalizeName(name, unit, metricType, mn.Namespace)
|
||||
return
|
||||
}
|
||||
|
||||
// Simple case (no full normalization, no units, etc.).
|
||||
metricName := strings.Join(strings.FieldsFunc(name, func(r rune) bool {
|
||||
return !isValidCompliantMetricChar(r) && r != '_'
|
||||
}), "_")
|
||||
|
||||
// Namespace?
|
||||
if mn.Namespace != "" {
|
||||
namespace := strings.Join(strings.FieldsFunc(mn.Namespace, func(r rune) bool {
|
||||
return !isValidCompliantMetricChar(r) && r != '_'
|
||||
}), "_")
|
||||
normalizedName = namespace + "_" + metricName
|
||||
return
|
||||
}
|
||||
|
||||
// Metric name starts with a digit? Prefix it with an underscore.
|
||||
if metricName != "" && unicode.IsDigit(rune(metricName[0])) {
|
||||
metricName = "_" + metricName
|
||||
}
|
||||
|
||||
normalizedName = metricName
|
||||
return
|
||||
}
|
||||
|
||||
// isValidCompliantMetricChar checks if a rune is a valid metric name character (a-z, A-Z, 0-9, :).
|
||||
func isValidCompliantMetricChar(r rune) bool {
|
||||
return (r >= 'a' && r <= 'z') ||
|
||||
(r >= 'A' && r <= 'Z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == ':'
|
||||
}
|
||||
|
||||
// replaceInvalidMetricChar replaces invalid metric name characters with underscore.
|
||||
func replaceInvalidMetricChar(r rune) rune {
|
||||
if isValidCompliantMetricChar(r) {
|
||||
return r
|
||||
}
|
||||
return '_'
|
||||
}
|
||||
|
||||
// Build a normalized name for the specified metric.
|
||||
func normalizeName(name, unit string, metricType MetricType, namespace string) string {
|
||||
// Split metric name into "tokens" (of supported metric name runes).
|
||||
// Note that this has the side effect of replacing multiple consecutive underscores with a single underscore.
|
||||
// This is part of the OTel to Prometheus specification: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus.
|
||||
nameTokens := strings.FieldsFunc(
|
||||
name,
|
||||
func(r rune) bool { return !isValidCompliantMetricChar(r) },
|
||||
)
|
||||
|
||||
mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit)
|
||||
nameTokens = addUnitTokens(nameTokens, cleanUpUnit(mainUnitSuffix), cleanUpUnit(perUnitSuffix))
|
||||
|
||||
// Append _total for Counters
|
||||
if metricType == MetricTypeMonotonicCounter {
|
||||
nameTokens = append(removeItem(nameTokens, "total"), "total")
|
||||
}
|
||||
|
||||
// Append _ratio for metrics with unit "1"
|
||||
// Some OTel receivers improperly use unit "1" for counters of objects
|
||||
// See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions
|
||||
// Until these issues have been fixed, we're appending `_ratio` for gauges ONLY
|
||||
// Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons)
|
||||
if unit == "1" && metricType == MetricTypeGauge {
|
||||
nameTokens = append(removeItem(nameTokens, "ratio"), "ratio")
|
||||
}
|
||||
|
||||
// Namespace?
|
||||
if namespace != "" {
|
||||
nameTokens = append([]string{namespace}, nameTokens...)
|
||||
}
|
||||
|
||||
// Build the string from the tokens, separated with underscores
|
||||
normalizedName := strings.Join(nameTokens, "_")
|
||||
|
||||
// Metric name cannot start with a digit, so prefix it with "_" in this case
|
||||
if normalizedName != "" && unicode.IsDigit(rune(normalizedName[0])) {
|
||||
normalizedName = "_" + normalizedName
|
||||
}
|
||||
|
||||
return normalizedName
|
||||
}
|
||||
|
||||
// addUnitTokens will add the suffixes to the nameTokens if they are not already present.
|
||||
// It will also remove trailing underscores from the main suffix to avoid double underscores
|
||||
// when joining the tokens.
|
||||
//
|
||||
// If the 'per' unit ends with underscore, the underscore will be removed. If the per unit is just
|
||||
// 'per_', it will be entirely removed.
|
||||
func addUnitTokens(nameTokens []string, mainUnitSuffix, perUnitSuffix string) []string {
|
||||
if slices.Contains(nameTokens, mainUnitSuffix) {
|
||||
mainUnitSuffix = ""
|
||||
}
|
||||
|
||||
if perUnitSuffix == "per_" {
|
||||
perUnitSuffix = ""
|
||||
} else {
|
||||
perUnitSuffix = strings.TrimSuffix(perUnitSuffix, "_")
|
||||
if slices.Contains(nameTokens, perUnitSuffix) {
|
||||
perUnitSuffix = ""
|
||||
}
|
||||
}
|
||||
|
||||
if perUnitSuffix != "" {
|
||||
mainUnitSuffix = strings.TrimSuffix(mainUnitSuffix, "_")
|
||||
}
|
||||
|
||||
if mainUnitSuffix != "" {
|
||||
nameTokens = append(nameTokens, mainUnitSuffix)
|
||||
}
|
||||
if perUnitSuffix != "" {
|
||||
nameTokens = append(nameTokens, perUnitSuffix)
|
||||
}
|
||||
return nameTokens
|
||||
}
|
||||
|
||||
// Remove the specified value from the slice.
|
||||
func removeItem(slice []string, value string) []string {
|
||||
newSlice := make([]string, 0, len(slice))
|
||||
for _, sliceEntry := range slice {
|
||||
if sliceEntry != value {
|
||||
newSlice = append(newSlice, sliceEntry)
|
||||
}
|
||||
}
|
||||
return newSlice
|
||||
}
|
||||
|
||||
func (mn *MetricNamer) buildMetricName(inputName, unit string, metricType MetricType) (name string, err error) {
|
||||
name = inputName
|
||||
if mn.Namespace != "" {
|
||||
name = mn.Namespace + "_" + name
|
||||
}
|
||||
|
||||
if mn.WithMetricSuffixes {
|
||||
// Append _ratio for metrics with unit "1"
|
||||
// Some OTel receivers improperly use unit "1" for counters of objects
|
||||
// See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions
|
||||
// Until these issues have been fixed, we're appending `_ratio` for gauges ONLY
|
||||
// Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons)
|
||||
if unit == "1" && metricType == MetricTypeGauge {
|
||||
name = trimSuffixAndDelimiter(name, "ratio")
|
||||
defer func() {
|
||||
name += "_ratio"
|
||||
}()
|
||||
}
|
||||
|
||||
// Append _total for Counters.
|
||||
if metricType == MetricTypeMonotonicCounter {
|
||||
name = trimSuffixAndDelimiter(name, "total")
|
||||
defer func() {
|
||||
name += "_total"
|
||||
}()
|
||||
}
|
||||
|
||||
mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit)
|
||||
if perUnitSuffix != "" {
|
||||
name = trimSuffixAndDelimiter(name, perUnitSuffix)
|
||||
defer func() {
|
||||
name = name + "_" + perUnitSuffix
|
||||
}()
|
||||
}
|
||||
// We don't need to trim and re-append the suffix here because this is
|
||||
// the inner-most suffix.
|
||||
if mainUnitSuffix != "" && !strings.HasSuffix(name, mainUnitSuffix) {
|
||||
name = name + "_" + mainUnitSuffix
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// trimSuffixAndDelimiter trims a suffix, plus one extra character which is
|
||||
// assumed to be a delimiter.
|
||||
func trimSuffixAndDelimiter(name, suffix string) string {
|
||||
if strings.HasSuffix(name, suffix) && len(name) > len(suffix)+1 {
|
||||
return name[:len(name)-(len(suffix)+1)]
|
||||
}
|
||||
return name
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
|
||||
package otlptranslator
|
||||
|
||||
// MetricType is a representation of metric types from OpenTelemetry.
|
||||
// Different types of Sums were introduced based on their metric temporalities.
|
||||
// For more details, see:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#sums
|
||||
type MetricType int
|
||||
|
||||
const (
|
||||
// MetricTypeUnknown represents an unknown metric type.
|
||||
MetricTypeUnknown = iota
|
||||
// MetricTypeNonMonotonicCounter represents a counter that is not monotonically increasing, also known as delta counter.
|
||||
MetricTypeNonMonotonicCounter
|
||||
// MetricTypeMonotonicCounter represents a counter that is monotonically increasing, also known as cumulative counter.
|
||||
MetricTypeMonotonicCounter
|
||||
// MetricTypeGauge represents a gauge metric.
|
||||
MetricTypeGauge
|
||||
// MetricTypeHistogram represents a histogram metric.
|
||||
MetricTypeHistogram
|
||||
// MetricTypeExponentialHistogram represents an exponential histogram metric.
|
||||
MetricTypeExponentialHistogram
|
||||
// MetricTypeSummary represents a summary metric.
|
||||
MetricTypeSummary
|
||||
)
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// Provenance-includes-location: https://github.com/prometheus/prometheus/blob/3602785a89162ccc99a940fb9d862219a2d02241/config/config.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: Copyright The Prometheus Authors
|
||||
|
||||
package otlptranslator
|
||||
|
||||
// TranslationStrategyOption is a constant that defines how metric and label
|
||||
// names should be handled during translation. The recommended approach is to
|
||||
// use either UnderscoreEscapingWithSuffixes for full Prometheus-style
|
||||
// compatibility, or NoTranslation for Otel-style names.
|
||||
type TranslationStrategyOption string
|
||||
|
||||
var (
|
||||
// NoUTF8EscapingWithSuffixes will accept metric/label names as they are. Unit
|
||||
// and type suffixes may be added to metric names, according to certain rules.
|
||||
NoUTF8EscapingWithSuffixes TranslationStrategyOption = "NoUTF8EscapingWithSuffixes"
|
||||
// UnderscoreEscapingWithSuffixes is the default option for translating OTLP
|
||||
// to Prometheus. This option will translate metric name characters that are
|
||||
// not alphanumerics/underscores/colons to underscores, and label name
|
||||
// characters that are not alphanumerics/underscores to underscores. Unit and
|
||||
// type suffixes may be appended to metric names, according to certain rules.
|
||||
UnderscoreEscapingWithSuffixes TranslationStrategyOption = "UnderscoreEscapingWithSuffixes"
|
||||
// UnderscoreEscapingWithoutSuffixes translates metric name characters that
|
||||
// are not alphanumerics/underscores/colons to underscores, and label name
|
||||
// characters that are not alphanumerics/underscores to underscores, but
|
||||
// unlike UnderscoreEscapingWithSuffixes it does not append any suffixes to
|
||||
// the names.
|
||||
UnderscoreEscapingWithoutSuffixes TranslationStrategyOption = "UnderscoreEscapingWithoutSuffixes"
|
||||
// NoTranslation (EXPERIMENTAL): disables all translation of incoming metric
|
||||
// and label names. This offers a way for the OTLP users to use native metric
|
||||
// names, reducing confusion.
|
||||
//
|
||||
// WARNING: This setting has significant known risks and limitations (see
|
||||
// https://prometheus.io/docs/practices/naming/ for details): * Impaired UX
|
||||
// when using PromQL in plain YAML (e.g. alerts, rules, dashboard, autoscaling
|
||||
// configuration). * Series collisions which in the best case may result in
|
||||
// OOO errors, in the worst case a silently malformed time series. For
|
||||
// instance, you may end up in situation of ingesting `foo.bar` series with
|
||||
// unit `seconds` and a separate series `foo.bar` with unit `milliseconds`.
|
||||
//
|
||||
// As a result, this setting is experimental and currently, should not be used
|
||||
// in production systems.
|
||||
//
|
||||
// TODO(ArthurSens): Mention `type-and-unit-labels` feature
|
||||
// (https://github.com/prometheus/proposals/pull/39) once released, as
|
||||
// potential mitigation of the above risks.
|
||||
NoTranslation TranslationStrategyOption = "NoTranslation"
|
||||
)
|
||||
|
||||
// ShouldEscape returns true if the translation strategy requires that metric
|
||||
// names be escaped.
|
||||
func (o TranslationStrategyOption) ShouldEscape() bool {
|
||||
switch o {
|
||||
case UnderscoreEscapingWithSuffixes, UnderscoreEscapingWithoutSuffixes:
|
||||
return true
|
||||
case NoTranslation, NoUTF8EscapingWithSuffixes:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldAddSuffixes returns a bool deciding whether the given translation
|
||||
// strategy should have suffixes added.
|
||||
func (o TranslationStrategyOption) ShouldAddSuffixes() bool {
|
||||
switch o {
|
||||
case UnderscoreEscapingWithSuffixes, NoUTF8EscapingWithSuffixes:
|
||||
return true
|
||||
case UnderscoreEscapingWithoutSuffixes, NoTranslation:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// Provenance-includes-location: https://github.com/prometheus/prometheus/blob/93e991ef7ed19cc997a9360c8016cac3767b8057/storage/remote/otlptranslator/prometheus/strconv.go.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: Copyright The Prometheus Authors
|
||||
// Provenance-includes-location: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/95e8f8fdc2a9dc87230406c9a3cf02be4fd68bea/pkg/translator/prometheus/normalize_name_test.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: Copyright The OpenTelemetry Authors.
|
||||
|
||||
package otlptranslator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// sanitizeLabelName replaces any characters not valid according to the
|
||||
// classical Prometheus label naming scheme with an underscore.
|
||||
// When preserveMultipleUnderscores is true, multiple consecutive underscores are preserved.
|
||||
// When false, multiple consecutive underscores are collapsed to a single underscore.
|
||||
func sanitizeLabelName(name string, preserveMultipleUnderscores bool) string {
|
||||
nameLength := len(name)
|
||||
|
||||
if preserveMultipleUnderscores {
|
||||
// Simple case: just replace invalid characters, preserve multiple underscores
|
||||
var b strings.Builder
|
||||
b.Grow(nameLength)
|
||||
for _, r := range name {
|
||||
if isValidCompliantLabelChar(r) {
|
||||
b.WriteRune(r)
|
||||
} else {
|
||||
b.WriteRune('_')
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
isReserved, labelName := isReservedLabel(name)
|
||||
if isReserved {
|
||||
name = labelName
|
||||
}
|
||||
|
||||
// Collapse multiple underscores while replacing invalid characters.
|
||||
var b strings.Builder
|
||||
b.Grow(nameLength)
|
||||
prevWasUnderscore := false
|
||||
|
||||
for _, r := range name {
|
||||
if isValidCompliantLabelChar(r) {
|
||||
b.WriteRune(r)
|
||||
prevWasUnderscore = false
|
||||
} else if !prevWasUnderscore {
|
||||
// Invalid character - replace with underscore.
|
||||
b.WriteRune('_')
|
||||
prevWasUnderscore = true
|
||||
}
|
||||
}
|
||||
if isReserved {
|
||||
return "__" + b.String() + "__"
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// isValidCompliantLabelChar checks if a rune is a valid label name character (a-z, A-Z, 0-9).
|
||||
func isValidCompliantLabelChar(r rune) bool {
|
||||
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')
|
||||
}
|
||||
|
||||
// isReservedLabel checks if a label is a reserved label.
|
||||
// Reserved labels are labels that start and end with exactly __.
|
||||
// The returned label name is the label name without the __ prefix and suffix.
|
||||
func isReservedLabel(name string) (bool, string) {
|
||||
if len(name) < 4 {
|
||||
return false, ""
|
||||
}
|
||||
if !strings.HasPrefix(name, "__") || !strings.HasSuffix(name, "__") {
|
||||
return false, ""
|
||||
}
|
||||
return true, name[2 : len(name)-2]
|
||||
}
|
||||
|
||||
// collapseMultipleUnderscores replaces multiple consecutive underscores with a single underscore.
|
||||
// This is equivalent to regexp.MustCompile(`__+`).ReplaceAllString(s, "_") but without using regex.
|
||||
func collapseMultipleUnderscores(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
prevWasUnderscore := false
|
||||
|
||||
for _, r := range s {
|
||||
if r == '_' {
|
||||
if !prevWasUnderscore {
|
||||
b.WriteRune('_')
|
||||
prevWasUnderscore = true
|
||||
}
|
||||
// Skip consecutive underscores
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
prevWasUnderscore = false
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
|
||||
package otlptranslator
|
||||
|
||||
import "strings"
|
||||
|
||||
// UnitNamer is a helper for building compliant unit names.
|
||||
// It processes OpenTelemetry Protocol (OTLP) unit strings and converts them
|
||||
// to Prometheus-compliant unit names.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// namer := UnitNamer{UTF8Allowed: false}
|
||||
// result := namer.Build("s") // "seconds"
|
||||
// result = namer.Build("By/s") // "bytes_per_second"
|
||||
type UnitNamer struct {
|
||||
UTF8Allowed bool
|
||||
}
|
||||
|
||||
// Build builds a unit name for the specified unit string.
|
||||
// It processes the unit by splitting it into main and per components,
|
||||
// applying unit mappings, and cleaning up invalid characters when UTF8Allowed is false.
|
||||
//
|
||||
// Unit mappings include:
|
||||
// - Time: s→seconds, ms→milliseconds, h→hours
|
||||
// - Bytes: By→bytes, KBy→kilobytes, MBy→megabytes
|
||||
// - SI: m→meters, V→volts, W→watts
|
||||
// - Special: 1→"" (empty), %→percent
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// namer := UnitNamer{UTF8Allowed: false}
|
||||
// namer.Build("s") // "seconds"
|
||||
// namer.Build("requests/s") // "requests_per_second"
|
||||
// namer.Build("1") // "" (dimensionless)
|
||||
func (un *UnitNamer) Build(unit string) string {
|
||||
mainUnit, perUnit := buildUnitSuffixes(unit)
|
||||
if !un.UTF8Allowed {
|
||||
mainUnit, perUnit = cleanUpUnit(mainUnit), cleanUpUnit(perUnit)
|
||||
}
|
||||
|
||||
var u string
|
||||
switch {
|
||||
case mainUnit != "" && perUnit != "":
|
||||
u = mainUnit + "_" + perUnit
|
||||
case mainUnit != "":
|
||||
u = mainUnit
|
||||
default:
|
||||
u = perUnit
|
||||
}
|
||||
|
||||
// Clean up leading and trailing underscores
|
||||
if len(u) > 0 && u[0:1] == "_" {
|
||||
u = u[1:]
|
||||
}
|
||||
if len(u) > 0 && u[len(u)-1:] == "_" {
|
||||
u = u[:len(u)-1]
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// Retrieve the Prometheus "basic" unit corresponding to the specified "basic" unit.
|
||||
// Returns the specified unit if not found in unitMap.
|
||||
func unitMapGetOrDefault(unit string) string {
|
||||
if promUnit, ok := unitMap[unit]; ok {
|
||||
return promUnit
|
||||
}
|
||||
return unit
|
||||
}
|
||||
|
||||
// Retrieve the Prometheus "per" unit corresponding to the specified "per" unit.
|
||||
// Returns the specified unit if not found in perUnitMap.
|
||||
func perUnitMapGetOrDefault(perUnit string) string {
|
||||
if promPerUnit, ok := perUnitMap[perUnit]; ok {
|
||||
return promPerUnit
|
||||
}
|
||||
return perUnit
|
||||
}
|
||||
|
||||
// buildUnitSuffixes builds the main and per unit suffixes for the specified unit
|
||||
// but doesn't do any special character transformation to accommodate Prometheus naming conventions.
|
||||
// Removing trailing underscores or appending suffixes is done in the caller.
|
||||
func buildUnitSuffixes(unit string) (mainUnitSuffix, perUnitSuffix string) {
|
||||
// Split unit at the '/' if any
|
||||
unitTokens := strings.SplitN(unit, "/", 2)
|
||||
|
||||
if len(unitTokens) > 0 {
|
||||
// Main unit
|
||||
// Update if not blank and doesn't contain '{}'
|
||||
mainUnitOTel := strings.TrimSpace(unitTokens[0])
|
||||
if mainUnitOTel != "" && !strings.ContainsAny(mainUnitOTel, "{}") {
|
||||
mainUnitSuffix = unitMapGetOrDefault(mainUnitOTel)
|
||||
}
|
||||
|
||||
// Per unit
|
||||
// Update if not blank and doesn't contain '{}'
|
||||
if len(unitTokens) > 1 && unitTokens[1] != "" {
|
||||
perUnitOTel := strings.TrimSpace(unitTokens[1])
|
||||
if perUnitOTel != "" && !strings.ContainsAny(perUnitOTel, "{}") {
|
||||
perUnitSuffix = perUnitMapGetOrDefault(perUnitOTel)
|
||||
}
|
||||
if perUnitSuffix != "" {
|
||||
perUnitSuffix = "per_" + perUnitSuffix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mainUnitSuffix, perUnitSuffix
|
||||
}
|
||||
|
||||
// cleanUpUnit cleans up unit so it matches model.LabelNameRE.
|
||||
func cleanUpUnit(unit string) string {
|
||||
// Multiple consecutive underscores are replaced with a single underscore.
|
||||
// This is part of the OTel to Prometheus specification: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus.
|
||||
return strings.TrimPrefix(collapseMultipleUnderscores(
|
||||
strings.Map(replaceInvalidMetricChar, unit),
|
||||
), "_")
|
||||
}
|
||||
+8
@@ -34,6 +34,14 @@ linters:
|
||||
capital: true
|
||||
misspell:
|
||||
locale: US
|
||||
revive:
|
||||
rules:
|
||||
- name: var-naming
|
||||
# TODO(SuperQ): See: https://github.com/prometheus/prometheus/issues/17766
|
||||
arguments:
|
||||
- []
|
||||
- []
|
||||
- - skip-package-name-checks: true
|
||||
exclusions:
|
||||
presets:
|
||||
- comments
|
||||
|
||||
+231
-16
@@ -1,4 +1,4 @@
|
||||
# Copyright 2018 The Prometheus Authors
|
||||
# Copyright The Prometheus Authors
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
@@ -55,13 +55,13 @@ ifneq ($(shell command -v gotestsum 2> /dev/null),)
|
||||
endif
|
||||
endif
|
||||
|
||||
PROMU_VERSION ?= 0.17.0
|
||||
PROMU_VERSION ?= 0.18.0
|
||||
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
|
||||
|
||||
SKIP_GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT_OPTS ?=
|
||||
GOLANGCI_LINT_VERSION ?= v2.1.5
|
||||
GOLANGCI_LINT_VERSION ?= v2.10.1
|
||||
GOLANGCI_FMT_OPTS ?=
|
||||
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
|
||||
# windows isn't included here because of the path separator being different.
|
||||
@@ -82,11 +82,50 @@ endif
|
||||
PREFIX ?= $(shell pwd)
|
||||
BIN_DIR ?= $(shell pwd)
|
||||
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
|
||||
DOCKERFILE_PATH ?= ./Dockerfile
|
||||
DOCKERBUILD_CONTEXT ?= ./
|
||||
DOCKER_REPO ?= prom
|
||||
|
||||
# Check if deprecated DOCKERFILE_PATH is set
|
||||
ifdef DOCKERFILE_PATH
|
||||
$(error DOCKERFILE_PATH is deprecated. Use DOCKERFILE_VARIANTS ?= $(DOCKERFILE_PATH) in the Makefile)
|
||||
endif
|
||||
|
||||
DOCKER_ARCHS ?= amd64
|
||||
DOCKERFILE_VARIANTS ?= Dockerfile $(wildcard Dockerfile.*)
|
||||
|
||||
# Function to extract variant from Dockerfile label.
|
||||
# Returns the variant name from io.prometheus.image.variant label, or "default" if not found.
|
||||
define dockerfile_variant
|
||||
$(strip $(or $(shell sed -n 's/.*io\.prometheus\.image\.variant="\([^"]*\)".*/\1/p' $(1)),default))
|
||||
endef
|
||||
|
||||
# Check for duplicate variant names (including default for Dockerfiles without labels).
|
||||
DOCKERFILE_VARIANT_NAMES := $(foreach df,$(DOCKERFILE_VARIANTS),$(call dockerfile_variant,$(df)))
|
||||
DOCKERFILE_VARIANT_NAMES_SORTED := $(sort $(DOCKERFILE_VARIANT_NAMES))
|
||||
ifneq ($(words $(DOCKERFILE_VARIANT_NAMES)),$(words $(DOCKERFILE_VARIANT_NAMES_SORTED)))
|
||||
$(error Duplicate variant names found. Each Dockerfile must have a unique io.prometheus.image.variant label, and only one can be without a label (default))
|
||||
endif
|
||||
|
||||
# Build variant:dockerfile pairs for shell iteration.
|
||||
DOCKERFILE_VARIANTS_WITH_NAMES := $(foreach df,$(DOCKERFILE_VARIANTS),$(call dockerfile_variant,$(df)):$(df))
|
||||
|
||||
# Shell helper to check whether a dockerfile/arch pair is excluded.
|
||||
define dockerfile_arch_is_excluded
|
||||
case " $(DOCKERFILE_ARCH_EXCLUSIONS) " in \
|
||||
*" $$dockerfile:$(1) "*) true ;; \
|
||||
*) false ;; \
|
||||
esac
|
||||
endef
|
||||
|
||||
# Shell helper to check whether a registry/arch pair is excluded.
|
||||
# Extracts registry from DOCKER_REPO (e.g., quay.io/prometheus -> quay.io)
|
||||
define registry_arch_is_excluded
|
||||
registry=$$(echo "$(DOCKER_REPO)" | cut -d'/' -f1); \
|
||||
case " $(DOCKER_REGISTRY_ARCH_EXCLUSIONS) " in \
|
||||
*" $$registry:$(1) "*) true ;; \
|
||||
*) false ;; \
|
||||
esac
|
||||
endef
|
||||
|
||||
BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS))
|
||||
PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS))
|
||||
@@ -112,7 +151,7 @@ common-all: precheck style check_license lint yamllint unused build test
|
||||
.PHONY: common-style
|
||||
common-style:
|
||||
@echo ">> checking code style"
|
||||
@fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
|
||||
@fmtRes=$$($(GOFMT) -d $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -name '*.go' -print)); \
|
||||
if [ -n "$${fmtRes}" ]; then \
|
||||
echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
|
||||
echo "Please ensure you are using $$($(GO) version) for formatting code."; \
|
||||
@@ -122,13 +161,19 @@ common-style:
|
||||
.PHONY: common-check_license
|
||||
common-check_license:
|
||||
@echo ">> checking license header"
|
||||
@licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
|
||||
@licRes=$$(for file in $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -type f -iname '*.go' -print) ; do \
|
||||
awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
|
||||
done); \
|
||||
if [ -n "$${licRes}" ]; then \
|
||||
echo "license header checking failed:"; echo "$${licRes}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo ">> checking for copyright years 2026 or later"
|
||||
@futureYearRes=$$(git grep -E 'Copyright (202[6-9]|20[3-9][0-9])' -- '*.go' ':!:vendor/*' || true); \
|
||||
if [ -n "$${futureYearRes}" ]; then \
|
||||
echo "Files with copyright year 2026 or later found (should use 'Copyright The Prometheus Authors'):"; echo "$${futureYearRes}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: common-deps
|
||||
common-deps:
|
||||
@@ -220,28 +265,194 @@ common-docker-repo-name:
|
||||
.PHONY: common-docker $(BUILD_DOCKER_ARCHS)
|
||||
common-docker: $(BUILD_DOCKER_ARCHS)
|
||||
$(BUILD_DOCKER_ARCHS): common-docker-%:
|
||||
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \
|
||||
-f $(DOCKERFILE_PATH) \
|
||||
--build-arg ARCH="$*" \
|
||||
--build-arg OS="linux" \
|
||||
$(DOCKERBUILD_CONTEXT)
|
||||
@for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \
|
||||
dockerfile=$${variant#*:}; \
|
||||
variant_name=$${variant%%:*}; \
|
||||
if $(call dockerfile_arch_is_excluded,$*); then \
|
||||
echo "Skipping $$variant_name variant for linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
distroless_arch="$*"; \
|
||||
if [ "$*" = "armv7" ]; then \
|
||||
distroless_arch="arm"; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" = "Dockerfile" ]; then \
|
||||
echo "Building default variant ($$variant_name) for linux-$* using $$dockerfile"; \
|
||||
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \
|
||||
-f $$dockerfile \
|
||||
--build-arg ARCH="$*" \
|
||||
--build-arg OS="linux" \
|
||||
--build-arg DISTROLESS_ARCH="$$distroless_arch" \
|
||||
$(DOCKERBUILD_CONTEXT); \
|
||||
if [ "$$variant_name" != "default" ]; then \
|
||||
echo "Tagging default variant with $$variant_name suffix"; \
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \
|
||||
"$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \
|
||||
fi; \
|
||||
else \
|
||||
echo "Building $$variant_name variant for linux-$* using $$dockerfile"; \
|
||||
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" \
|
||||
-f $$dockerfile \
|
||||
--build-arg ARCH="$*" \
|
||||
--build-arg OS="linux" \
|
||||
--build-arg DISTROLESS_ARCH="$$distroless_arch" \
|
||||
$(DOCKERBUILD_CONTEXT); \
|
||||
fi; \
|
||||
done
|
||||
|
||||
.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
|
||||
common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
|
||||
$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)"
|
||||
@for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \
|
||||
dockerfile=$${variant#*:}; \
|
||||
variant_name=$${variant%%:*}; \
|
||||
if $(call dockerfile_arch_is_excluded,$*); then \
|
||||
echo "Skipping push for $$variant_name variant on linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if $(call registry_arch_is_excluded,$*); then \
|
||||
echo "Skipping push for $$variant_name variant on linux-$* to $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \
|
||||
echo "Pushing $$variant_name variant for linux-$*"; \
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" = "Dockerfile" ]; then \
|
||||
echo "Pushing default variant ($$variant_name) for linux-$*"; \
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)"; \
|
||||
fi; \
|
||||
if [ "$(DOCKER_IMAGE_TAG)" = "latest" ]; then \
|
||||
if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \
|
||||
echo "Pushing $$variant_name variant version tags for linux-$*"; \
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" = "Dockerfile" ]; then \
|
||||
echo "Pushing default variant version tag for linux-$*"; \
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"; \
|
||||
fi; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
|
||||
.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
|
||||
common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
|
||||
$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
|
||||
@for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \
|
||||
dockerfile=$${variant#*:}; \
|
||||
variant_name=$${variant%%:*}; \
|
||||
if $(call dockerfile_arch_is_excluded,$*); then \
|
||||
echo "Skipping tag for $$variant_name variant on linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if $(call registry_arch_is_excluded,$*); then \
|
||||
echo "Skipping tag for $$variant_name variant on linux-$* for $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \
|
||||
echo "Tagging $$variant_name variant for linux-$* as latest"; \
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest-$$variant_name"; \
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" = "Dockerfile" ]; then \
|
||||
echo "Tagging default variant ($$variant_name) for linux-$* as latest"; \
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"; \
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
.PHONY: common-docker-manifest
|
||||
common-docker-manifest:
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG))
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)"
|
||||
@for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \
|
||||
dockerfile=$${variant#*:}; \
|
||||
variant_name=$${variant%%:*}; \
|
||||
if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \
|
||||
echo "Creating manifest for $$variant_name variant"; \
|
||||
refs=""; \
|
||||
for arch in $(DOCKER_ARCHS); do \
|
||||
if $(call dockerfile_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for $$variant_name (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if $(call registry_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for $$variant_name on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \
|
||||
done; \
|
||||
if [ -z "$$refs" ]; then \
|
||||
echo "Skipping manifest for $$variant_name variant (no supported architectures)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" $$refs; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" = "Dockerfile" ]; then \
|
||||
echo "Creating default variant ($$variant_name) manifest"; \
|
||||
refs=""; \
|
||||
for arch in $(DOCKER_ARCHS); do \
|
||||
if $(call dockerfile_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for default variant (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if $(call registry_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for default variant on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)"; \
|
||||
done; \
|
||||
if [ -z "$$refs" ]; then \
|
||||
echo "Skipping default variant manifest (no supported architectures)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $$refs; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)"; \
|
||||
fi; \
|
||||
if [ "$(DOCKER_IMAGE_TAG)" = "latest" ]; then \
|
||||
if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \
|
||||
echo "Creating manifest for $$variant_name variant version tag"; \
|
||||
refs=""; \
|
||||
for arch in $(DOCKER_ARCHS); do \
|
||||
if $(call dockerfile_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for $$variant_name version tag (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if $(call registry_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for $$variant_name version tag on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \
|
||||
done; \
|
||||
if [ -z "$$refs" ]; then \
|
||||
echo "Skipping version-tag manifest for $$variant_name variant (no supported architectures)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name" $$refs; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \
|
||||
fi; \
|
||||
if [ "$$dockerfile" = "Dockerfile" ]; then \
|
||||
echo "Creating default variant version tag manifest"; \
|
||||
refs=""; \
|
||||
for arch in $(DOCKER_ARCHS); do \
|
||||
if $(call dockerfile_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for default variant version tag (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
if $(call registry_arch_is_excluded,$$arch); then \
|
||||
echo " Skipping $$arch for default variant version tag on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)"; \
|
||||
done; \
|
||||
if [ -z "$$refs" ]; then \
|
||||
echo "Skipping default variant version-tag manifest (no supported architectures)"; \
|
||||
continue; \
|
||||
fi; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)" $$refs; \
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)"; \
|
||||
fi; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
.PHONY: promu
|
||||
promu: $(PROMU)
|
||||
@@ -266,6 +477,10 @@ $(GOLANGCI_LINT):
|
||||
| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
|
||||
endif
|
||||
|
||||
.PHONY: common-print-golangci-lint-version
|
||||
common-print-golangci-lint-version:
|
||||
@echo $(GOLANGCI_LINT_VERSION)
|
||||
|
||||
.PHONY: precheck
|
||||
precheck::
|
||||
|
||||
|
||||
+1
-2
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package procfs
|
||||
|
||||
@@ -502,7 +501,7 @@ func parseCPUInfoRISCV(info []byte) ([]CPUInfo, error) {
|
||||
return cpuinfo, nil
|
||||
}
|
||||
|
||||
func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { // nolint:unused,deadcode
|
||||
func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { //nolint:unused
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -12,8 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && (arm || arm64)
|
||||
// +build linux
|
||||
// +build arm arm64
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-2
@@ -12,8 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && (mips || mipsle || mips64 || mips64le)
|
||||
// +build linux
|
||||
// +build mips mipsle mips64 mips64le
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && !386 && !amd64 && !arm && !arm64 && !loong64 && !mips && !mips64 && !mips64le && !mipsle && !ppc64 && !ppc64le && !riscv64 && !s390x
|
||||
// +build linux,!386,!amd64,!arm,!arm64,!loong64,!mips,!mips64,!mips64le,!mipsle,!ppc64,!ppc64le,!riscv64,!s390x
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-2
@@ -12,8 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && (ppc64 || ppc64le)
|
||||
// +build linux
|
||||
// +build ppc64 ppc64le
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-2
@@ -12,8 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && (riscv || riscv64)
|
||||
// +build linux
|
||||
// +build riscv riscv64
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-2
@@ -12,8 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build linux && (386 || amd64)
|
||||
// +build linux
|
||||
// +build 386 amd64
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !freebsd && !linux
|
||||
// +build !freebsd,!linux
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build freebsd || linux
|
||||
// +build freebsd linux
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-2
@@ -12,8 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (linux || darwin) && !appengine
|
||||
// +build linux darwin
|
||||
// +build !appengine
|
||||
|
||||
package util
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (linux && appengine) || (!linux && !darwin)
|
||||
// +build linux,appengine !linux,!darwin
|
||||
|
||||
package util
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
+4
@@ -25,6 +25,7 @@ type (
|
||||
|
||||
// NetTCP returns the IPv4 kernel/networking statistics for TCP datagrams
|
||||
// read from /proc/net/tcp.
|
||||
//
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead.
|
||||
func (fs FS) NetTCP() (NetTCP, error) {
|
||||
return newNetTCP(fs.proc.Path("net/tcp"))
|
||||
@@ -32,6 +33,7 @@ func (fs FS) NetTCP() (NetTCP, error) {
|
||||
|
||||
// NetTCP6 returns the IPv6 kernel/networking statistics for TCP datagrams
|
||||
// read from /proc/net/tcp6.
|
||||
//
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead.
|
||||
func (fs FS) NetTCP6() (NetTCP, error) {
|
||||
return newNetTCP(fs.proc.Path("net/tcp6"))
|
||||
@@ -39,6 +41,7 @@ func (fs FS) NetTCP6() (NetTCP, error) {
|
||||
|
||||
// NetTCPSummary returns already computed statistics like the total queue lengths
|
||||
// for TCP datagrams read from /proc/net/tcp.
|
||||
//
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead.
|
||||
func (fs FS) NetTCPSummary() (*NetTCPSummary, error) {
|
||||
return newNetTCPSummary(fs.proc.Path("net/tcp"))
|
||||
@@ -46,6 +49,7 @@ func (fs FS) NetTCPSummary() (*NetTCPSummary, error) {
|
||||
|
||||
// NetTCP6Summary returns already computed statistics like the total queue lengths
|
||||
// for TCP datagrams read from /proc/net/tcp6.
|
||||
//
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead.
|
||||
func (fs FS) NetTCP6Summary() (*NetTCPSummary, error) {
|
||||
return newNetTCPSummary(fs.proc.Path("net/tcp6"))
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ type Interrupts map[string]Interrupt
|
||||
|
||||
// Interrupts creates a new instance from a given Proc instance.
|
||||
func (p Proc) Interrupts() (Interrupts, error) {
|
||||
data, err := util.ReadFileNoStat(p.path("interrupts"))
|
||||
data, err := util.ReadFileNoStat(p.fs.proc.Path("interrupts"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
-2
@@ -12,8 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris) && !js
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
// +build !js
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ type ProcStatm struct {
|
||||
}
|
||||
|
||||
// NewStatm returns the current status information of the process.
|
||||
//
|
||||
// Deprecated: Use p.Statm() instead.
|
||||
func (p Proc) NewStatm() (ProcStatm, error) {
|
||||
return p.Statm()
|
||||
|
||||
+43
@@ -83,6 +83,19 @@ type ProcStatus struct {
|
||||
|
||||
// CpusAllowedList: List of cpu cores processes are allowed to run on.
|
||||
CpusAllowedList []uint64
|
||||
|
||||
// CapInh is the bitmap of inheritable capabilities
|
||||
//
|
||||
// See: https://www.kernel.org/doc/man-pages/online/pages/man7/capabilities.7.html
|
||||
CapInh uint64
|
||||
// CapPrm is the bitmap of permitted capabilities
|
||||
CapPrm uint64
|
||||
// CapEff is the bitmap of effective capabilities
|
||||
CapEff uint64
|
||||
// CapBnd is the bitmap of bounding capabilities
|
||||
CapBnd uint64
|
||||
// CapAmb is the bitmap of ambient capabilities
|
||||
CapAmb uint64
|
||||
}
|
||||
|
||||
// NewStatus returns the current status information of the process.
|
||||
@@ -190,6 +203,36 @@ func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintByt
|
||||
s.NonVoluntaryCtxtSwitches = vUint
|
||||
case "Cpus_allowed_list":
|
||||
s.CpusAllowedList = calcCpusAllowedList(vString)
|
||||
case "CapInh":
|
||||
var err error
|
||||
s.CapInh, err = strconv.ParseUint(vString, 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "CapPrm":
|
||||
var err error
|
||||
s.CapPrm, err = strconv.ParseUint(vString, 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "CapEff":
|
||||
var err error
|
||||
s.CapEff, err = strconv.ParseUint(vString, 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "CapBnd":
|
||||
var err error
|
||||
s.CapBnd, err = strconv.ParseUint(vString, 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "CapAmb":
|
||||
var err error
|
||||
s.CapAmb, err = strconv.ParseUint(vString, 16, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package procfs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user