diff --git a/README.md b/README.md
index d1e80dafc1..8274e87429 100644
--- a/README.md
+++ b/README.md
@@ -10,45 +10,68 @@
-
-
Generate distributed traces for any application in k8s without code changes.
+Generate distributed traces for any application in Kubernetes without code changes.
+## What is Odigos?
+
+Odigos is an open-source distributed tracing solution that simplifyes and improves observability for Kubernetes environments. It provides instant tracing capabilities without requiring any code changes to your applications.
+
+## Key Features
+
+* **Code-Free Instrumentation** : Set up distributed tracing in minutes, eliminating manual code modifications.
+* **Multi-Language Support** : Works with Java, Python, .NET, Node.js, and Go applications.
+* **eBPF-Powered** : Utilizes eBPF technology for high-performance instrumentation of Go applications. eBPF-based instrumentation for Java, Python, and Node.js is available in the enterprise edition.
+* **OpenTelemetry Compatible** : Generates traces in OpenTelemetry format for broad tool compatibility.
+* **Vendor Agnostic** : Integrates with various monitoring solutions, avoiding vendor lock-in.
+* **Automatic Scaling** : Manages and scales OpenTelemetry collectors based on data volume.
+* **Opinionated Defaults** : Supplies common defaults and best practices out-of-the-box, requiring no deep knowledge of OpenTelemetry.
+
+## Why Choose Odigos
+
+1. **Simplicity** : Implement distributed tracing with minimal effort and complexity.
+2. **Performance** : Separates data recording and processing to minimize runtime impact.
+3. **Community-Backed** : With 3,000+ GitHub stars and a growing contributor base.
+4. **Expertise** : Created by multiple maintainers of OpenTelemetry, ensuring deep integration and alignment with industry standards.
+
+Odigos empowers platform engineers, DevOps professionals, and SREs to enhance their observability strategies quickly and effectively. It is an ideal solution for modern cloud-native environments, combining simplicity, performance, and industry expertise.
+
+## Features
### β¨ Language Agnostic Auto-instrumentation
-Odigos supports any application written in Java, Python, .NET, Node.js, and **Go**.
+Odigos supports any application written in Java, Python, .NET, Node.js, and **Go**.
Historically, compiled languages like Go have been difficult to instrument without code changes. Odigos solves this problem by uniquely leveraging [eBPF](https://ebpf.io).
-
-
+
### π€ Keep your existing observability tools
-Odigos currently supports all the popular managed and open-source destinations.
+
+Odigos currently supports all the popular managed and open-source destinations.
By producing data in the [OpenTelemetry](https://opentelemetry.io) format, Odigos can be used with any observability tool that supports OTLP.
For a complete list of supported destinations, see [here](#supported-destinations).
-
+
+
+### ποΈ Collectors Management
-### ποΈ Collectors Management
-Odigos automatically scales OpenTelemetry collectors based on observability data volume.
+Odigos automatically scales OpenTelemetry collectors based on observability data volume.
Manage and configure collectors via a convenient web UI.
-
+
## Installation
Installing Odigos takes less than 5 minutes and requires no code changes.
Download our [CLI](https://docs.odigos.io/installation) and run the following command:
-
```bash
odigos install
```
@@ -61,32 +84,34 @@ For more details, see our [quickstart guide](https://docs.odigos.io/intro).
### Managed
-| | Traces | Metrics | Logs |
-|-------------------------| ------- | ------- |------|
-| New Relic | β
| β
| β
|
-| Datadog | β
| β
| β
|
-| Grafana Cloud | β
| β
| β
|
-| Honeycomb | β
| β
| β
|
-| Chronosphere | β
| β
| |
-| Logz.io | β
| β
| β
|
-| qryn.cloud | β
| β
| β
|
-| OpsVerse | β
| β
| β
|
-| Dynatrace | β
| β
| β
|
-| AWS S3 | β
| β
| β
|
-| Google Cloud Monitoring | β
| | β
|
-| Google Cloud Storage | β
| | β
|
-| Azure Blob Storage | β
| | β
|
-| Splunk | β
| | |
-| Lightstep | β
| | |
-| Sentry | β
| | |
-| Axiom | β
| | β
|
-| Sumo Logic | β
| β
| β
|
-| Coralogix | β
| β
| β
|
+
+| | Traces | Metrics | Logs |
+| ------------------------- | -------- | --------- | ------ |
+| New Relic | β
| β
| β
|
+| Datadog | β
| β
| β
|
+| Grafana Cloud | β
| β
| β
|
+| Honeycomb | β
| β
| β
|
+| Chronosphere | β
| β
| |
+| Logz.io | β
| β
| β
|
+| qryn.cloud | β
| β
| β
|
+| OpsVerse | β
| β
| β
|
+| Dynatrace | β
| β
| β
|
+| AWS S3 | β
| β
| β
|
+| Google Cloud Monitoring | β
| | β
|
+| Google Cloud Storage | β
| | β
|
+| Azure Blob Storage | β
| | β
|
+| Splunk | β
| | |
+| Lightstep | β
| | |
+| Sentry | β
| | |
+| Axiom | β
| | β
|
+| Sumo Logic | β
| β
| β
|
+| Coralogix | β
| β
| β
|
### Open Source
+
| | Traces | Metrics | Logs |
-| ------------- | ------ | ------- | ---- |
+| --------------- | -------- | --------- | ------ |
| Prometheus | | β
| |
| Tempo | β
| | |
| Loki | | | β
|
diff --git a/api/config/crd/bases/odigos.io_collectorsgroups.yaml b/api/config/crd/bases/odigos.io_collectorsgroups.yaml
index b8dca11fc5..e278eb3be7 100644
--- a/api/config/crd/bases/odigos.io_collectorsgroups.yaml
+++ b/api/config/crd/bases/odigos.io_collectorsgroups.yaml
@@ -48,20 +48,32 @@ spec:
This can be used to resolve conflicting ports when a collector is using the host network.
format: int32
type: integer
- memorySettings:
+ resourcesSettings:
description: |-
- Memory settings for the collectors group.
+ Resources [memory/cpu] settings for the collectors group.
these settings are used to protect the collectors instances from:
- running out of memory and being killed by the k8s OOM killer
- consuming all available memory on the node which can lead to node instability
- pushing back pressure to the instrumented applications
properties:
+ cpuLimitMillicores:
+ description: |-
+ CPU resource limit to be used on the pod template.
+ it will be embedded in the as a resource limit of the form "cpu: m"
+ type: integer
+ cpuRequestMillicores:
+ description: |-
+ CPU resource request to be used on the pod template.
+ it will be embedded in the as a resource request of the form "cpu: m"
+ type: integer
gomemlimitMiB:
description: |-
the GOMEMLIMIT environment variable value for the collector pod.
this is when go runtime will start garbage collection.
it is recommended to be set to 80% of the hard limit of the memory limiter.
type: integer
+ maxReplicas:
+ type: integer
memoryLimitMiB:
description: |-
This option sets the limit on the memory usage of the collector.
@@ -92,7 +104,13 @@ spec:
MemoryRequestMiB is the memory resource request to be used on the pod template.
it will be embedded in the as a resource request of the form "memory: Mi"
type: integer
+ minReplicas:
+ description: Minumum + Maximum number of replicas for the collector
+ - these relevant only for gateway.
+ type: integer
required:
+ - cpuLimitMillicores
+ - cpuRequestMillicores
- gomemlimitMiB
- memoryLimitMiB
- memoryLimiterLimitMiB
@@ -106,7 +124,7 @@ spec:
type: string
required:
- collectorOwnMetricsPort
- - memorySettings
+ - resourcesSettings
- role
type: object
status:
diff --git a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupmemorysettings.go b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupmemorysettings.go
deleted file mode 100644
index 4bd5e45d61..0000000000
--- a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupmemorysettings.go
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
-Copyright 2022.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-// Code generated by applyconfiguration-gen. DO NOT EDIT.
-
-package v1alpha1
-
-// CollectorsGroupMemorySettingsApplyConfiguration represents a declarative configuration of the CollectorsGroupMemorySettings type for use
-// with apply.
-type CollectorsGroupMemorySettingsApplyConfiguration struct {
- MemoryRequestMiB *int `json:"memoryRequestMiB,omitempty"`
- MemoryLimitMiB *int `json:"memoryLimitMiB,omitempty"`
- MemoryLimiterLimitMiB *int `json:"memoryLimiterLimitMiB,omitempty"`
- MemoryLimiterSpikeLimitMiB *int `json:"memoryLimiterSpikeLimitMiB,omitempty"`
- GomemlimitMiB *int `json:"gomemlimitMiB,omitempty"`
-}
-
-// CollectorsGroupMemorySettingsApplyConfiguration constructs a declarative configuration of the CollectorsGroupMemorySettings type for use with
-// apply.
-func CollectorsGroupMemorySettings() *CollectorsGroupMemorySettingsApplyConfiguration {
- return &CollectorsGroupMemorySettingsApplyConfiguration{}
-}
-
-// WithMemoryRequestMiB sets the MemoryRequestMiB field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the MemoryRequestMiB field is set to the value of the last call.
-func (b *CollectorsGroupMemorySettingsApplyConfiguration) WithMemoryRequestMiB(value int) *CollectorsGroupMemorySettingsApplyConfiguration {
- b.MemoryRequestMiB = &value
- return b
-}
-
-// WithMemoryLimitMiB sets the MemoryLimitMiB field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the MemoryLimitMiB field is set to the value of the last call.
-func (b *CollectorsGroupMemorySettingsApplyConfiguration) WithMemoryLimitMiB(value int) *CollectorsGroupMemorySettingsApplyConfiguration {
- b.MemoryLimitMiB = &value
- return b
-}
-
-// WithMemoryLimiterLimitMiB sets the MemoryLimiterLimitMiB field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the MemoryLimiterLimitMiB field is set to the value of the last call.
-func (b *CollectorsGroupMemorySettingsApplyConfiguration) WithMemoryLimiterLimitMiB(value int) *CollectorsGroupMemorySettingsApplyConfiguration {
- b.MemoryLimiterLimitMiB = &value
- return b
-}
-
-// WithMemoryLimiterSpikeLimitMiB sets the MemoryLimiterSpikeLimitMiB field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the MemoryLimiterSpikeLimitMiB field is set to the value of the last call.
-func (b *CollectorsGroupMemorySettingsApplyConfiguration) WithMemoryLimiterSpikeLimitMiB(value int) *CollectorsGroupMemorySettingsApplyConfiguration {
- b.MemoryLimiterSpikeLimitMiB = &value
- return b
-}
-
-// WithGomemlimitMiB sets the GomemlimitMiB field in the declarative configuration to the given value
-// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the GomemlimitMiB field is set to the value of the last call.
-func (b *CollectorsGroupMemorySettingsApplyConfiguration) WithGomemlimitMiB(value int) *CollectorsGroupMemorySettingsApplyConfiguration {
- b.GomemlimitMiB = &value
- return b
-}
diff --git a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupresourcessettings.go b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupresourcessettings.go
new file mode 100644
index 0000000000..635172f561
--- /dev/null
+++ b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupresourcessettings.go
@@ -0,0 +1,110 @@
+/*
+Copyright 2022.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// CollectorsGroupResourcesSettingsApplyConfiguration represents a declarative configuration of the CollectorsGroupResourcesSettings type for use
+// with apply.
+type CollectorsGroupResourcesSettingsApplyConfiguration struct {
+ MinReplicas *int `json:"minReplicas,omitempty"`
+ MaxReplicas *int `json:"maxReplicas,omitempty"`
+ MemoryRequestMiB *int `json:"memoryRequestMiB,omitempty"`
+ MemoryLimitMiB *int `json:"memoryLimitMiB,omitempty"`
+ CpuRequestMillicores *int `json:"cpuRequestMillicores,omitempty"`
+ CpuLimitMillicores *int `json:"cpuLimitMillicores,omitempty"`
+ MemoryLimiterLimitMiB *int `json:"memoryLimiterLimitMiB,omitempty"`
+ MemoryLimiterSpikeLimitMiB *int `json:"memoryLimiterSpikeLimitMiB,omitempty"`
+ GomemlimitMiB *int `json:"gomemlimitMiB,omitempty"`
+}
+
+// CollectorsGroupResourcesSettingsApplyConfiguration constructs a declarative configuration of the CollectorsGroupResourcesSettings type for use with
+// apply.
+func CollectorsGroupResourcesSettings() *CollectorsGroupResourcesSettingsApplyConfiguration {
+ return &CollectorsGroupResourcesSettingsApplyConfiguration{}
+}
+
+// WithMinReplicas sets the MinReplicas field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MinReplicas field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithMinReplicas(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.MinReplicas = &value
+ return b
+}
+
+// WithMaxReplicas sets the MaxReplicas field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MaxReplicas field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithMaxReplicas(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.MaxReplicas = &value
+ return b
+}
+
+// WithMemoryRequestMiB sets the MemoryRequestMiB field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MemoryRequestMiB field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithMemoryRequestMiB(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.MemoryRequestMiB = &value
+ return b
+}
+
+// WithMemoryLimitMiB sets the MemoryLimitMiB field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MemoryLimitMiB field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithMemoryLimitMiB(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.MemoryLimitMiB = &value
+ return b
+}
+
+// WithCpuRequestMillicores sets the CpuRequestMillicores field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the CpuRequestMillicores field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithCpuRequestMillicores(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.CpuRequestMillicores = &value
+ return b
+}
+
+// WithCpuLimitMillicores sets the CpuLimitMillicores field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the CpuLimitMillicores field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithCpuLimitMillicores(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.CpuLimitMillicores = &value
+ return b
+}
+
+// WithMemoryLimiterLimitMiB sets the MemoryLimiterLimitMiB field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MemoryLimiterLimitMiB field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithMemoryLimiterLimitMiB(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.MemoryLimiterLimitMiB = &value
+ return b
+}
+
+// WithMemoryLimiterSpikeLimitMiB sets the MemoryLimiterSpikeLimitMiB field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MemoryLimiterSpikeLimitMiB field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithMemoryLimiterSpikeLimitMiB(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.MemoryLimiterSpikeLimitMiB = &value
+ return b
+}
+
+// WithGomemlimitMiB sets the GomemlimitMiB field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the GomemlimitMiB field is set to the value of the last call.
+func (b *CollectorsGroupResourcesSettingsApplyConfiguration) WithGomemlimitMiB(value int) *CollectorsGroupResourcesSettingsApplyConfiguration {
+ b.GomemlimitMiB = &value
+ return b
+}
diff --git a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupspec.go b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupspec.go
index f4ff9a4605..0fdc93d964 100644
--- a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupspec.go
+++ b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/collectorsgroupspec.go
@@ -24,9 +24,9 @@ import (
// CollectorsGroupSpecApplyConfiguration represents a declarative configuration of the CollectorsGroupSpec type for use
// with apply.
type CollectorsGroupSpecApplyConfiguration struct {
- Role *v1alpha1.CollectorsGroupRole `json:"role,omitempty"`
- CollectorOwnMetricsPort *int32 `json:"collectorOwnMetricsPort,omitempty"`
- MemorySettings *CollectorsGroupMemorySettingsApplyConfiguration `json:"memorySettings,omitempty"`
+ Role *v1alpha1.CollectorsGroupRole `json:"role,omitempty"`
+ CollectorOwnMetricsPort *int32 `json:"collectorOwnMetricsPort,omitempty"`
+ ResourcesSettings *CollectorsGroupResourcesSettingsApplyConfiguration `json:"resourcesSettings,omitempty"`
}
// CollectorsGroupSpecApplyConfiguration constructs a declarative configuration of the CollectorsGroupSpec type for use with
@@ -51,10 +51,10 @@ func (b *CollectorsGroupSpecApplyConfiguration) WithCollectorOwnMetricsPort(valu
return b
}
-// WithMemorySettings sets the MemorySettings field in the declarative configuration to the given value
+// WithResourcesSettings sets the ResourcesSettings field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
-// If called multiple times, the MemorySettings field is set to the value of the last call.
-func (b *CollectorsGroupSpecApplyConfiguration) WithMemorySettings(value *CollectorsGroupMemorySettingsApplyConfiguration) *CollectorsGroupSpecApplyConfiguration {
- b.MemorySettings = value
+// If called multiple times, the ResourcesSettings field is set to the value of the last call.
+func (b *CollectorsGroupSpecApplyConfiguration) WithResourcesSettings(value *CollectorsGroupResourcesSettingsApplyConfiguration) *CollectorsGroupSpecApplyConfiguration {
+ b.ResourcesSettings = value
return b
}
diff --git a/api/generated/odigos/applyconfiguration/utils.go b/api/generated/odigos/applyconfiguration/utils.go
index 954bd9be8b..c4383287bf 100644
--- a/api/generated/odigos/applyconfiguration/utils.go
+++ b/api/generated/odigos/applyconfiguration/utils.go
@@ -41,8 +41,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &odigosv1alpha1.CollectorGatewayConfigurationApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("CollectorsGroup"):
return &odigosv1alpha1.CollectorsGroupApplyConfiguration{}
- case v1alpha1.SchemeGroupVersion.WithKind("CollectorsGroupMemorySettings"):
- return &odigosv1alpha1.CollectorsGroupMemorySettingsApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("CollectorsGroupResourcesSettings"):
+ return &odigosv1alpha1.CollectorsGroupResourcesSettingsApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("CollectorsGroupSpec"):
return &odigosv1alpha1.CollectorsGroupSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("CollectorsGroupStatus"):
diff --git a/api/odigos/v1alpha1/collectorsgroup_types.go b/api/odigos/v1alpha1/collectorsgroup_types.go
index a8187f53b3..5a674ee132 100644
--- a/api/odigos/v1alpha1/collectorsgroup_types.go
+++ b/api/odigos/v1alpha1/collectorsgroup_types.go
@@ -30,11 +30,15 @@ const (
CollectorsGroupRoleNodeCollector CollectorsGroupRole = CollectorsGroupRole(k8sconsts.CollectorsRoleNodeCollector)
)
-// The raw values of the memory settings for the collectors group.
+// The raw values to control the collectors group resources and behavior.
// any defaulting, validations and calculations should be done in the controllers
// that create this CR.
// Values will be used as is without any further processing.
-type CollectorsGroupMemorySettings struct {
+type CollectorsGroupResourcesSettings struct {
+
+ // Minumum + Maximum number of replicas for the collector - these relevant only for gateway.
+ MinReplicas *int `json:"minReplicas,omitempty"`
+ MaxReplicas *int `json:"maxReplicas,omitempty"`
// MemoryRequestMiB is the memory resource request to be used on the pod template.
// it will be embedded in the as a resource request of the form "memory: Mi"
@@ -47,6 +51,13 @@ type CollectorsGroupMemorySettings struct {
// so one can set this to the same value as the memory request or higher to allow for some buffer for bursts.
MemoryLimitMiB int `json:"memoryLimitMiB"`
+ // CPU resource request to be used on the pod template.
+ // it will be embedded in the as a resource request of the form "cpu: m"
+ CpuRequestMillicores int `json:"cpuRequestMillicores"`
+ // CPU resource limit to be used on the pod template.
+ // it will be embedded in the as a resource limit of the form "cpu: m"
+ CpuLimitMillicores int `json:"cpuLimitMillicores"`
+
// this parameter sets the "limit_mib" parameter in the memory limiter configuration for the collector.
// it is the hard limit after which a force garbage collection will be performed.
// this value will end up comparing against the go runtime reported heap Alloc value.
@@ -76,12 +87,12 @@ type CollectorsGroupSpec struct {
// This can be used to resolve conflicting ports when a collector is using the host network.
CollectorOwnMetricsPort int32 `json:"collectorOwnMetricsPort"`
- // Memory settings for the collectors group.
+ // Resources [memory/cpu] settings for the collectors group.
// these settings are used to protect the collectors instances from:
// - running out of memory and being killed by the k8s OOM killer
// - consuming all available memory on the node which can lead to node instability
// - pushing back pressure to the instrumented applications
- MemorySettings CollectorsGroupMemorySettings `json:"memorySettings"`
+ ResourcesSettings CollectorsGroupResourcesSettings `json:"resourcesSettings"`
}
// CollectorsGroupStatus defines the observed state of Collector
diff --git a/api/odigos/v1alpha1/zz_generated.deepcopy.go b/api/odigos/v1alpha1/zz_generated.deepcopy.go
index 07567069bf..f825166ae7 100644
--- a/api/odigos/v1alpha1/zz_generated.deepcopy.go
+++ b/api/odigos/v1alpha1/zz_generated.deepcopy.go
@@ -99,7 +99,7 @@ func (in *CollectorsGroup) DeepCopyInto(out *CollectorsGroup) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
- out.Spec = in.Spec
+ in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
@@ -154,16 +154,26 @@ func (in *CollectorsGroupList) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *CollectorsGroupMemorySettings) DeepCopyInto(out *CollectorsGroupMemorySettings) {
+func (in *CollectorsGroupResourcesSettings) DeepCopyInto(out *CollectorsGroupResourcesSettings) {
*out = *in
+ if in.MinReplicas != nil {
+ in, out := &in.MinReplicas, &out.MinReplicas
+ *out = new(int)
+ **out = **in
+ }
+ if in.MaxReplicas != nil {
+ in, out := &in.MaxReplicas, &out.MaxReplicas
+ *out = new(int)
+ **out = **in
+ }
}
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorsGroupMemorySettings.
-func (in *CollectorsGroupMemorySettings) DeepCopy() *CollectorsGroupMemorySettings {
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorsGroupResourcesSettings.
+func (in *CollectorsGroupResourcesSettings) DeepCopy() *CollectorsGroupResourcesSettings {
if in == nil {
return nil
}
- out := new(CollectorsGroupMemorySettings)
+ out := new(CollectorsGroupResourcesSettings)
in.DeepCopyInto(out)
return out
}
@@ -171,7 +181,7 @@ func (in *CollectorsGroupMemorySettings) DeepCopy() *CollectorsGroupMemorySettin
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CollectorsGroupSpec) DeepCopyInto(out *CollectorsGroupSpec) {
*out = *in
- out.MemorySettings = in.MemorySettings
+ in.ResourcesSettings.DeepCopyInto(&out.ResourcesSettings)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectorsGroupSpec.
diff --git a/assets/choose_apps.png b/assets/choose_apps.png
deleted file mode 100644
index 2f426df636..0000000000
Binary files a/assets/choose_apps.png and /dev/null differ
diff --git a/assets/choose_dest.png b/assets/choose_dest.png
deleted file mode 100644
index ae9d159958..0000000000
Binary files a/assets/choose_dest.png and /dev/null differ
diff --git a/assets/dests.png b/assets/dests.png
deleted file mode 100644
index e923d143be..0000000000
Binary files a/assets/dests.png and /dev/null differ
diff --git a/assets/hacktoberfest_tee.png b/assets/hacktoberfest_tee.png
deleted file mode 100644
index 41d5e5a01c..0000000000
Binary files a/assets/hacktoberfest_tee.png and /dev/null differ
diff --git a/assets/odigos-cover.jpg b/assets/odigos-cover.jpg
deleted file mode 100644
index c95e56cbc3..0000000000
Binary files a/assets/odigos-cover.jpg and /dev/null differ
diff --git a/assets/overview_page.png b/assets/overview_page.png
deleted file mode 100644
index faa88c1ab9..0000000000
Binary files a/assets/overview_page.png and /dev/null differ
diff --git a/autoscaler/controllers/gateway/configmap.go b/autoscaler/controllers/gateway/configmap.go
index 1547517e1f..1c4f0d7ff4 100644
--- a/autoscaler/controllers/gateway/configmap.go
+++ b/autoscaler/controllers/gateway/configmap.go
@@ -116,8 +116,8 @@ func syncConfigMap(dests *odigosv1.DestinationList, allProcessors *odigosv1.Proc
memoryLimiterConfiguration := config.GenericMap{
"check_interval": "1s",
- "limit_mib": gateway.Spec.MemorySettings.MemoryLimiterLimitMiB,
- "spike_limit_mib": gateway.Spec.MemorySettings.MemoryLimiterSpikeLimitMiB,
+ "limit_mib": gateway.Spec.ResourcesSettings.MemoryLimiterLimitMiB,
+ "spike_limit_mib": gateway.Spec.ResourcesSettings.MemoryLimiterSpikeLimitMiB,
}
processors := common.FilterAndSortProcessorsByOrderHint(allProcessors, odigosv1.CollectorsGroupRoleClusterGateway)
diff --git a/autoscaler/controllers/gateway/deployment.go b/autoscaler/controllers/gateway/deployment.go
index e15f98a45c..207d7ae65c 100644
--- a/autoscaler/controllers/gateway/deployment.go
+++ b/autoscaler/controllers/gateway/deployment.go
@@ -90,8 +90,18 @@ func patchDeployment(existing *appsv1.Deployment, desired *appsv1.Deployment, ct
func getDesiredDeployment(dests *odigosv1.DestinationList, configDataHash string,
gateway *odigosv1.CollectorsGroup, scheme *runtime.Scheme, imagePullSecrets []string, odigosVersion string) (*appsv1.Deployment, error) {
- requestMemoryQuantity := resource.MustParse(fmt.Sprintf("%dMi", gateway.Spec.MemorySettings.MemoryRequestMiB))
- limitMemoryQuantity := resource.MustParse(fmt.Sprintf("%dMi", gateway.Spec.MemorySettings.MemoryLimitMiB))
+ // request + limits for memory and cpu
+ requestMemoryQuantity := resource.MustParse(fmt.Sprintf("%dMi", gateway.Spec.ResourcesSettings.MemoryRequestMiB))
+ limitMemoryQuantity := resource.MustParse(fmt.Sprintf("%dMi", gateway.Spec.ResourcesSettings.MemoryLimitMiB))
+
+ requestCPU := resource.MustParse(fmt.Sprintf("%dm", gateway.Spec.ResourcesSettings.CpuRequestMillicores))
+ limitCPU := resource.MustParse(fmt.Sprintf("%dm", gateway.Spec.ResourcesSettings.CpuLimitMillicores))
+
+ // deployment replicas
+ var gatewayReplicas int32 = 1
+ if gateway.Spec.ResourcesSettings.MinReplicas != nil {
+ gatewayReplicas = int32(*gateway.Spec.ResourcesSettings.MinReplicas)
+ }
desiredDeployment := &appsv1.Deployment{
ObjectMeta: v1.ObjectMeta{
@@ -100,7 +110,7 @@ func getDesiredDeployment(dests *odigosv1.DestinationList, configDataHash string
Labels: ClusterCollectorGateway,
},
Spec: appsv1.DeploymentSpec{
- Replicas: intPtr(1),
+ Replicas: intPtr(gatewayReplicas),
Selector: &v1.LabelSelector{
MatchLabels: ClusterCollectorGateway,
},
@@ -159,7 +169,7 @@ func getDesiredDeployment(dests *odigosv1.DestinationList, configDataHash string
},
{
Name: "GOMEMLIMIT",
- Value: fmt.Sprintf("%dMiB", gateway.Spec.MemorySettings.GomemlimitMiB),
+ Value: fmt.Sprintf("%dMiB", gateway.Spec.ResourcesSettings.GomemlimitMiB),
},
},
SecurityContext: &corev1.SecurityContext{
@@ -190,9 +200,11 @@ func getDesiredDeployment(dests *odigosv1.DestinationList, configDataHash string
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: requestMemoryQuantity,
+ corev1.ResourceCPU: requestCPU,
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: limitMemoryQuantity,
+ corev1.ResourceCPU: limitCPU,
},
},
},
diff --git a/autoscaler/controllers/gateway/hpa.go b/autoscaler/controllers/gateway/hpa.go
index a718cde6a1..6887f9f9fb 100644
--- a/autoscaler/controllers/gateway/hpa.go
+++ b/autoscaler/controllers/gateway/hpa.go
@@ -22,11 +22,12 @@ import (
const (
memoryLimitPercentageForHPA = 75
+ cpuLimitPercentageForHPA = 75
)
var (
- minReplicas = intPtr(1)
- maxReplicas = int32(10)
+ defaultMinReplicas = intPtr(1)
+ defaultMaxReplicas = int32(10)
stabilizationWindowSeconds = intPtr(300) // cooldown period for scaling down
)
@@ -35,9 +36,24 @@ func syncHPA(gateway *odigosv1.CollectorsGroup, ctx context.Context, c client.Cl
var hpa client.Object
- memLimit := gateway.Spec.MemorySettings.GomemlimitMiB * memoryLimitPercentageForHPA / 100.0
+ // Memory metric calculation
+ memLimit := gateway.Spec.ResourcesSettings.GomemlimitMiB * memoryLimitPercentageForHPA / 100
metricQuantity := resource.MustParse(fmt.Sprintf("%dMi", memLimit))
+ // CPU metric calculation
+ cpuTargetMillicores := gateway.Spec.ResourcesSettings.CpuLimitMillicores * cpuLimitPercentageForHPA / 100
+ metricQuantityCPU := resource.MustParse(fmt.Sprintf("%dm", cpuTargetMillicores))
+
+ minReplicas := defaultMinReplicas
+ if gateway.Spec.ResourcesSettings.MinReplicas != nil && *gateway.Spec.ResourcesSettings.MinReplicas > 0 {
+ minReplicas = intPtr(int32(*gateway.Spec.ResourcesSettings.MinReplicas))
+ }
+
+ maxReplicas := defaultMaxReplicas
+ if gateway.Spec.ResourcesSettings.MaxReplicas != nil && *gateway.Spec.ResourcesSettings.MaxReplicas > 0 {
+ maxReplicas = int32(*gateway.Spec.ResourcesSettings.MaxReplicas)
+ }
+
switch {
case kubeVersion.LessThan(version.MustParse("1.23.0")):
hpa = &autoscalingv2beta1.HorizontalPodAutoscaler{
@@ -62,6 +78,13 @@ func syncHPA(gateway *odigosv1.CollectorsGroup, ctx context.Context, c client.Cl
TargetAverageValue: &metricQuantity,
},
},
+ {
+ Type: autoscalingv2beta1.ResourceMetricSourceType,
+ Resource: &autoscalingv2beta1.ResourceMetricSource{
+ Name: "cpu",
+ TargetAverageValue: &metricQuantityCPU,
+ },
+ },
},
},
}
@@ -91,6 +114,16 @@ func syncHPA(gateway *odigosv1.CollectorsGroup, ctx context.Context, c client.Cl
},
},
},
+ {
+ Type: autoscalingv2beta2.ResourceMetricSourceType,
+ Resource: &autoscalingv2beta2.ResourceMetricSource{
+ Name: "cpu",
+ Target: autoscalingv2beta2.MetricTarget{
+ Type: autoscalingv2beta2.AverageValueMetricType,
+ AverageValue: &metricQuantityCPU,
+ },
+ },
+ },
},
Behavior: &autoscalingv2beta2.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscalingv2beta2.HPAScalingRules{
@@ -125,6 +158,16 @@ func syncHPA(gateway *odigosv1.CollectorsGroup, ctx context.Context, c client.Cl
},
},
},
+ {
+ Type: autoscalingv2.ResourceMetricSourceType,
+ Resource: &autoscalingv2.ResourceMetricSource{
+ Name: "cpu",
+ Target: autoscalingv2.MetricTarget{
+ Type: autoscalingv2.AverageValueMetricType,
+ AverageValue: &metricQuantityCPU,
+ },
+ },
+ },
},
Behavior: &autoscalingv2.HorizontalPodAutoscalerBehavior{
ScaleDown: &autoscalingv2.HPAScalingRules{
diff --git a/cli/cmd/resources/profiles/semconv-deprecated.yaml b/cli/cmd/resources/profiles/semconv-deprecated.yaml
new file mode 100644
index 0000000000..f4925ae38a
--- /dev/null
+++ b/cli/cmd/resources/profiles/semconv-deprecated.yaml
@@ -0,0 +1,19 @@
+apiVersion: actions.odigos.io/v1alpha1
+kind: RenameAttribute
+metadata:
+ name: semconv
+spec:
+ actionName: "semconv"
+ notes: "Auto generated rule from semconv profile. Do not edit."
+ renames:
+ net.peer.address: network.peer.address
+ net.local.address: network.local.address
+ net.peer.ip: network.peer.address
+ net.peer.port: network.peer.port
+ net.host.ip: network.local.address
+ net.host.name: server.address
+ net.host.port: server.port
+ net.transport: network.transport
+ db.operation: db.operation.name
+ signals:
+ - TRACES
\ No newline at end of file
diff --git a/cli/cmd/resources/profiles/semconv.yaml b/cli/cmd/resources/profiles/semconv.yaml
index 450de49409..6f872d391f 100644
--- a/cli/cmd/resources/profiles/semconv.yaml
+++ b/cli/cmd/resources/profiles/semconv.yaml
@@ -1,19 +1,63 @@
-apiVersion: actions.odigos.io/v1alpha1
-kind: RenameAttribute
+apiVersion: odigos.io/v1alpha1
+kind: Processor
metadata:
name: semconv
+ namespace: odigos-system
spec:
- actionName: "semconv"
+ type: attributes
+ processorName: semconv-attributes
notes: "Auto generated rule from semconv profile. Do not edit."
- renames:
- net.peer.address: network.peer.address
- net.local.address: network.local.address
- net.peer.ip: network.peer.address
- net.peer.port: network.peer.port
- net.host.ip: network.local.address
- net.host.name: server.address
- net.host.port: server.port
- net.transport: network.transport
- db.operation: db.operation.name
+ processorConfig:
+ actions:
+ - key: network.peer.address
+ from_attribute: net.peer.address
+ action: upsert
+ - key: net.peer.address
+ action: delete
+ - key: network.local.address
+ from_attribute: net.local.address
+ action: upsert
+ - key: net.local.address
+ action: delete
+ - key: network.peer.address
+ from_attribute: net.peer.ip
+ action: upsert
+ - key: net.peer.ip
+ action: delete
+ - key: network.peer.port
+ from_attribute: net.peer.port
+ action: upsert
+ - key: net.peer.port
+ action: delete
+ - key: network.local.address
+ from_attribute: net.host.ip
+ action: upsert
+ - key: net.host.ip
+ action: delete
+ - key: server.address
+ from_attribute: net.host.name
+ action: upsert
+ - key: net.host.name
+ action: delete
+ - key: server.port
+ from_attribute: net.host.port
+ action: upsert
+ - key: net.host.port
+ action: delete
+ - key: network.transport
+ from_attribute: net.transport
+ action: upsert
+ - key: net.transport
+ action: delete
+ - key: db.operation.name
+ from_attribute: db.operation
+ action: upsert
+ - key: db.operation
+ action: delete
+
+
+
signals:
- TRACES
+ collectorRoles:
+ - CLUSTER_GATEWAY
\ No newline at end of file
diff --git a/common/config/last9.go b/common/config/last9.go
new file mode 100644
index 0000000000..938d4d4b4c
--- /dev/null
+++ b/common/config/last9.go
@@ -0,0 +1,60 @@
+package config
+
+import (
+ "errors"
+
+ "github.com/odigos-io/odigos/common"
+)
+
+const (
+ l9OtlpEndpointKey = "LAST9_OTLP_ENDPOINT"
+ l9OtlpAuthHeaderKey = "LAST9_OTLP_BASIC_AUTH_HEADER"
+)
+
+type Last9 struct{}
+
+func (m *Last9) DestType() common.DestinationType {
+ // DestinationType defined in common/dests.go
+ return common.Last9DestinationType
+}
+
+func (m *Last9) ModifyConfig(dest ExporterConfigurer, currentConfig *Config) error {
+ config := dest.GetConfig()
+ l9OtlpEndpoint, exists := config[l9OtlpEndpointKey]
+ if !exists {
+ return errors.New("Last9 OpenTelemetry Endpoint key(\"LAST9_OTLP_ENDPOINT\") not specified, Last9 will not be configured")
+ }
+
+ // to make sure that the exporter name is unique, we'll ask a ID from destination
+ exporterName := "otlp/last9-" + dest.GetID()
+ currentConfig.Exporters[exporterName] = GenericMap{
+ "endpoint": l9OtlpEndpoint,
+ "headers": GenericMap{
+ "Authorization": "${LAST9_OTLP_BASIC_AUTH_HEADER}",
+ },
+ }
+
+ // Modify the config here
+ if isTracingEnabled(dest) {
+ tracesPipelineName := "traces/last9-" + dest.GetID()
+ currentConfig.Service.Pipelines[tracesPipelineName] = Pipeline{
+ Exporters: []string{exporterName},
+ }
+ }
+
+ if isMetricsEnabled(dest) {
+ metricsPipelineName := "metrics/last9-" + dest.GetID()
+ currentConfig.Service.Pipelines[metricsPipelineName] = Pipeline{
+ Exporters: []string{exporterName},
+ }
+ }
+
+ if isLoggingEnabled(dest) {
+ logsPipelineName := "logs/last9-" + dest.GetID()
+ currentConfig.Service.Pipelines[logsPipelineName] = Pipeline{
+ Exporters: []string{exporterName},
+ }
+ }
+
+ return nil
+}
diff --git a/common/config/root.go b/common/config/root.go
index b619cda4e6..643cd55ec5 100644
--- a/common/config/root.go
+++ b/common/config/root.go
@@ -16,7 +16,7 @@ const (
var availableConfigers = []Configer{
&Middleware{}, &Honeycomb{}, &GrafanaCloudPrometheus{}, &GrafanaCloudTempo{},
- &GrafanaCloudLoki{}, &Datadog{}, &NewRelic{}, &Logzio{}, &Prometheus{},
+ &GrafanaCloudLoki{}, &Datadog{}, &NewRelic{}, &Logzio{}, &Last9{}, &Prometheus{},
&Tempo{}, &Loki{}, &Jaeger{}, &GenericOTLP{}, &OTLPHttp{}, &Elasticsearch{}, &Quickwit{}, &Signoz{}, &Qryn{},
&OpsVerse{}, &Splunk{}, &Lightstep{}, &GoogleCloud{}, &GoogleCloudStorage{}, &Sentry{}, &AzureBlobStorage{},
&AWSS3{}, &Dynatrace{}, &Chronosphere{}, &ElasticAPM{}, &Axiom{}, &SumoLogic{}, &Coralogix{}, &Clickhouse{},
diff --git a/common/dests.go b/common/dests.go
index 014bc09dee..5c2f1dad5c 100644
--- a/common/dests.go
+++ b/common/dests.go
@@ -23,6 +23,7 @@ const (
GrafanaCloudTempoDestinationType DestinationType = "grafanacloudtempo"
HoneycombDestinationType DestinationType = "honeycomb"
JaegerDestinationType DestinationType = "jaeger"
+ Last9DestinationType DestinationType = "last9"
LightstepDestinationType DestinationType = "lightstep"
LogzioDestinationType DestinationType = "logzio"
LokiDestinationType DestinationType = "loki"
diff --git a/common/odigos_config.go b/common/odigos_config.go
index d6434dda4e..196cb7641e 100644
--- a/common/odigos_config.go
+++ b/common/odigos_config.go
@@ -9,11 +9,28 @@ type CollectorNodeConfiguration struct {
}
type CollectorGatewayConfiguration struct {
+ // MinReplicas is the number of replicas for the cluster gateway collector deployment.
+ // Also set the minReplicas for the HPA to this value.
+ MinReplicas int `json:"minReplicas,omitempty"`
+
+ // MaxReplicas set the maxReplicas for the HPA to this value.
+ MaxReplicas int `json:"maxReplicas,omitempty"`
+
// RequestMemoryMiB is the memory request for the cluster gateway collector deployment.
// it will be embedded in the deployment as a resource request of the form "memory: Mi"
// default value is 500Mi
RequestMemoryMiB int `json:"requestMemoryMiB,omitempty"`
+ // RequestCPUm is the CPU request for the cluster gateway collector deployment.
+ // it will be embedded in the deployment as a resource request of the form "cpu: m"
+ // default value is 500m
+ RequestCPUm int `json:"requestCPUm,omitempty"`
+
+ // LimitCPUm is the CPU limit for the cluster gateway collector deployment.
+ // it will be embedded in the deployment as a resource limit of the form "cpu: m"
+ // default value is 1000m
+ LimitCPUm int `json:"limitCPUm,omitempty"`
+
// this parameter sets the "limit_mib" parameter in the memory limiter configuration for the collector gateway.
// it is the hard limit after which a force garbage collection will be performed.
// if not set, it will be 50Mi below the memory request.
@@ -32,19 +49,19 @@ type CollectorGatewayConfiguration struct {
// OdigosConfiguration defines the desired state of OdigosConfiguration
type OdigosConfiguration struct {
- ConfigVersion int `json:"configVersion"`
- TelemetryEnabled bool `json:"telemetryEnabled,omitempty"`
- OpenshiftEnabled bool `json:"openshiftEnabled,omitempty"`
- IgnoredNamespaces []string `json:"ignoredNamespaces,omitempty"`
- IgnoredContainers []string `json:"ignoredContainers,omitempty"`
- Psp bool `json:"psp,omitempty"`
- ImagePrefix string `json:"imagePrefix,omitempty"`
- OdigletImage string `json:"odigletImage,omitempty"`
- InstrumentorImage string `json:"instrumentorImage,omitempty"`
- AutoscalerImage string `json:"autoscalerImage,omitempty"`
- CollectorGateway *CollectorGatewayConfiguration `json:"collectorGateway,omitempty"`
- CollectorNode *CollectorNodeConfiguration `json:"collectorNode,omitempty"`
- Profiles []ProfileName `json:"profiles,omitempty"`
+ ConfigVersion int `json:"configVersion"`
+ TelemetryEnabled bool `json:"telemetryEnabled,omitempty"`
+ OpenshiftEnabled bool `json:"openshiftEnabled,omitempty"`
+ IgnoredNamespaces []string `json:"ignoredNamespaces,omitempty"`
+ IgnoredContainers []string `json:"ignoredContainers,omitempty"`
+ Psp bool `json:"psp,omitempty"`
+ ImagePrefix string `json:"imagePrefix,omitempty"`
+ OdigletImage string `json:"odigletImage,omitempty"`
+ InstrumentorImage string `json:"instrumentorImage,omitempty"`
+ AutoscalerImage string `json:"autoscalerImage,omitempty"`
+ CollectorGateway *CollectorGatewayConfiguration `json:"collectorGateway,omitempty"`
+ CollectorNode *CollectorNodeConfiguration `json:"collectorNode,omitempty"`
+ Profiles []ProfileName `json:"profiles,omitempty"`
// this is internal currently, and is not exposed on the CLI / helm
// used for odigos enterprise
diff --git a/destinations/data/last9.yaml b/destinations/data/last9.yaml
new file mode 100644
index 0000000000..a345a4b0ce
--- /dev/null
+++ b/destinations/data/last9.yaml
@@ -0,0 +1,31 @@
+apiVersion: internal.odigos.io/v1beta1
+kind: Destination
+metadata:
+ type: last9
+ displayName: Last9
+ category: managed
+spec:
+ image: last9.svg
+ signals:
+ traces:
+ supported: true
+ metrics:
+ supported: true
+ logs:
+ supported: true
+ fields:
+ - name: LAST9_OTLP_ENDPOINT
+ displayName: Last9 OpenTelemetry Endpoint
+ componentType: input
+ componentProps:
+ type: text
+ required: true
+ tooltip: 'Last9 OpenTelemetry Endpoint. Can be found at https://app.last9.io/integrations?category=all&integration=OpenTelemetry'
+ - name: LAST9_OTLP_BASIC_AUTH_HEADER
+ displayName: Basic Auth Header
+ componentType: input
+ secret: true
+ componentProps:
+ type: password
+ required: true
+ placeholder: "Basic ..."
diff --git a/destinations/logos/last9.svg b/destinations/logos/last9.svg
new file mode 100644
index 0000000000..fd6c2b639b
--- /dev/null
+++ b/destinations/logos/last9.svg
@@ -0,0 +1 @@
+
diff --git a/docs/backends/last9.mdx b/docs/backends/last9.mdx
new file mode 100644
index 0000000000..9f26d42a87
--- /dev/null
+++ b/docs/backends/last9.mdx
@@ -0,0 +1,72 @@
+---
+title: "Last9"
+---
+
+## Obtaining Last9 OpenTelemetry Endpoint and Basic Auth Header
+
+[Click here](https://app.last9.io/integrations?category=all&integration=OpenTelemetry) to visit the Last9 OpenTelemetry integration page.
+
+
+
+
+
+## Configuring Last9 Backend
+
+- **Endpoint** - Last9 OpenTelemetry Endpoint obtained in above step.
+- **Authorization Header**: Last9 OpenTelemetry Basic Auth Header obtained in above step.
+
+## Adding a Destination to Odigos
+
+Odigos makes it simple to add and configure destinations, allowing you to select the specific signals [traces/logs/metrics] that you want to send to each destination. There are two primary methods for configuring destinations in Odigos:
+
+1. **Using the UI**
+ To add a destination via the UI, follow these steps:
+ - Use the Odigos CLI to access the UI: [Odigos UI](https://docs.odigos.io/cli/odigos_ui)
+ ```bash
+ odigos ui
+ ```
+- In the left sidebar, navigate to the `Destination` page.
+
+- Click `Add New Destination`
+
+- Select `Last9` and follow the on-screen instructions.
+
+
+
+2. **Using kubernetes manifests**
+
+Save the YAML below to a file (e.g., `destination.yaml`) and apply it using `kubectl`:
+
+```bash
+kubectl apply -f destination.yaml
+```
+
+
+```yaml
+apiVersion: odigos.io/v1alpha1
+kind: Destination
+metadata:
+ name: last9-example
+ namespace: odigos-system
+spec:
+ data:
+ LAST9_OTLP_ENDPOINT:
+ destinationName: last9
+ secretRef:
+ name: last9-secret
+ signals:
+ - TRACES
+ - METRICS
+ - LOGS
+ type: last9
+
+---
+apiVersion: v1
+data:
+ LAST9_OTLP_BASIC_AUTH_HEADER:
+kind: Secret
+metadata:
+ name: last9-secret
+ namespace: odigos-system
+type: Opaque
+```
diff --git a/docs/images/choose_apps.png b/docs/images/choose_apps.png
deleted file mode 100644
index f511209036..0000000000
Binary files a/docs/images/choose_apps.png and /dev/null differ
diff --git a/docs/images/choose_dest.png b/docs/images/choose_dest.png
deleted file mode 100644
index ae9d159958..0000000000
Binary files a/docs/images/choose_dest.png and /dev/null differ
diff --git a/docs/images/choose_jaeger.png b/docs/images/choose_jaeger.png
deleted file mode 100644
index 73bdb5ec24..0000000000
Binary files a/docs/images/choose_jaeger.png and /dev/null differ
diff --git a/docs/images/jaeger_connection.png b/docs/images/jaeger_connection.png
deleted file mode 100644
index 5d2faeaa3f..0000000000
Binary files a/docs/images/jaeger_connection.png and /dev/null differ
diff --git a/docs/images/last9.png b/docs/images/last9.png
new file mode 100644
index 0000000000..000f04cf1f
Binary files /dev/null and b/docs/images/last9.png differ
diff --git a/docs/images/observability_pipeline.png b/docs/images/observability_pipeline.png
deleted file mode 100644
index 1d49d23413..0000000000
Binary files a/docs/images/observability_pipeline.png and /dev/null differ
diff --git a/docs/images/ui_choose_apps.png b/docs/images/ui_choose_apps.png
new file mode 100644
index 0000000000..ab92110119
Binary files /dev/null and b/docs/images/ui_choose_apps.png differ
diff --git a/docs/images/ui_choose_dest.png b/docs/images/ui_choose_dest.png
new file mode 100644
index 0000000000..707a301f4d
Binary files /dev/null and b/docs/images/ui_choose_dest.png differ
diff --git a/docs/images/ui_jaeger_connection.png b/docs/images/ui_jaeger_connection.png
new file mode 100644
index 0000000000..b957e9232e
Binary files /dev/null and b/docs/images/ui_jaeger_connection.png differ
diff --git a/docs/images/ui_overview.png b/docs/images/ui_overview.png
new file mode 100644
index 0000000000..4ccd20f5b2
Binary files /dev/null and b/docs/images/ui_overview.png differ
diff --git a/docs/mint.json b/docs/mint.json
index b00e7b5b37..32718b0c88 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -125,7 +125,7 @@
"instrumentations/java/java",
"instrumentations/java/ebpf"
]
- }
+ }
]
},
{
@@ -218,6 +218,7 @@
"backends/honeycomb",
"backends/jaeger",
"backends/lightstep",
+ "backends/last9",
"backends/logzio",
"backends/loki",
"backends/newrelic",
diff --git a/docs/quickstart/building-a-pipeline.mdx b/docs/quickstart/building-a-pipeline.mdx
index 69fcf3fe7e..9756c6e3e7 100644
--- a/docs/quickstart/building-a-pipeline.mdx
+++ b/docs/quickstart/building-a-pipeline.mdx
@@ -8,7 +8,7 @@ sidebarTitle: "Building a pipeline"
You should now see the following page:
-
+
Select all the applications in the `default` namespace and click `Next`.
@@ -22,7 +22,7 @@ In the next step, you will be asked to select a destination for your traces.
Scroll down to the self hosted destinations and click on **Jaeger**.
-
+
#### Connection details
@@ -34,14 +34,14 @@ You will now be asked to provide the connection details for your Jaeger instance
Enter any name you want for the destination, and enter `jaeger.tracing:4317` as the host.
-
+
-**That's it!** You can now click `Next` and finish the wizard.
+**That's it!** You can now click `Done` and finish the wizard.
Odigos will now instrument your selected applications and deploy the nessesary OpenTelemetry collectors.
The following page will show an overview of your observability pipeline:
-
+
\ No newline at end of file
diff --git a/frontend/graph/conversions.go b/frontend/graph/conversions.go
index 71b7df4fa1..a4cb29a9cc 100644
--- a/frontend/graph/conversions.go
+++ b/frontend/graph/conversions.go
@@ -96,9 +96,16 @@ func instrumentedApplicationToActualSource(instrumentedApp v1alpha1.Instrumented
// Map the container runtime details
var containers []*gqlmodel.SourceContainerRuntimeDetails
for _, container := range instrumentedApp.Spec.RuntimeDetails {
+ var otherAgentName *string
+ if container.OtherAgent != nil {
+ otherAgentName = &container.OtherAgent.Name
+ }
+
containers = append(containers, &gqlmodel.SourceContainerRuntimeDetails{
- ContainerName: container.ContainerName,
- Language: string(container.Language),
+ ContainerName: container.ContainerName,
+ Language: string(container.Language),
+ RuntimeVersion: container.RuntimeVersion,
+ OtherAgent: otherAgentName,
})
}
diff --git a/frontend/graph/generated.go b/frontend/graph/generated.go
index 84cbb2e834..12a4888743 100644
--- a/frontend/graph/generated.go
+++ b/frontend/graph/generated.go
@@ -339,8 +339,10 @@ type ComplexityRoot struct {
}
SourceContainerRuntimeDetails struct {
- ContainerName func(childComplexity int) int
- Language func(childComplexity int) int
+ ContainerName func(childComplexity int) int
+ Language func(childComplexity int) int
+ OtherAgent func(childComplexity int) int
+ RuntimeVersion func(childComplexity int) int
}
SupportedSignals struct {
@@ -1714,6 +1716,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.SourceContainerRuntimeDetails.Language(childComplexity), true
+ case "SourceContainerRuntimeDetails.otherAgent":
+ if e.complexity.SourceContainerRuntimeDetails.OtherAgent == nil {
+ break
+ }
+
+ return e.complexity.SourceContainerRuntimeDetails.OtherAgent(childComplexity), true
+
+ case "SourceContainerRuntimeDetails.runtimeVersion":
+ if e.complexity.SourceContainerRuntimeDetails.RuntimeVersion == nil {
+ break
+ }
+
+ return e.complexity.SourceContainerRuntimeDetails.RuntimeVersion(childComplexity), true
+
case "SupportedSignals.logs":
if e.complexity.SupportedSignals.Logs == nil {
break
@@ -6391,6 +6407,10 @@ func (ec *executionContext) fieldContext_InstrumentedApplicationDetails_containe
return ec.fieldContext_SourceContainerRuntimeDetails_containerName(ctx, field)
case "language":
return ec.fieldContext_SourceContainerRuntimeDetails_language(ctx, field)
+ case "runtimeVersion":
+ return ec.fieldContext_SourceContainerRuntimeDetails_runtimeVersion(ctx, field)
+ case "otherAgent":
+ return ec.fieldContext_SourceContainerRuntimeDetails_otherAgent(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type SourceContainerRuntimeDetails", field.Name)
},
@@ -10483,6 +10503,91 @@ func (ec *executionContext) fieldContext_SourceContainerRuntimeDetails_language(
return fc, nil
}
+func (ec *executionContext) _SourceContainerRuntimeDetails_runtimeVersion(ctx context.Context, field graphql.CollectedField, obj *model.SourceContainerRuntimeDetails) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_SourceContainerRuntimeDetails_runtimeVersion(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.RuntimeVersion, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(string)
+ fc.Result = res
+ return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_SourceContainerRuntimeDetails_runtimeVersion(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "SourceContainerRuntimeDetails",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _SourceContainerRuntimeDetails_otherAgent(ctx context.Context, field graphql.CollectedField, obj *model.SourceContainerRuntimeDetails) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_SourceContainerRuntimeDetails_otherAgent(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.OtherAgent, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(*string)
+ fc.Result = res
+ return ec.marshalOString2αstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_SourceContainerRuntimeDetails_otherAgent(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "SourceContainerRuntimeDetails",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _SupportedSignals_traces(ctx context.Context, field graphql.CollectedField, obj *model.SupportedSignals) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_SupportedSignals_traces(ctx, field)
if err != nil {
@@ -15893,6 +15998,13 @@ func (ec *executionContext) _SourceContainerRuntimeDetails(ctx context.Context,
if out.Values[i] == graphql.Null {
out.Invalids++
}
+ case "runtimeVersion":
+ out.Values[i] = ec._SourceContainerRuntimeDetails_runtimeVersion(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ out.Invalids++
+ }
+ case "otherAgent":
+ out.Values[i] = ec._SourceContainerRuntimeDetails_otherAgent(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
diff --git a/frontend/graph/model/models_gen.go b/frontend/graph/model/models_gen.go
index 24f26f103e..417e1f88f4 100644
--- a/frontend/graph/model/models_gen.go
+++ b/frontend/graph/model/models_gen.go
@@ -475,8 +475,10 @@ type SingleSourceMetricsResponse struct {
}
type SourceContainerRuntimeDetails struct {
- ContainerName string `json:"containerName"`
- Language string `json:"language"`
+ ContainerName string `json:"containerName"`
+ Language string `json:"language"`
+ RuntimeVersion string `json:"runtimeVersion"`
+ OtherAgent *string `json:"otherAgent,omitempty"`
}
type TestConnectionResponse struct {
diff --git a/frontend/graph/schema.graphqls b/frontend/graph/schema.graphqls
index 8d3d231ae2..37a4beaa61 100644
--- a/frontend/graph/schema.graphqls
+++ b/frontend/graph/schema.graphqls
@@ -47,6 +47,8 @@ enum InstallationStatus {
type SourceContainerRuntimeDetails {
containerName: String!
language: String!
+ runtimeVersion: String!
+ otherAgent: String
}
type InstrumentationOption {
@@ -203,11 +205,7 @@ type ComputePlatform {
computePlatformType: ComputePlatformType!
k8sActualNamespace(name: String!): K8sActualNamespace
k8sActualNamespaces: [K8sActualNamespace]!
- k8sActualSource(
- name: String
- namespace: String
- kind: String
- ): K8sActualSource
+ k8sActualSource(name: String, namespace: String, kind: String): K8sActualSource
k8sActualSources: [K8sActualSource]!
destinations: [Destination!]!
actions: [IcaInstanceResponse!]!
@@ -456,29 +454,16 @@ type Query {
type Mutation {
createNewDestination(destination: DestinationInput!): Destination!
persistK8sNamespace(namespace: PersistNamespaceItemInput!): Boolean!
- persistK8sSources(
- namespace: String!
- sources: [PersistNamespaceSourceInput!]!
- ): Boolean!
- testConnectionForDestination(
- destination: DestinationInput!
- ): TestConnectionResponse!
- updateK8sActualSource(
- sourceId: K8sSourceId!
- patchSourceRequest: PatchSourceRequestInput!
- ): Boolean!
+ persistK8sSources(namespace: String!, sources: [PersistNamespaceSourceInput!]!): Boolean!
+ testConnectionForDestination(destination: DestinationInput!): TestConnectionResponse!
+ updateK8sActualSource(sourceId: K8sSourceId!, patchSourceRequest: PatchSourceRequestInput!): Boolean!
updateDestination(id: ID!, destination: DestinationInput!): Destination!
deleteDestination(id: ID!): Boolean!
createAction(action: ActionInput!): Action!
updateAction(id: ID!, action: ActionInput!): Action!
deleteAction(id: ID!, actionType: String!): Boolean!
- createInstrumentationRule(
- instrumentationRule: InstrumentationRuleInput!
- ): InstrumentationRule!
- updateInstrumentationRule(
- ruleId: ID!
- instrumentationRule: InstrumentationRuleInput!
- ): InstrumentationRule!
+ createInstrumentationRule(instrumentationRule: InstrumentationRuleInput!): InstrumentationRule!
+ updateInstrumentationRule(ruleId: ID!, instrumentationRule: InstrumentationRuleInput!): InstrumentationRule!
deleteInstrumentationRule(ruleId: ID!): Boolean!
}
diff --git a/frontend/graph/schema.resolvers.go b/frontend/graph/schema.resolvers.go
index edcf25b4d1..b3c4b18a96 100644
--- a/frontend/graph/schema.resolvers.go
+++ b/frontend/graph/schema.resolvers.go
@@ -280,7 +280,6 @@ func (r *destinationResolver) Type(ctx context.Context, obj *model.Destination)
// Conditions is the resolver for the conditions field.
func (r *destinationResolver) Conditions(ctx context.Context, obj *model.Destination) ([]*model.Condition, error) {
-
conditions := make([]*model.Condition, 0, len(obj.Conditions))
for _, c := range obj.Conditions {
// Convert LastTransitionTime to a string pointer if it's not nil
diff --git a/frontend/webapp/components/common/card-details/index.tsx b/frontend/webapp/components/common/card-details/index.tsx
deleted file mode 100644
index 752e3bca08..0000000000
--- a/frontend/webapp/components/common/card-details/index.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-import styled from 'styled-components';
-import { Text } from '@/reuseable-components';
-import { ConfiguredFields } from '@/components';
-
-interface Props {
- title?: string;
- data: {
- title: string;
- tooltip?: string;
- value: string;
- }[];
-}
-
-const Container = styled.div`
- display: flex;
- flex-direction: column;
- padding: 16px 24px 24px 24px;
- flex-direction: column;
- align-items: flex-start;
- gap: 16px;
- align-self: stretch;
- border-radius: 24px;
- border: 1px solid ${({ theme }) => theme.colors.border};
-`;
-
-const TitleWrapper = styled.div``;
-
-export const CardDetails: React.FC = ({ title = 'Details', data }) => {
- return (
-
-
- {title}
-
-
-
- );
-};
diff --git a/frontend/webapp/components/common/configured-fields/index.tsx b/frontend/webapp/components/common/configured-fields/index.tsx
deleted file mode 100644
index e268efc7c1..0000000000
--- a/frontend/webapp/components/common/configured-fields/index.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import React, { Fragment } from 'react';
-import styled from 'styled-components';
-import { Text, Status, Tooltip } from '@/reuseable-components';
-import { MonitorsLegend } from '@/components/overview';
-import theme from '@/styles/theme';
-
-interface Detail {
- title: string;
- tooltip?: string;
- value: string;
-}
-
-interface Props {
- details: Detail[];
-}
-
-const ListContainer = styled.div`
- display: flex;
- flex-wrap: wrap;
- gap: 24px 40px;
-`;
-
-const ListItem = styled.div``;
-
-const ItemTitle = styled(Text)`
- color: ${({ theme }) => theme.text.grey};
- font-size: 10px;
- line-height: 16px;
-`;
-
-const ItemValue = styled(Text)`
- color: ${({ theme }) => theme.colors.text};
- font-size: 12px;
- line-height: 18px;
-`;
-
-export const ConfiguredFields: React.FC = ({ details }) => {
- const parseValue = (value: string) => {
- let str = '';
-
- try {
- const parsed = JSON.parse(value);
-
- // Handle arrays
- if (Array.isArray(parsed)) {
- str = parsed
- .map((item) => {
- if (typeof item === 'object' && item !== null) return `${item.key}: ${item.value}`;
- else return item;
- })
- .join(', ');
- }
-
- // Handle objects (non-array JSON objects)
- else if (typeof parsed === 'object' && parsed !== null) {
- str = Object.entries(parsed)
- .map(([key, val]) => `${key}: ${val}`)
- .join(', ');
- }
-
- // Should never reach this if it's a string (it will throw)
- else {
- str = value;
- }
- } catch (error) {
- str = value;
- }
-
- const strSplitted = str.split('\n');
-
- return strSplitted.map((str, idx) => (
-
- {str}
- {idx < strSplitted.length - 1 ?
: null}
-
- ));
- };
-
- const renderValue = (title: string, value: string) => {
- switch (title) {
- case 'Status':
- return ;
- case 'Monitors':
- return ;
- default:
- return {parseValue(value)};
- }
- };
-
- return (
-
- {details.map((detail, index) => (
-
-
- {detail.title}
-
- {renderValue(detail.title, detail.value)}
-
- ))}
-
- );
-};
diff --git a/frontend/webapp/components/common/index.ts b/frontend/webapp/components/common/index.ts
index a9076a4d90..00da52a3ea 100644
--- a/frontend/webapp/components/common/index.ts
+++ b/frontend/webapp/components/common/index.ts
@@ -1,3 +1 @@
-export * from './card-details';
-export * from './configured-fields';
export * from './dropdowns';
diff --git a/frontend/webapp/components/main/header/cp-title/index.tsx b/frontend/webapp/components/main/header/cp-title/index.tsx
index c7bf856d20..4ea8902b11 100644
--- a/frontend/webapp/components/main/header/cp-title/index.tsx
+++ b/frontend/webapp/components/main/header/cp-title/index.tsx
@@ -1,10 +1,11 @@
import React from 'react';
import Image from 'next/image';
import styled from 'styled-components';
+import { PlatformTypes } from '@/types';
import { Text } from '@/reuseable-components';
-interface PlatformProps {
- type: 'k8s' | 'vm';
+interface Props {
+ type: PlatformTypes;
}
const PlatformWrapper = styled.div`
@@ -28,21 +29,14 @@ const Title = styled(Text)`
color: ${({ theme }) => theme.colors.white};
`;
-const PlatformTitle: React.FC = ({ type }) => {
+const PlatformTitle: React.FC = ({ type }) => {
return (
-
+
-
- {type === 'k8s' ? 'Kubernetes Cluster' : 'Virtual Machine'}
-
+ {type === PlatformTypes.K8S ? 'Kubernetes Cluster' : 'Virtual Machine'}
);
diff --git a/frontend/webapp/components/main/header/index.tsx b/frontend/webapp/components/main/header/index.tsx
index f74f9aeb44..fbfb1be26b 100644
--- a/frontend/webapp/components/main/header/index.tsx
+++ b/frontend/webapp/components/main/header/index.tsx
@@ -1,32 +1,29 @@
import React from 'react';
import Image from 'next/image';
+import { FlexRow } from '@/styles';
import styled from 'styled-components';
+import { PlatformTypes } from '@/types';
import { PlatformTitle } from './cp-title';
import { useConnectionStore } from '@/store';
-import { Status } from '@/reuseable-components';
+import { ConnectionStatus } from '@/reuseable-components';
import { NotificationManager } from '@/components/notification';
interface MainHeaderProps {}
-const Flex = styled.div`
- display: flex;
- align-items: center;
-`;
-
-const HeaderContainer = styled(Flex)`
+const HeaderContainer = styled(FlexRow)`
width: 100%;
padding: 12px 0;
background-color: ${({ theme }) => theme.colors.darker_grey};
border-bottom: 1px solid rgba(249, 249, 249, 0.16);
`;
-const AlignLeft = styled(Flex)`
+const AlignLeft = styled(FlexRow)`
margin-right: auto;
margin-left: 32px;
gap: 16px;
`;
-const AlignRight = styled(Flex)`
+const AlignRight = styled(FlexRow)`
margin-left: auto;
margin-right: 32px;
gap: 16px;
@@ -39,8 +36,8 @@ export const MainHeader: React.FC = () => {
-
- {!connecting && }
+
+ {!connecting && }
diff --git a/frontend/webapp/containers/main/actions/action-drawer/build-card.ts b/frontend/webapp/containers/main/actions/action-drawer/build-card.ts
index 50953951c9..b53676ed25 100644
--- a/frontend/webapp/containers/main/actions/action-drawer/build-card.ts
+++ b/frontend/webapp/containers/main/actions/action-drawer/build-card.ts
@@ -1,4 +1,6 @@
+import { DISPLAY_TITLES } from '@/utils';
import type { ActionDataParsed } from '@/types';
+import { DataCardFieldTypes, type DataCardRow } from '@/reuseable-components';
const buildCard = (action: ActionDataParsed) => {
const {
@@ -19,19 +21,20 @@ const buildCard = (action: ActionDataParsed) => {
},
} = action;
- const arr = [
- { title: 'Type', value: type },
- { title: 'Status', value: String(!disabled) },
- { title: 'Monitors', value: signals.map((str) => str.toLowerCase()).join(', ') },
- { title: 'Name', value: actionName || 'N/A' },
- { title: 'Notes', value: notes || 'N/A' },
+ const arr: DataCardRow[] = [
+ { title: DISPLAY_TITLES.TYPE, value: type },
+ { type: DataCardFieldTypes.ACTIVE_STATUS, title: DISPLAY_TITLES.STATUS, value: String(!disabled) },
+ { title: DISPLAY_TITLES.NAME, value: actionName },
+ { title: DISPLAY_TITLES.NOTES, value: notes },
+ { type: DataCardFieldTypes.DIVIDER, width: '100%' },
+ { type: DataCardFieldTypes.MONITORS, title: DISPLAY_TITLES.SIGNALS_FOR_PROCESSING, value: signals.map((str) => str.toLowerCase()).join(', ') },
];
if (clusterAttributes) {
let str = '';
clusterAttributes.forEach(({ attributeName, attributeStringValue }, idx) => {
str += `${attributeName}: ${attributeStringValue}`;
- if (idx < clusterAttributes.length - 1) str += '\n';
+ if (idx < clusterAttributes.length - 1) str += ', ';
});
arr.push({ title: 'Attributes', value: str });
diff --git a/frontend/webapp/containers/main/actions/action-drawer/index.tsx b/frontend/webapp/containers/main/actions/action-drawer/index.tsx
index 9204193a28..d0b0936e7f 100644
--- a/frontend/webapp/containers/main/actions/action-drawer/index.tsx
+++ b/frontend/webapp/containers/main/actions/action-drawer/index.tsx
@@ -3,9 +3,9 @@ import buildCard from './build-card';
import { ActionFormBody } from '../';
import styled from 'styled-components';
import { useDrawerStore } from '@/store';
-import { CardDetails } from '@/components';
-import { ACTION, getActionIcon } from '@/utils';
+import { ACTION, DATA_CARDS, getActionIcon } from '@/utils';
import buildDrawerItem from './build-drawer-item';
+import { DataCard } from '@/reuseable-components';
import { useActionCRUD, useActionFormData } from '@/hooks';
import OverviewDrawer from '../../overview/overview-drawer';
import { ACTION_OPTIONS } from '../action-modal/action-options';
@@ -120,7 +120,7 @@ export const ActionDrawer: React.FC = () => {
/>
) : (
-
+
)}
);
diff --git a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx
index 517fe575f6..89360955a4 100644
--- a/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx
+++ b/frontend/webapp/containers/main/destinations/add-destination/configured-destinations-list/index.tsx
@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import Image from 'next/image';
import styled from 'styled-components';
+import { DeleteWarning } from '@/components';
import { IAppState, useAppStore } from '@/store';
-import { ConfiguredFields, DeleteWarning } from '@/components';
-import { Button, Divider, ExtendIcon, Text } from '@/reuseable-components';
import { OVERVIEW_ENTITY_TYPES, type ConfiguredDestination } from '@/types';
+import { Button, DataCardFields, Divider, ExtendIcon, Text } from '@/reuseable-components';
const Container = styled.div`
display: flex;
@@ -12,10 +12,10 @@ const Container = styled.div`
align-items: flex-start;
gap: 12px;
margin-top: 24px;
- align-self: stretch;
+ max-height: calc(100vh - 400px);
height: 100%;
- max-height: 548px;
- overflow-y: auto;
+ overflow-x: hidden;
+ overflow-y: scroll;
`;
const ListItem = styled.div`
@@ -38,9 +38,9 @@ const ListItemHeader = styled.div`
`;
const ListItemContent = styled.div`
- margin-left: 16px;
display: flex;
gap: 12px;
+ margin-left: 16px;
`;
const DestinationIconWrapper = styled.div`
@@ -135,8 +135,8 @@ const ConfiguredDestinationsListItem: React.FC<{ item: ConfiguredDestination; is
{expand && (
-
-
+
+
)}
diff --git a/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts b/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts
index c9868e1ea1..0cf3a2cd09 100644
--- a/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts
+++ b/frontend/webapp/containers/main/destinations/destination-drawer/build-card.ts
@@ -1,17 +1,19 @@
-import { ActualDestination, DestinationDetailsResponse, ExportedSignals } from '@/types';
-import { safeJsonParse } from '@/utils';
+import { DISPLAY_TITLES, safeJsonParse } from '@/utils';
+import { DataCardRow, DataCardFieldTypes } from '@/reuseable-components';
+import type { ActualDestination, DestinationDetailsResponse, ExportedSignals } from '@/types';
const buildMonitorsList = (exportedSignals: ExportedSignals): string =>
Object.keys(exportedSignals)
.filter((key) => exportedSignals[key])
- .join(', ') || 'N/A';
+ .join(', ');
const buildCard = (destination: ActualDestination, destinationTypeDetails: DestinationDetailsResponse['destinationTypeDetails']) => {
const { exportedSignals, destinationType, fields } = destination;
- const arr = [
- { title: 'Destination', value: destinationType.displayName },
- { title: 'Monitors', value: buildMonitorsList(exportedSignals) },
+ const arr: DataCardRow[] = [
+ { title: DISPLAY_TITLES.DESTINATION, value: destinationType.displayName },
+ { type: DataCardFieldTypes.MONITORS, title: DISPLAY_TITLES.MONITORS, value: buildMonitorsList(exportedSignals) },
+ { type: DataCardFieldTypes.DIVIDER, width: '100%' },
];
Object.entries(safeJsonParse>(fields, {})).map(([key, value]) => {
@@ -22,7 +24,7 @@ const buildCard = (destination: ActualDestination, destinationTypeDetails: Desti
arr.push({
title: found?.displayName || key,
- value: secret || value || 'N/A',
+ value: secret || value,
});
});
diff --git a/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx b/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx
index fba80220de..77884b9cc1 100644
--- a/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx
+++ b/frontend/webapp/containers/main/destinations/destination-drawer/index.tsx
@@ -1,13 +1,12 @@
import React, { useMemo, useState } from 'react';
-import { ACTION } from '@/utils';
+import { ACTION, DATA_CARDS } from '@/utils';
import buildCard from './build-card';
import styled from 'styled-components';
import { useDrawerStore } from '@/store';
-import { CardDetails } from '@/components';
import buildDrawerItem from './build-drawer-item';
-import { ConditionDetails } from '@/reuseable-components';
import OverviewDrawer from '../../overview/overview-drawer';
import { DestinationFormBody } from '../destination-form-body';
+import { ConditionDetails, DataCard } from '@/reuseable-components';
import { OVERVIEW_ENTITY_TYPES, type ActualDestination } from '@/types';
import { useDestinationCRUD, useDestinationFormData, useDestinationTypes } from '@/hooks';
@@ -138,7 +137,7 @@ export const DestinationDrawer: React.FC = () => {
) : (
-
+
)}
diff --git a/frontend/webapp/containers/main/destinations/destination-form-body/index.tsx b/frontend/webapp/containers/main/destinations/destination-form-body/index.tsx
index ab36f0ebb3..df8dbfbd71 100644
--- a/frontend/webapp/containers/main/destinations/destination-form-body/index.tsx
+++ b/frontend/webapp/containers/main/destinations/destination-form-body/index.tsx
@@ -12,7 +12,7 @@ interface Props {
formData: DestinationInput;
formErrors: Record;
validateForm: () => boolean;
- handleFormChange: (key: keyof DestinationInput | string, val: any) => void;
+ handleFormChange: (key: keyof DestinationInput, val: any) => void;
dynamicFields: DynamicField[];
setDynamicFields: Dispatch>;
}
diff --git a/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/build-card.ts b/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/build-card.ts
index b1f617598e..260a4ff6b3 100644
--- a/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/build-card.ts
+++ b/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/build-card.ts
@@ -1,13 +1,16 @@
+import { DISPLAY_TITLES } from '@/utils';
import type { InstrumentationRuleSpec } from '@/types';
+import { DataCardRow, DataCardFieldTypes } from '@/reuseable-components';
const buildCard = (rule: InstrumentationRuleSpec) => {
const { type, ruleName, notes, disabled, payloadCollection } = rule;
- const arr = [
- { title: 'Type', value: type || 'N/A' },
- { title: 'Status', value: String(!disabled) },
- { title: 'Name', value: ruleName || 'N/A' },
- { title: 'Notes', value: notes || 'N/A' },
+ const arr: DataCardRow[] = [
+ { title: DISPLAY_TITLES.TYPE, value: type },
+ { type: DataCardFieldTypes.ACTIVE_STATUS, title: DISPLAY_TITLES.STATUS, value: String(!disabled) },
+ { title: DISPLAY_TITLES.NAME, value: ruleName },
+ { title: DISPLAY_TITLES.NOTES, value: notes },
+ { type: DataCardFieldTypes.DIVIDER, width: '100%' },
];
if (payloadCollection) {
diff --git a/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx b/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx
index af379dfceb..7a5921f9da 100644
--- a/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx
+++ b/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx
@@ -3,8 +3,8 @@ import buildCard from './build-card';
import { RuleFormBody } from '../';
import styled from 'styled-components';
import { useDrawerStore } from '@/store';
-import { CardDetails } from '@/components';
-import { ACTION, getRuleIcon } from '@/utils';
+import { ACTION, DATA_CARDS, getRuleIcon } from '@/utils';
+import { DataCard } from '@/reuseable-components';
import buildDrawerItem from './build-drawer-item';
import { RULE_OPTIONS } from '../rule-modal/rule-options';
import OverviewDrawer from '../../overview/overview-drawer';
@@ -117,7 +117,7 @@ export const RuleDrawer: React.FC = () => {
/>
) : (
-
+
)}
);
diff --git a/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx b/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx
index d1a4cc9fe5..427002838e 100644
--- a/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx
+++ b/frontend/webapp/containers/main/overview/overview-drawer/drawer-header/index.tsx
@@ -26,6 +26,10 @@ const InputWrapper = styled(SectionItemsWrapper)`
const Title = styled(Text)`
font-size: 18px;
line-height: 26px;
+ max-width: 400px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
`;
const DrawerItemImageWrapper = styled.div`
diff --git a/frontend/webapp/containers/main/sources/source-drawer-container/build-card.ts b/frontend/webapp/containers/main/sources/source-drawer-container/build-card.ts
index 151c7c6155..0f27272eff 100644
--- a/frontend/webapp/containers/main/sources/source-drawer-container/build-card.ts
+++ b/frontend/webapp/containers/main/sources/source-drawer-container/build-card.ts
@@ -1,15 +1,17 @@
+import { DISPLAY_TITLES } from '@/utils';
import type { K8sActualSource } from '@/types';
+import { DataCardRow } from '@/reuseable-components';
const buildCard = (source: K8sActualSource) => {
const { name, kind, namespace, instrumentedApplicationDetails } = source;
const { containerName, language } = instrumentedApplicationDetails?.containers?.[0] || {};
- const arr = [
- { title: 'Namespace', value: namespace },
- { title: 'Kind', value: kind },
- { title: 'Name', value: name },
- { title: 'Container Name', value: containerName || 'N/A' },
- { title: 'Language', value: language || 'N/A' },
+ const arr: DataCardRow[] = [
+ { title: DISPLAY_TITLES.NAMESPACE, value: namespace },
+ { title: DISPLAY_TITLES.KIND, value: kind },
+ { title: DISPLAY_TITLES.CONTAINER_NAME, value: containerName },
+ { title: DISPLAY_TITLES.NAME, value: name },
+ { title: DISPLAY_TITLES.LANGUAGE, value: language },
];
return arr;
diff --git a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx
index 8a67b9dfb3..6f9381e936 100644
--- a/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx
+++ b/frontend/webapp/containers/main/sources/source-drawer-container/index.tsx
@@ -3,13 +3,12 @@ import buildCard from './build-card';
import styled from 'styled-components';
import { useSourceCRUD } from '@/hooks';
import { useDrawerStore } from '@/store';
-import { CardDetails } from '@/components';
import buildDrawerItem from './build-drawer-item';
import { UpdateSourceBody } from '../update-source-body';
-import { ConditionDetails } from '@/reuseable-components';
import OverviewDrawer from '../../overview/overview-drawer';
-import { ACTION, getMainContainerLanguageLogo } from '@/utils';
-import { OVERVIEW_ENTITY_TYPES, WorkloadId, type K8sActualSource } from '@/types';
+import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type K8sActualSource } from '@/types';
+import { ACTION, DATA_CARDS, getMainContainerLanguage, getProgrammingLanguageIcon } from '@/utils';
+import { ConditionDetails, DataCard, DataCardRow, DataCardFieldTypes } from '@/reuseable-components';
interface Props {}
@@ -75,6 +74,23 @@ export const SourceDrawer: React.FC = () => {
return arr;
}, [selectedItem]);
+ const containersData = useMemo(() => {
+ if (!selectedItem) return [];
+
+ const { item } = selectedItem as { item: K8sActualSource };
+
+ return (
+ item.instrumentedApplicationDetails.containers.map(
+ (container) =>
+ ({
+ type: DataCardFieldTypes.SOURCE_CONTAINER,
+ width: '100%',
+ value: JSON.stringify(container),
+ } as DataCardRow),
+ ) || []
+ );
+ }, [selectedItem]);
+
if (!selectedItem?.item) return null;
const { id, item } = selectedItem as { id: WorkloadId; item: K8sActualSource };
@@ -102,7 +118,7 @@ export const SourceDrawer: React.FC = () => {
= () => {
) : (
-
+
+
)}
diff --git a/frontend/webapp/graphql/queries/compute-platform.ts b/frontend/webapp/graphql/queries/compute-platform.ts
index 2e61d9a727..a62a0629e3 100644
--- a/frontend/webapp/graphql/queries/compute-platform.ts
+++ b/frontend/webapp/graphql/queries/compute-platform.ts
@@ -13,6 +13,8 @@ export const GET_COMPUTE_PLATFORM = gql`
containers {
containerName
language
+ runtimeVersion
+ otherAgent
}
conditions {
type
diff --git a/frontend/webapp/hooks/actions/useActionFormData.ts b/frontend/webapp/hooks/actions/useActionFormData.ts
index fef65a4fc4..e6f7214627 100644
--- a/frontend/webapp/hooks/actions/useActionFormData.ts
+++ b/frontend/webapp/hooks/actions/useActionFormData.ts
@@ -1,7 +1,6 @@
-import { useState } from 'react';
-import { useNotify } from '../notification/useNotify';
import { DrawerBaseItem } from '@/store';
-import { ACTION, FORM_ALERTS, NOTIFICATION } from '@/utils';
+import { useGenericForm, useNotify } from '@/hooks';
+import { FORM_ALERTS, NOTIFICATION } from '@/utils';
import type { ActionDataParsed, ActionInput } from '@/types';
const INITIAL: ActionInput = {
@@ -16,21 +15,7 @@ const INITIAL: ActionInput = {
export function useActionFormData() {
const notify = useNotify();
-
- const [formData, setFormData] = useState({ ...INITIAL });
- const [formErrors, setFormErrors] = useState>({});
-
- const handleFormChange = (key: keyof typeof INITIAL, val: any) => {
- setFormData((prev) => ({
- ...prev,
- [key]: val,
- }));
- };
-
- const resetFormData = () => {
- setFormData({ ...INITIAL });
- setFormErrors({});
- };
+ const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL);
const validateForm = (params?: { withAlert?: boolean; alertTitle?: string }) => {
const errors = {};
@@ -60,7 +45,7 @@ export function useActionFormData() {
});
}
- setFormErrors(errors);
+ handleErrorChange(undefined, undefined, errors);
return ok;
};
@@ -98,7 +83,7 @@ export function useActionFormData() {
}
});
- setFormData(updatedData);
+ handleFormChange(undefined, undefined, updatedData);
};
return {
diff --git a/frontend/webapp/hooks/common/index.ts b/frontend/webapp/hooks/common/index.ts
index 71b90efaa6..3707609309 100644
--- a/frontend/webapp/hooks/common/index.ts
+++ b/frontend/webapp/hooks/common/index.ts
@@ -1,4 +1,5 @@
export * from './useContainerSize';
+export * from './useGenericForm';
export * from './useOnClickOutside';
export * from './useKeyDown';
export * from './useTimeAgo';
diff --git a/frontend/webapp/hooks/common/useGenericForm.ts b/frontend/webapp/hooks/common/useGenericForm.ts
new file mode 100644
index 0000000000..4b60a5e6a7
--- /dev/null
+++ b/frontend/webapp/hooks/common/useGenericForm.ts
@@ -0,0 +1,51 @@
+import { useState } from 'react';
+
+export const useGenericForm =