Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[exporter/bmchelix] New component: BMC Helix Exporter #36964

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .chloggen/bmchelixexporter-new-component.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: new_component

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: exporter/bmchelix

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "Add a new component for exporting metrics to BMC Helix"

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [36773]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ exporter/awss3exporter/ @open-telemetry/collector-cont
exporter/awsxrayexporter/ @open-telemetry/collector-contrib-approvers @wangzlei @srprash
exporter/azuredataexplorerexporter/ @open-telemetry/collector-contrib-approvers @ag-ramachandran
exporter/azuremonitorexporter/ @open-telemetry/collector-contrib-approvers @pcwiese
exporter/bmchelixexporter/ @open-telemetry/collector-contrib-approvers @bertysentry @NassimBtk
exporter/carbonexporter/ @open-telemetry/collector-contrib-approvers @aboguszewski-sumo
exporter/cassandraexporter/ @open-telemetry/collector-contrib-approvers @atoulme @emreyalvac
exporter/clickhouseexporter/ @open-telemetry/collector-contrib-approvers @hanjm @dmitryax @Frapschen @SpencerTorres
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ body:
- exporter/awsxray
- exporter/azuredataexplorer
- exporter/azuremonitor
- exporter/bmchelix
- exporter/carbon
- exporter/cassandra
- exporter/clickhouse
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ body:
- exporter/awsxray
- exporter/azuredataexplorer
- exporter/azuremonitor
- exporter/bmchelix
- exporter/carbon
- exporter/cassandra
- exporter/clickhouse
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/other.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ body:
- exporter/awsxray
- exporter/azuredataexplorer
- exporter/azuremonitor
- exporter/bmchelix
- exporter/carbon
- exporter/cassandra
- exporter/clickhouse
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/unmaintained.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ body:
- exporter/awsxray
- exporter/azuredataexplorer
- exporter/azuremonitor
- exporter/bmchelix
- exporter/carbon
- exporter/cassandra
- exporter/clickhouse
Expand Down
1 change: 1 addition & 0 deletions exporter/bmchelixexporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
129 changes: 129 additions & 0 deletions exporter/bmchelixexporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# BMC Helix Exporter

<!-- status autogenerated section -->
| Status | |
| ------------- |-----------|
| Stability | [development]: metrics |
| Distributions | [] |
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fbmchelix%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fbmchelix) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fbmchelix%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fbmchelix) |
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@bertysentry](https://www.github.com/bertysentry), [@NassimBtk](https://www.github.com/NassimBtk) |

[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development
<!-- end autogenerated section -->

This exporter supports sending metrics to BMC Helix Operation Management.
NassimBtk marked this conversation as resolved.
Show resolved Hide resolved

## Getting Started

The following settings are **required**:

- `endpoint`: is the URL of your BMC Helix environment, at **onbmc.com** (e.g., `https://company.onbmc.com`).
NassimBtk marked this conversation as resolved.
Show resolved Hide resolved
- `api_key`: API key to authenticate the exporter. Connect to BMC Helix Operations Management, go to the Administration > Repository page, and click on the Copy API Key button to get your API Key.
NassimBtk marked this conversation as resolved.
Show resolved Hide resolved

Example:

```yaml
exporters:
bmchelix/helix1:
endpoint: https://company.onbmc.com
api_key: <api-key>
```

### Optional Settings

The following settings can be **optionally configured**:

- `timeout`: (default = `10s`) Timeout for requests made to the BMC Helix.
- `retry_on_failure` [details here](https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter/exporterhelper#configuration)
- `enabled` (default = true)
- `initial_interval` (default = 5s) Time to wait after the first failure before retrying; ignored if `enabled` is false.
- `max_interval` (default = 30s) The upper bound on backoff; ignored if `enabled` is false.
- `max_elapsed_time` (default = 300s) The maximum amount of time spent trying to send a batch; ignored if `enabled` is false. If set to 0, the retries are never stopped.
- `resource_to_telemetry_conversion`: [details here](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/resourcetotelemetry#configuration)
- `enabled` (default = true): If enabled is true, all the resource attributes will be converted to metric labels by default.

Example:

```yaml
exporters:
bmchelix/helix2:
endpoint: https://company.onbmc.com
api_key: <api-key>
timeout: 20s
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 1m
max_elapsed_time: 8m
resource_to_telemetry_conversion:
enabled: true
```

### Resource to Telemetry Conversion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option (resource_to_telemetry_conversion) should probably be removed as it's difficult to understand and is pretty useless in BMC Helix use case.


To ensure resource attributes (e.g., `host.name`) are available for all metrics, keep `resource_to_telemetry_conversion` enabled by default. Disabling it will remove resource attributes from metrics unless explicitly configured.

---

## Setting Required Attributes for Metrics

To ensure metrics are correctly populated in BMC Helix, the following attributes must be set:
NassimBtk marked this conversation as resolved.
Show resolved Hide resolved

- `entityName`: Unique identifier for the entity. Used as display name if `instanceName` is missing.
- `entityTypeId`: Type identifier for the entity.
- `instanceName`: Display name of the entity.

> **Note:** If `entityName` or `entityTypeId` is missing, the metric will not be populated.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> **Note:** If `entityName` or `entityTypeId` is missing, the metric will not be populated.
> **Note:** If `entityName` or `entityTypeId` is missing, the metric will not be exported.
To ensure the necessary attributes are not present, it is recommended to leverage the [transform processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor) with [OTTL](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl), and include it in the configuration of the telemetry pipeline.
The minimal pipeline most often looks like: `OTEL metrics --> (batch/memory limit) --> transform processor --> bmchelix exporter`.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are present, ...

NassimBtk marked this conversation as resolved.
Show resolved Hide resolved

### Transformer Example for Hardware Metrics

You can use the following OpenTelemetry Transformation Language (OTTL) configuration to map these attributes dynamically:

```yaml
transform/hw_to_helix:
# Apply transformations to all metrics
metric_statements:

- context: datapoint
statements:
# Create a new attribute 'entityName' with the value of 'id'
- set(attributes["entityName"], attributes["id"]) where attributes["id"] != nil
# Create a new attribute 'instanceName' with the value of 'name'
- set(attributes["instanceName"], attributes["name"]) where attributes["name"] != nil

- context: datapoint
conditions:
- IsMatch(metric.name, ".*\\.agent\\..*")
statements:
- set(attributes["entityName"], attributes["host.id"]) where attributes["host.id"] != nil
- set(attributes["instanceName"], attributes["service.name"]) where attributes["service.name"] != nil
- set(attributes["entityTypeId"], "agent")

- context: datapoint
statements:
# Mapping entityTypeId based on metric names and attributes
- set(attributes["entityTypeId"], "connector") where IsMatch(metric.name, ".*\\.connector\\..*")
- set(attributes["entityTypeId"], "host") where IsMatch(metric.name, ".*\\.host\\..*") or attributes["hw.type"] == "host"
- set(attributes["entityTypeId"], "battery") where IsMatch(metric.name, "hw\\.battery\\..*") or attributes["hw.type"] == "battery"
- set(attributes["entityTypeId"], "blade") where IsMatch(metric.name, "hw\\.blade\\..*") or attributes["hw.type"] == "blade"
- set(attributes["entityTypeId"], "cpu") where IsMatch(metric.name, "hw\\.cpu\\..*") or attributes["hw.type"] == "cpu"
- set(attributes["entityTypeId"], "disk_controller") where IsMatch(metric.name, "hw\\.disk_controller\\..*") or attributes["hw.type"] == "disk_controller"
- set(attributes["entityTypeId"], "enclosure") where IsMatch(metric.name, "hw\\.enclosure\\..*") or attributes["hw.type"] == "enclosure"
- set(attributes["entityTypeId"], "fan") where IsMatch(metric.name, "hw\\.fan\\..*") or attributes["hw.type"] == "fan"
- set(attributes["entityTypeId"], "gpu") where IsMatch(metric.name, "hw\\.gpu\\..*") or attributes["hw.type"] == "gpu"
- set(attributes["entityTypeId"], "led") where IsMatch(metric.name, "hw\\.led\\..*") or attributes["hw.type"] == "led"
- set(attributes["entityTypeId"], "logical_disk") where IsMatch(metric.name, "hw\\.logical_disk\\..*") or attributes["hw.type"] == "logical_disk"
- set(attributes["entityTypeId"], "lun") where IsMatch(metric.name, "hw\\.lun\\..*") or attributes["hw.type"] == "lun"
- set(attributes["entityTypeId"], "memory") where IsMatch(metric.name, "hw\\.memory\\..*") or attributes["hw.type"] == "memory"
- set(attributes["entityTypeId"], "network") where IsMatch(metric.name, "hw\\.network\\..*") or attributes["hw.type"] == "network"
- set(attributes["entityTypeId"], "other_device") where IsMatch(metric.name, "hw\\.other_device\\..*") or attributes["hw.type"] == "other_device"
- set(attributes["entityTypeId"], "physical_disk") where IsMatch(metric.name, "hw\\.physical_disk\\..*") or attributes["hw.type"] == "physical_disk"
- set(attributes["entityTypeId"], "power_supply") where IsMatch(metric.name, "hw\\.power_supply\\..*") or attributes["hw.type"] == "power_supply"
- set(attributes["entityTypeId"], "robotics") where IsMatch(metric.name, "hw\\.robotics\\..*") or attributes["hw.type"] == "robotics"
- set(attributes["entityTypeId"], "tape_drive") where IsMatch(metric.name, "hw\\.tape_drive\\..*") or attributes["hw.type"] == "tape_drive"
- set(attributes["entityTypeId"], "temperature") where IsMatch(metric.name, "hw\\.temperature.*") or attributes["hw.type"] == "temperature"
- set(attributes["entityTypeId"], "vm") where IsMatch(metric.name, "hw\\.vm\\..*") or attributes["hw.type"] == "vm"
- set(attributes["entityTypeId"], "voltage") where IsMatch(metric.name, "hw\\.voltage.*") or attributes["hw.type"] == "voltage"
```

This transformer dynamically sets the attributes required for BMC Helix based on metric names and resource attributes.
37 changes: 37 additions & 0 deletions exporter/bmchelixexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package bmchelixexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/bmchelixexporter"

import (
"errors"
"time"

"go.opentelemetry.io/collector/config/configretry"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry"
)

// Config struct is used to store the configuration of the exporter
type Config struct {
Endpoint string `mapstructure:"endpoint"`
ApiKey string `mapstructure:"api_key"`
Timeout time.Duration `mapstructure:"timeout"`
RetryConfig configretry.BackOffConfig `mapstructure:"retry_on_failure"`
ResourceToTelemetryConfig resourcetotelemetry.Settings `mapstructure:"resource_to_telemetry_conversion"`
}

// validate the configuration
func (c *Config) Validate() error {
if c.Endpoint == "" {
return errors.New("endpoint is required")
}
if c.ApiKey == "" {
return errors.New("api key is required")
}
if c.Timeout < 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If timeout must be a positive integer, then I think it should be if c.Timeout <= 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

return errors.New("timeout must be a positive integer")
}

return nil
}
130 changes: 130 additions & 0 deletions exporter/bmchelixexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package bmchelixexporter

import (
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configretry"
"go.opentelemetry.io/collector/confmap/confmaptest"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/bmchelixexporter/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/resourcetotelemetry"
)

func TestLoadConfig(t *testing.T) {
t.Parallel()

cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)

tests := []struct {
id component.ID
expected component.Config
errorMessage string
}{
{
id: component.NewIDWithName(metadata.Type, "helix1"),
expected: &Config{
Endpoint: "https://helix1:8080",
ApiKey: "api_key",
Timeout: 10 * time.Second,
RetryConfig: configretry.NewDefaultBackOffConfig(),
ResourceToTelemetryConfig: resourcetotelemetry.Settings{
Enabled: true,
},
},
},
{
id: component.NewIDWithName(metadata.Type, "helix2"),
expected: &Config{
Endpoint: "https://helix2:8080",
ApiKey: "api_key",
Timeout: 20 * time.Second,
RetryConfig: configretry.BackOffConfig{
Enabled: true,
InitialInterval: 5 * time.Second,
RandomizationFactor: 0.5,
Multiplier: 1.5,
MaxInterval: 1 * time.Minute,
MaxElapsedTime: 8 * time.Minute,
},
ResourceToTelemetryConfig: resourcetotelemetry.Settings{
Enabled: true,
},
},
},
}

for _, tt := range tests {
t.Run(tt.id.String(), func(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()

sub, err := cm.Sub(tt.id.String())
require.NoError(t, err)
require.NoError(t, sub.Unmarshal(cfg))

assert.NoError(t, component.ValidateConfig(cfg))
assert.Equal(t, tt.expected, cfg)
})
}
}

func TestValidateConfig(t *testing.T) {
tests := []struct {
name string
config *Config
err string
}{
{
name: "valid_config",
config: &Config{
Endpoint: "https://helix:8080",
ApiKey: "api_key",
Timeout: 10 * time.Second,
},
},
{
name: "invalid_config1",
config: &Config{
ApiKey: "api_key",
},
err: "endpoint is required",
},
{
name: "invalid_config2",
config: &Config{
Endpoint: "https://helix:8080",
},
err: "api key is required",
},
{
name: "invalid_config3",
config: &Config{
Endpoint: "https://helix:8080",
ApiKey: "api_key",
Timeout: -1,
},
err: "timeout must be a positive integer",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.err != "" {
err := tt.config.Validate()
assert.Error(t, err)
assert.Equal(t, tt.err, err.Error())
} else {
assert.NoError(t, tt.config.Validate())
}
})
}
}
7 changes: 7 additions & 0 deletions exporter/bmchelixexporter/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:generate mdatagen metadata.yaml

// Package bmchelixexporter implements an exporter that sends data to BMC Helix.
package bmchelixexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/bmchelixexporter"
Loading
Loading