Skip to content

Commit

Permalink
TRACING-4824: add a way to detect components support level in upstream
Browse files Browse the repository at this point in the history
Signed-off-by: Israel Blancas <[email protected]>
  • Loading branch information
Israel Blancas committed Oct 17, 2024
1 parent 43d959f commit 024dcd5
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 1 deletion.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ dist/**
*.pp
opentelemetry-collector/
opentelemetry-collector-contrib/
component-parser
31 changes: 31 additions & 0 deletions component-parser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# OpenTelemetry Collector Component Parser

This program parses and displays information about OpenTelemetry Collector components based on their metadata files. It provides a summary of the support levels for various components across different signal types (traces, metrics, and logs).

## Features

- Clones or updates OpenTelemetry Collector repositories
- Parses component metadata files
- Displays support levels for receivers, processors, exporters, connectors, and extensions

## Installation

1. Clone this repository:
```
git clone https://github.com/yourusername/otel-component-parser.git
cd otel-component-parser
```

2. Build the program:
```
go build -o component-parser
```

## Usage

Run the program with:
```
./component-parser
```

The program will clone or update the OpenTelemetry Collector and OpenTelemetry Collector Contrib repositories, parse the metadata files, and display the support levels for each component.
17 changes: 17 additions & 0 deletions component-parser/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
318 changes: 318 additions & 0 deletions component-parser/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
package main

import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"text/tabwriter"

"gopkg.in/yaml.v3"
)

// Define the structure for the dist section
type Dist struct {
Module string `yaml:"module"`
Name string `yaml:"name"`
Description string `yaml:"description"`
Version string `yaml:"version"`
OutputPath string `yaml:"output_path"`
OtelcolVersion string `yaml:"otelcol_version"`
}

// Define the structure for each component entry (receivers, exporters, etc.)
type Component struct {
Gomod string `yaml:"gomod"`
}

// Define the structure for the whole YAML file
type ComponentsConfig struct {
Dist Dist `yaml:"dist"`
Receivers []Component `yaml:"receivers"`
Exporters []Component `yaml:"exporters"`
Extensions []Component `yaml:"extensions"`
Processors []Component `yaml:"processors"`
Connectors []Component `yaml:"connectors"`
}

// extractComponentName extracts the component name from the gomod string
func extractComponentName(gomod string) string {
parts := strings.Split(gomod, "/")
if len(parts) > 0 {
// The last part of the gomod contains version, so split by space to remove it
component := parts[len(parts)-1]
// Remove the version part if present (e.g., "receiver v0.107.0")
component = strings.Split(component, " ")[0]
return component
}
return gomod
}

// getDistributionConfig reads the manifest.yaml file and returns a ComponentsConfig struct
func getDistributionConfig(manifestFile string) (ComponentsConfig, error) {
yamlFile, err := os.ReadFile(manifestFile)
if err != nil {
return ComponentsConfig{}, fmt.Errorf("error reading YAML file: %w", err)
}

var config ComponentsConfig
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
return ComponentsConfig{}, fmt.Errorf("error parsing YAML: %w", err)
}

return config, nil
}

// getSupportedComponents returns a list of supported components from the manifest
func getSupportedComponents(config ComponentsConfig) []string {
components := make([]string, 0, len(config.Receivers)+len(config.Exporters)+len(config.Extensions)+len(config.Processors)+len(config.Connectors))

// Use a helper function to avoid repetition
appendComponents := func(comps []Component) {
for _, comp := range comps {
components = append(components, extractComponentName(comp.Gomod))
}
}

// Append components from each category
appendComponents(config.Receivers)
appendComponents(config.Exporters)
appendComponents(config.Extensions)
appendComponents(config.Processors)
appendComponents(config.Connectors)

return components
}

// Define the structure to match the YAML file structure
type Metadata struct {
Type string `yaml:"type"`
Status struct {
Class string `yaml:"class"`
Stability Stability `yaml:"stability"`
} `yaml:"status"`
}

type Stability struct {
Unmaintained []string `yaml:"unmaintained"`
Deprecated []string `yaml:"deprecated"`
Alpha []string `yaml:"alpha"`
Development []string `yaml:"development"`
Beta []string `yaml:"beta"`
Stable []string `yaml:"stable"`
}

// parseYAMLFile parses a metadata.yaml file and returns a Metadata struct
func parseYAMLFile(filepath string) (*Metadata, error) {
data, err := os.ReadFile(filepath)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}

var m Metadata
err = yaml.Unmarshal(data, &m)
if err != nil {
return nil, fmt.Errorf("failed to parse YAML: %w", err)
}

return &m, nil
}

// findMetadataFiles walks through directories and finds all metadata.yaml files
func findMetadataFiles(rootDir string, manifest ComponentsConfig) ([]string, error) {
supportedComponents := getSupportedComponents(manifest)

var files []string
err := filepath.WalkDir(rootDir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || d.Name() != "metadata.yaml" {
return nil
}

compName := filepath.Base(filepath.Dir(path))
if slices.Contains(supportedComponents, compName) {
files = append(files, path)
return nil
}

return nil
})

if err != nil {
return nil, err
}

return files, nil
}

// getSupportLevel gets the support level for the given category
func getSupportLevel(stability *Stability, category string) string {
if contains(stability.Unmaintained, category) {
return "Unmaintained"
}
if contains(stability.Deprecated, category) {
return "Deprecated"
}
if contains(stability.Stable, category) {
return "Stable"
}
if contains(stability.Beta, category) {
return "Beta"
}
if contains(stability.Development, category) {
return "Development"
}
if contains(stability.Alpha, category) {
return "Alpha"
}
return "n/a"
}

// Helper function to check if a slice contains a specific category
func contains(slice []string, item string) bool {
for _, v := range slice {
if v == item {
return true
}
}
return false
}

func processMetadataFiles(files []string) map[string][]*Metadata {
componentsByClass := make(map[string][]*Metadata)
for _, file := range files {
sg, err := parseYAMLFile(file)
if err != nil {
log.Printf("Failed to parse %s: %v\n", file, err)
continue
}
if sg.Type == "" {
log.Printf("Skipping invalid component in file %s: missing type\n", file)
continue
}
class := sg.Status.Class
componentsByClass[class] = append(componentsByClass[class], sg)
}
return componentsByClass
}

func printComponentTable(componentType, header string, components []*Metadata, getSupportFunc func(*Metadata) string) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)
fmt.Println(strings.Repeat("=", len(header)))
fmt.Println(componentType)
fmt.Println(strings.Repeat("=", len(header)))
fmt.Fprintln(w, header)

for _, component := range components {
support := getSupportFunc(component)
fmt.Fprintf(w, "%s\t%s\n", component.Type, support)
}
w.Flush()
fmt.Println()
}

func main() {
manifestPath := flag.String("m", "manifest.yaml", "Specify the path to the manifest.yaml file")
flag.Parse()

repos := []string{
"opentelemetry-collector",
"opentelemetry-collector-contrib",
}

pwd, err := os.Getwd()
if err != nil {
log.Fatalln(err)
}

metadataFiles := []string{}

for _, repo := range repos {
manifest, err := getDistributionConfig(*manifestPath)
if err != nil {
log.Fatalf("Error getting distribution config: %v", err)
}

if _, err := os.Stat(filepath.Join(pwd, repo)); os.IsNotExist(err) {
log.Printf("Repository %s does not exist, cloning...\n", repo)
err := exec.Command("git", "clone", fmt.Sprintf("https://github.com/open-telemetry/%s.git", repo)).Run()
if err != nil {
log.Fatalf("Error cloning repository %s: %v", repo, err)
}
}

log.Printf("Checking out version %s of %s\n", manifest.Dist.OtelcolVersion, repo)
c := exec.Command("git", "checkout", fmt.Sprintf("v%s", manifest.Dist.OtelcolVersion))
c.Dir = filepath.Join(pwd, repo)
err = c.Run()
if err != nil {
log.Fatalf("Error checking out version %s of %s: %v", manifest.Dist.OtelcolVersion, repo, err)
}

// Find all metadata.yaml files in subdirectories
f, err := findMetadataFiles(filepath.Join(pwd, repo), manifest)
if err != nil {
log.Fatalf("Error finding YAML files: %v", err)
}
metadataFiles = append(metadataFiles, f...)
}

componentsByClass := processMetadataFiles(metadataFiles)

for class, components := range componentsByClass {
if class == "connector" || class == "extension" {
continue
}
printComponentTable(class, "Component Name\tTraces Support\tMetrics Support\tLogs Support", components, func(c *Metadata) string {
tracesSupport := getSupportLevel(&c.Status.Stability, "traces")
metricsSupport := getSupportLevel(&c.Status.Stability, "metrics")
logsSupport := getSupportLevel(&c.Status.Stability, "logs")
return fmt.Sprintf("%s\t%s\t%s", tracesSupport, metricsSupport, logsSupport)
})
}

// Create a tab writer to display the output in table format
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)

// Print the table headers
header := "Component Name\tTraces to traces\tTraces to metrics\tTraces to logs\tMetrics to metrics\tMetrics to traces\tMetrics to logs\tLogs to logs\tLogs to traces\tLogs to metrics"
fmt.Println(strings.Repeat("=", len(header)))
fmt.Println("connectors")
fmt.Println(strings.Repeat("=", len(header)))

fmt.Fprintln(w, header)
for _, component := range componentsByClass["connector"] {
tracesToTraces := getSupportLevel(&component.Status.Stability, "traces_to_traces")
tracesToMetrics := getSupportLevel(&component.Status.Stability, "traces_to_metrics")
tracesToLogs := getSupportLevel(&component.Status.Stability, "traces_to_logs")

metricsToMetrics := getSupportLevel(&component.Status.Stability, "metrics_to_metrics")
metricsToTraces := getSupportLevel(&component.Status.Stability, "metrics_to_traces")
metricsToLogs := getSupportLevel(&component.Status.Stability, "metrics_to_logs")

logsToLogs := getSupportLevel(&component.Status.Stability, "logs_to_logs")
logsToTraces := getSupportLevel(&component.Status.Stability, "logs_to_traces")
logsToMetrics := getSupportLevel(&component.Status.Stability, "logs_to_metrics")

fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", component.Type, tracesToTraces, tracesToMetrics, tracesToLogs, metricsToMetrics, metricsToTraces, metricsToLogs, logsToLogs, logsToTraces, logsToMetrics)
}
w.Flush()

fmt.Println()

// Create a tab writer to display the output in table format
w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)

// Print the table headers
printComponentTable("extensions", "Component Name\tSupport", componentsByClass["extension"], func(c *Metadata) string {
return getSupportLevel(&c.Status.Stability, "extension")
})
w.Flush()
}

0 comments on commit 024dcd5

Please sign in to comment.