github.com/grafana/pyroscope@v1.18.0/pkg/ingester/pyroscope/ingest_adapter.go (about)

     1  package pyroscope
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/go-kit/log/level"
    11  
    12  	"github.com/grafana/pyroscope/pkg/distributor/model"
    13  	"github.com/grafana/pyroscope/pkg/tenant"
    14  
    15  	"connectrpc.com/connect"
    16  	"google.golang.org/protobuf/proto"
    17  
    18  	"github.com/go-kit/log"
    19  	"github.com/google/uuid"
    20  	"github.com/prometheus/prometheus/model/labels"
    21  
    22  	pushv1 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1"
    23  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    24  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    25  	"github.com/grafana/pyroscope/pkg/og/ingestion"
    26  	"github.com/grafana/pyroscope/pkg/og/storage"
    27  	"github.com/grafana/pyroscope/pkg/og/storage/tree"
    28  )
    29  
    30  type PushService interface {
    31  	Push(ctx context.Context, req *connect.Request[pushv1.PushRequest]) (*connect.Response[pushv1.PushResponse], error)
    32  	PushBatch(ctx context.Context, req *model.PushRequest) error
    33  }
    34  
    35  type Limits interface {
    36  	MaxProfileSizeBytes(tenantID string) int
    37  }
    38  
    39  func NewPyroscopeIngestHandler(svc PushService, limits Limits, logger log.Logger) http.Handler {
    40  	return NewIngestHandler(
    41  		logger,
    42  		&pyroscopeIngesterAdapter{svc: svc, limits: limits, log: logger},
    43  	)
    44  }
    45  
    46  type pyroscopeIngesterAdapter struct {
    47  	svc    PushService
    48  	limits Limits
    49  	log    log.Logger
    50  }
    51  
    52  func (p *pyroscopeIngesterAdapter) Ingest(ctx context.Context, in *ingestion.IngestInput) error {
    53  	pprofable, ok := in.Profile.(ingestion.ParseableToPprof)
    54  	if ok {
    55  		return p.parseToPprof(ctx, in, pprofable)
    56  	} else {
    57  		return in.Profile.Parse(ctx, p, p, in.Metadata)
    58  	}
    59  }
    60  
    61  const (
    62  	metricProcessCPU  = "process_cpu"
    63  	metricMemory      = "memory"
    64  	metricMutex       = "mutex"
    65  	metricBlock       = "block"
    66  	metricWall        = "wall"
    67  	stUnitCount       = "count"
    68  	stTypeSamples     = "samples"
    69  	stTypeCPU         = "cpu"
    70  	stTypeWall        = "wall"
    71  	stUnitBytes       = "bytes"
    72  	stTypeContentions = "contentions"
    73  	stTypeDelay       = "delay"
    74  	stUnitNanos       = "nanoseconds"
    75  )
    76  
    77  func (p *pyroscopeIngesterAdapter) Put(ctx context.Context, pi *storage.PutInput) error {
    78  	if pi.LabelSet.HasProfileID() {
    79  		return nil
    80  	}
    81  	metric, stType, stUnit, app, err := convertMetadata(pi)
    82  	if err != nil {
    83  		return connect.NewError(
    84  			connect.CodeInvalidArgument,
    85  			fmt.Errorf("pyroscopeIngesterAdapter failed to convert metadata: %w", err),
    86  		)
    87  	}
    88  	mdata := &tree.PprofMetadata{
    89  		Type:      stType,
    90  		Unit:      stUnit,
    91  		StartTime: pi.StartTime,
    92  	}
    93  	if pi.SampleRate != 0 && (metric == metricWall || metric == metricProcessCPU) {
    94  		period := time.Second.Nanoseconds() / int64(pi.SampleRate)
    95  		mdata.Period = period
    96  		mdata.PeriodType = stTypeCPU
    97  		mdata.PeriodUnit = stUnitNanos
    98  		if metric == metricWall {
    99  			mdata.Type = stTypeWall
   100  		} else {
   101  			mdata.Type = stTypeCPU
   102  		}
   103  		mdata.Unit = stUnitNanos
   104  		pi.Val.Scale(uint64(period))
   105  	}
   106  	pprof := pi.Val.Pprof(mdata)
   107  	b, err := proto.Marshal(pprof)
   108  	if err != nil {
   109  		return connect.NewError(
   110  			connect.CodeInvalidArgument,
   111  			fmt.Errorf("pyroscopeIngesterAdapter failed to marshal pprof: %w", err),
   112  		)
   113  	}
   114  	req := &pushv1.PushRequest{}
   115  	series := &pushv1.RawProfileSeries{
   116  		Labels: make([]*typesv1.LabelPair, 0, 3+len(pi.LabelSet.Labels())),
   117  	}
   118  	series.Labels = append(series.Labels, &typesv1.LabelPair{
   119  		Name:  labels.MetricName,
   120  		Value: metric,
   121  	}, &typesv1.LabelPair{
   122  		Name:  phlaremodel.LabelNameDelta,
   123  		Value: "false",
   124  	})
   125  	if pi.SpyName != "" {
   126  		series.Labels = append(series.Labels, &typesv1.LabelPair{
   127  			Name:  phlaremodel.LabelNamePyroscopeSpy,
   128  			Value: pi.SpyName,
   129  		})
   130  	}
   131  	hasServiceName := false
   132  	for k, v := range pi.LabelSet.Labels() {
   133  		if !phlaremodel.IsLabelAllowedForIngestion(k) {
   134  			continue
   135  		}
   136  		if k == "service_name" {
   137  			hasServiceName = true
   138  		}
   139  		series.Labels = append(series.Labels, &typesv1.LabelPair{
   140  			Name:  k,
   141  			Value: v,
   142  		})
   143  	}
   144  	// If service_name is not present, use app_name as the service_name.
   145  	if !hasServiceName {
   146  		series.Labels = append(series.Labels, &typesv1.LabelPair{
   147  			Name:  "service_name",
   148  			Value: app,
   149  		})
   150  	} else {
   151  		// Check if app_name already exists
   152  		hasAppName := false
   153  		for _, label := range series.Labels {
   154  			if label.Name == "app_name" {
   155  				hasAppName = true
   156  				break
   157  			}
   158  		}
   159  
   160  		if !hasAppName {
   161  			series.Labels = append(series.Labels, &typesv1.LabelPair{
   162  				Name:  "app_name",
   163  				Value: app,
   164  			})
   165  		}
   166  	}
   167  	series.Samples = []*pushv1.RawSample{{
   168  		RawProfile: b,
   169  		ID:         uuid.New().String(),
   170  	}}
   171  	req.Series = append(req.Series, series)
   172  	_, err = p.svc.Push(ctx, connect.NewRequest(req))
   173  	if err != nil {
   174  		return fmt.Errorf("pyroscopeIngesterAdapter failed to push: %w", err)
   175  	}
   176  	return nil
   177  }
   178  
   179  func (p *pyroscopeIngesterAdapter) Evaluate(input *storage.PutInput) (storage.SampleObserver, bool) {
   180  	return nil, false // noop
   181  }
   182  
   183  func (p *pyroscopeIngesterAdapter) parseToPprof(
   184  	ctx context.Context,
   185  	in *ingestion.IngestInput,
   186  	pprofable ingestion.ParseableToPprof,
   187  ) error {
   188  	plainReq, err := pprofable.ParseToPprof(ctx, in.Metadata, p.limits)
   189  	if err != nil {
   190  		return fmt.Errorf("parsing IngestInput-pprof failed %w", err)
   191  	}
   192  	if len(plainReq.Series) == 0 {
   193  		tenantID, _ := tenant.ExtractTenantIDFromContext(ctx)
   194  		_ = level.Debug(p.log).Log("msg", "empty profile",
   195  			"application", in.Metadata.LabelSet.ServiceName(),
   196  			"orgID", tenantID)
   197  		return nil
   198  	}
   199  	err = p.svc.PushBatch(ctx, plainReq)
   200  	if err != nil {
   201  		return fmt.Errorf("pushing IngestInput-pprof failed %w", err)
   202  	}
   203  	return nil
   204  }
   205  
   206  func convertMetadata(pi *storage.PutInput) (metricName, stType, stUnit, app string, err error) {
   207  	app = pi.LabelSet.ServiceName()
   208  	parts := strings.Split(app, ".")
   209  	if len(parts) <= 1 {
   210  		stType = stTypeCPU
   211  	} else {
   212  		stType = parts[len(parts)-1]
   213  		app = strings.Join(parts[:len(parts)-1], ".")
   214  	}
   215  	switch stType {
   216  	case stTypeCPU:
   217  		metricName = metricProcessCPU
   218  		stType = stTypeSamples
   219  		stUnit = stUnitCount
   220  	case "wall":
   221  		metricName = metricWall
   222  		stType = stTypeSamples
   223  		stUnit = stUnitCount
   224  	case "inuse_objects":
   225  		metricName = metricMemory
   226  		stUnit = stUnitCount
   227  	case "inuse_space":
   228  		metricName = metricMemory
   229  		stUnit = stUnitBytes
   230  	case "alloc_objects":
   231  		metricName = metricMemory
   232  		stUnit = stUnitCount
   233  	case "alloc_space":
   234  		metricName = metricMemory
   235  		stUnit = stUnitBytes
   236  	case "goroutines":
   237  		metricName = "goroutine"
   238  		stUnit = stUnitCount
   239  	case "mutex_count":
   240  		stType = stTypeContentions
   241  		stUnit = stUnitCount
   242  		metricName = metricMutex
   243  	case "mutex_duration":
   244  		stType = stTypeDelay
   245  		stUnit = stUnitNanos
   246  		metricName = metricMutex
   247  	case "block_count":
   248  		stType = stTypeContentions
   249  		stUnit = stUnitCount
   250  		metricName = metricBlock
   251  	case "block_duration":
   252  		stType = stTypeDelay
   253  		stUnit = stUnitNanos
   254  		metricName = metricBlock
   255  	case "itimer":
   256  		metricName = metricProcessCPU
   257  		stType = stTypeSamples
   258  		stUnit = stUnitCount
   259  	case "alloc_in_new_tlab_objects":
   260  		metricName = metricMemory
   261  		stType = "alloc_in_new_tlab_objects"
   262  		stUnit = stUnitCount
   263  	case "alloc_in_new_tlab_bytes":
   264  		metricName = metricMemory
   265  		stType = "alloc_in_new_tlab_bytes"
   266  		stUnit = stUnitBytes
   267  	case "alloc_outside_tlab_objects":
   268  		metricName = metricMemory
   269  		stType = "alloc_outside_tlab_objects"
   270  		stUnit = stUnitCount
   271  	case "alloc_outside_tlab_bytes":
   272  		metricName = metricMemory
   273  		stType = "alloc_outside_tlab_bytes"
   274  		stUnit = stUnitBytes
   275  	case "lock_count":
   276  		stType = stTypeContentions
   277  		stUnit = stUnitCount
   278  		metricName = metricBlock
   279  	case "lock_duration":
   280  		stType = stTypeDelay
   281  		stUnit = stUnitNanos
   282  		metricName = metricBlock
   283  	case "live":
   284  		metricName = metricMemory
   285  		stType = "live"
   286  		stUnit = stUnitCount
   287  	case "exceptions":
   288  		metricName = "exceptions"
   289  		stType = stTypeSamples
   290  		stUnit = stUnitCount
   291  	case "seconds", "nanoseconds", "microseconds", "milliseconds":
   292  		metricName = metricWall
   293  		stType = stTypeSamples
   294  		stUnit = stUnitCount
   295  	case "bytes":
   296  		metricName = metricMemory
   297  		stType = stTypeSamples
   298  		stUnit = stUnitBytes
   299  	default:
   300  		err = fmt.Errorf("unknown profile type: %s", stType)
   301  	}
   302  
   303  	return metricName, stType, stUnit, app, err
   304  }