Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 14 additions & 6 deletions internal/exporter/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,15 +279,21 @@ func (c *collector) collectComponentData(data *HealthData) error {

health := "Unknown"
reason := "No health data"
errorMsg := ""
var timeValue interface{}
var extraInfo interface{} = map[string]interface{}{} // Default to empty map for JSON marshaling
var suggestedActions interface{}
var incidents interface{} = []apiv1.HealthStateIncident{}

if len(healthStates) > 0 {
firstState := healthStates[0]
health = string(firstState.Health)
reason = firstState.Reason
errorMsg = firstState.Error
timeValue = firstState.Time
if firstState.SuggestedActions != nil {
suggestedActions = firstState.SuggestedActions
}
if len(firstState.Incidents) > 0 {
incidents = firstState.Incidents
}
Expand All @@ -314,12 +320,14 @@ func (c *collector) collectComponentData(data *HealthData) error {
}

componentData[componentName] = map[string]interface{}{
"component_name": componentName,
"health": health,
"reason": reason,
"time": timeValue,
"extra_info": extraInfo,
"incidents": incidents,
"component_name": componentName,
"health": health,
"reason": reason,
"error": errorMsg,
"time": timeValue,
"extra_info": extraInfo,
"suggested_actions": suggestedActions,
"incidents": incidents,
}
}

Expand Down
12 changes: 12 additions & 0 deletions internal/exporter/collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,13 @@ func TestCollector_CollectComponentData_WithComponents(t *testing.T) {
Component: "test-component",
Health: "Healthy",
Reason: "All checks passed",
Error: "detailed error info",
Time: metav1.Time{Time: time.Now()},
ExtraInfo: map[string]string{"key": "value"},
SuggestedActions: &apiv1.SuggestedActions{
Description: "reboot the node",
RepairActions: []apiv1.RepairActionType{apiv1.RepairActionTypeRebootSystem},
},
Incidents: []apiv1.HealthStateIncident{
{
EntityID: "GPU-1234",
Expand Down Expand Up @@ -472,6 +477,11 @@ func TestCollector_CollectComponentData_WithComponents(t *testing.T) {
assert.Equal(t, "test-component", dataMap["component_name"])
assert.Equal(t, "Healthy", dataMap["health"])
assert.Equal(t, "All checks passed", dataMap["reason"])
assert.Equal(t, "detailed error info", dataMap["error"])
suggestedActions, ok := dataMap["suggested_actions"].(*apiv1.SuggestedActions)
require.True(t, ok, "suggested_actions should preserve the typed SuggestedActions pointer")
require.NotNil(t, suggestedActions)
assert.Equal(t, "reboot the node", suggestedActions.Description)
incidents, ok := dataMap["incidents"].([]apiv1.HealthStateIncident)
require.True(t, ok, "incidents should preserve the typed health incidents slice")
require.Len(t, incidents, 1)
Expand Down Expand Up @@ -504,6 +514,8 @@ func TestCollector_CollectComponentData_NoHealthStates(t *testing.T) {
compData := data.ComponentData["empty-component"].(map[string]interface{})
assert.Equal(t, "Unknown", compData["health"])
assert.Equal(t, "No health data", compData["reason"])
assert.Equal(t, "", compData["error"])
assert.Nil(t, compData["suggested_actions"])
}

func TestCollector_AllFeaturesEnabled(t *testing.T) {
Expand Down
23 changes: 23 additions & 0 deletions internal/exporter/converter/otlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,10 @@ func (c *otlpConverter) convertToOTLPLogs(data *collector.HealthData) []*logsv1.

health := componentInfo["health"]
reason := componentInfo["reason"]
errorMsg := componentInfo["error"]
timeVal := componentInfo["time"]
extraInfo := componentInfo["extra_info"]
suggestedActions := componentInfo["suggested_actions"]
incidents := componentInfo["incidents"]

attributes := []*commonv1.KeyValue{
Expand Down Expand Up @@ -428,6 +430,15 @@ func (c *otlpConverter) convertToOTLPLogs(data *collector.HealthData) []*logsv1.
}

// Add optional fields
if errStr, ok := errorMsg.(string); ok && errStr != "" {
attributes = append(attributes, &commonv1.KeyValue{
Key: "error",
Value: &commonv1.AnyValue{
Value: &commonv1.AnyValue_StringValue{StringValue: errStr},
},
})
}

if timeVal != nil {
attributes = append(attributes, &commonv1.KeyValue{
Key: "time",
Expand All @@ -449,6 +460,18 @@ func (c *otlpConverter) convertToOTLPLogs(data *collector.HealthData) []*logsv1.
}
}

if suggestedActions != nil {
jsonBytes, err := json.Marshal(suggestedActions)
if err == nil && string(jsonBytes) != "null" {
attributes = append(attributes, &commonv1.KeyValue{
Key: "suggested_actions",
Value: &commonv1.AnyValue{
Value: &commonv1.AnyValue_StringValue{StringValue: string(jsonBytes)},
},
})
}
}

if typedIncidents, ok := toHealthStateIncidents(incidents); ok && len(typedIncidents) > 0 {
attributes = append(attributes, &commonv1.KeyValue{
Key: "incidents",
Expand Down
18 changes: 15 additions & 3 deletions internal/exporter/converter/otlp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,17 @@ func TestOTLPConverter_Convert_WithComponentData(t *testing.T) {
"gpu": map[string]any{
"time": metav1.Time{Time: time.Now()},
"component_name": "gpu",
"health": "healthy",
"reason": "All checks passed",
"health": "Unhealthy",
"reason": "failed to get recent events",
"error": "database is locked",
"extra_info": map[string]any{
"device_uuid": "PCI:0000:04:00",
"data": rawData,
},
"suggested_actions": &apiv1.SuggestedActions{
Description: "reboot the node",
RepairActions: []apiv1.RepairActionType{apiv1.RepairActionTypeRebootSystem},
},
"incidents": []apiv1.HealthStateIncident{
{
EntityID: "GPU-1234",
Expand Down Expand Up @@ -335,12 +340,19 @@ func TestOTLPConverter_Convert_WithComponentData(t *testing.T) {
// Find component data log
found := false
for _, log := range logs {
if contains(log.Body.GetStringValue(), "gpu") && contains(log.Body.GetStringValue(), "healthy") {
if contains(log.Body.GetStringValue(), "gpu") && contains(log.Body.GetStringValue(), "Unhealthy") {
assert.Equal(t, "database is locked", findAttribute(t, log.Attributes, "error").GetStringValue())

extraInfo := findAttribute(t, log.Attributes, "extra_info").GetStringValue()
require.NotEmpty(t, extraInfo)
assert.Contains(t, extraInfo, `"device_uuid":"PCI:0000:04:00"`)
assert.Contains(t, extraInfo, `"data":"{\"time\":\"2026-02-20T23:22:44Z\",\"data_source\":\"kmsg\",\"xid\":149}"`)

suggestedActions := findAttribute(t, log.Attributes, "suggested_actions").GetStringValue()
require.NotEmpty(t, suggestedActions)
assert.Contains(t, suggestedActions, `"description":"reboot the node"`)
assert.Contains(t, suggestedActions, `"REBOOT_SYSTEM"`)

incidents := findAttribute(t, log.Attributes, "incidents").GetArrayValue()
require.NotNil(t, incidents)
require.Len(t, incidents.Values, 1)
Expand Down
Loading