Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow exporting via signed URL #4666

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
1,542 changes: 949 additions & 593 deletions gen/proto/go/parca/query/v1alpha1/query.pb.go

Large diffs are not rendered by default.

653 changes: 653 additions & 0 deletions gen/proto/go/parca/query/v1alpha1/query_vtproto.pb.go

Large diffs are not rendered by default.

79 changes: 75 additions & 4 deletions gen/proto/swagger/parca/query/v1alpha1/query.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@
},
{
"name": "reportType",
"description": "report_type is the type of report to return\n\n - REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED: REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED unspecified\n - REPORT_TYPE_PPROF: REPORT_TYPE_PPROF unspecified\n - REPORT_TYPE_TOP: REPORT_TYPE_TOP unspecified\n - REPORT_TYPE_CALLGRAPH: REPORT_TYPE_CALLGRAPH unspecified\n - REPORT_TYPE_FLAMEGRAPH_TABLE: REPORT_TYPE_FLAMEGRAPH_TABLE unspecified\n - REPORT_TYPE_FLAMEGRAPH_ARROW: REPORT_TYPE_FLAMEGRAPH_ARROW unspecified\n - REPORT_TYPE_SOURCE: REPORT_TYPE_SOURCE contains source code annotated with profiling information\n - REPORT_TYPE_TABLE_ARROW: REPORT_TYPE_TABLE_ARROW unspecified\n - REPORT_TYPE_PROFILE_METADATA: REPORT_TYPE_PROFILE_METADATA contains metadata about the profile i.e. binaries, labels",
"description": "report_type is the type of report to return\n\n - REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED: REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED unspecified\n - REPORT_TYPE_PPROF: REPORT_TYPE_PPROF unspecified\n - REPORT_TYPE_TOP: REPORT_TYPE_TOP unspecified\n - REPORT_TYPE_CALLGRAPH: REPORT_TYPE_CALLGRAPH unspecified\n - REPORT_TYPE_FLAMEGRAPH_TABLE: REPORT_TYPE_FLAMEGRAPH_TABLE unspecified\n - REPORT_TYPE_FLAMEGRAPH_ARROW: REPORT_TYPE_FLAMEGRAPH_ARROW unspecified\n - REPORT_TYPE_SOURCE: REPORT_TYPE_SOURCE contains source code annotated with profiling information\n - REPORT_TYPE_TABLE_ARROW: REPORT_TYPE_TABLE_ARROW unspecified\n - REPORT_TYPE_PROFILE_METADATA: REPORT_TYPE_PROFILE_METADATA contains metadata about the profile i.e. binaries, labels\n - REPORT_TYPE_EXPORT: REPORT_TYPE_EXPORT contains the profile as a downloadable file",
"in": "query",
"required": false,
"type": "string",
Expand All @@ -312,7 +312,8 @@
"REPORT_TYPE_FLAMEGRAPH_ARROW",
"REPORT_TYPE_SOURCE",
"REPORT_TYPE_TABLE_ARROW",
"REPORT_TYPE_PROFILE_METADATA"
"REPORT_TYPE_PROFILE_METADATA",
"REPORT_TYPE_EXPORT"
],
"default": "REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED"
},
Expand Down Expand Up @@ -390,6 +391,18 @@
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "exportFormat",
"description": "the format to respond the data in for the export\n\n - FORMAT_UNSPECIFIED: FORMAT_UNSPECIFIED unspecified\n - FORMAT_PPROF: DOWNLOAD_FORMAT_PPROF pprof format",
"in": "query",
"required": false,
"type": "string",
"enum": [
"FORMAT_UNSPECIFIED",
"FORMAT_PPROF"
],
"default": "FORMAT_UNSPECIFIED"
}
],
"tags": [
Expand Down Expand Up @@ -581,10 +594,11 @@
"REPORT_TYPE_FLAMEGRAPH_ARROW",
"REPORT_TYPE_SOURCE",
"REPORT_TYPE_TABLE_ARROW",
"REPORT_TYPE_PROFILE_METADATA"
"REPORT_TYPE_PROFILE_METADATA",
"REPORT_TYPE_EXPORT"
],
"default": "REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED",
"description": "- REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED: REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED unspecified\n - REPORT_TYPE_PPROF: REPORT_TYPE_PPROF unspecified\n - REPORT_TYPE_TOP: REPORT_TYPE_TOP unspecified\n - REPORT_TYPE_CALLGRAPH: REPORT_TYPE_CALLGRAPH unspecified\n - REPORT_TYPE_FLAMEGRAPH_TABLE: REPORT_TYPE_FLAMEGRAPH_TABLE unspecified\n - REPORT_TYPE_FLAMEGRAPH_ARROW: REPORT_TYPE_FLAMEGRAPH_ARROW unspecified\n - REPORT_TYPE_SOURCE: REPORT_TYPE_SOURCE contains source code annotated with profiling information\n - REPORT_TYPE_TABLE_ARROW: REPORT_TYPE_TABLE_ARROW unspecified\n - REPORT_TYPE_PROFILE_METADATA: REPORT_TYPE_PROFILE_METADATA contains metadata about the profile i.e. binaries, labels",
"description": "- REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED: REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED unspecified\n - REPORT_TYPE_PPROF: REPORT_TYPE_PPROF unspecified\n - REPORT_TYPE_TOP: REPORT_TYPE_TOP unspecified\n - REPORT_TYPE_CALLGRAPH: REPORT_TYPE_CALLGRAPH unspecified\n - REPORT_TYPE_FLAMEGRAPH_TABLE: REPORT_TYPE_FLAMEGRAPH_TABLE unspecified\n - REPORT_TYPE_FLAMEGRAPH_ARROW: REPORT_TYPE_FLAMEGRAPH_ARROW unspecified\n - REPORT_TYPE_SOURCE: REPORT_TYPE_SOURCE contains source code annotated with profiling information\n - REPORT_TYPE_TABLE_ARROW: REPORT_TYPE_TABLE_ARROW unspecified\n - REPORT_TYPE_PROFILE_METADATA: REPORT_TYPE_PROFILE_METADATA contains metadata about the profile i.e. binaries, labels\n - REPORT_TYPE_EXPORT: REPORT_TYPE_EXPORT contains the profile as a downloadable file",
"title": "ReportType is the type of report to return"
},
"metastorev1alpha1Function": {
Expand Down Expand Up @@ -813,6 +827,10 @@
"$ref": "#/definitions/v1alpha1Filter"
},
"title": "a set of filter to apply to the query request"
},
"exportFormat": {
"$ref": "#/definitions/v1alpha1Format",
"title": "the format to respond the data in for the export"
}
},
"title": "QueryRequest is a request for a profile query"
Expand Down Expand Up @@ -853,6 +871,10 @@
"$ref": "#/definitions/v1alpha1ProfileMetadata",
"title": "profile_metadata contains metadata about the profile i.e. binaries, labels"
},
"export": {
"$ref": "#/definitions/v1alpha1Export",
"description": "export is the response for an export request."
},
"total": {
"type": "string",
"format": "int64",
Expand Down Expand Up @@ -1068,6 +1090,24 @@
},
"title": "DiffProfile contains parameters for a profile diff request"
},
"v1alpha1Export": {
"type": "object",
"properties": {
"format": {
"$ref": "#/definitions/v1alpha1Format",
"description": "The format of the download."
},
"inline": {
"$ref": "#/definitions/v1alpha1InlineExport",
"description": "InlineExport is the response for an export request where actual content\nis inlined in the response."
},
"url": {
"$ref": "#/definitions/v1alpha1URLExport",
"description": "URL is the response for an export request where actual content\nis to be downloaded from the provided URL."
}
},
"description": "Export is the response for an export request."
},
"v1alpha1Filter": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -1256,6 +1296,16 @@
},
"title": "FlamegraphRootNode is a root node of a flame graph"
},
"v1alpha1Format": {
"type": "string",
"enum": [
"FORMAT_UNSPECIFIED",
"FORMAT_PPROF"
],
"default": "FORMAT_UNSPECIFIED",
"description": "- FORMAT_UNSPECIFIED: FORMAT_UNSPECIFIED unspecified\n - FORMAT_PPROF: DOWNLOAD_FORMAT_PPROF pprof format",
"title": "format is the format to offer the data in for the download"
},
"v1alpha1FrameFilter": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -1289,6 +1339,17 @@
},
"title": "GroupBy encapsulates the repeated fields to group by"
},
"v1alpha1InlineExport": {
"type": "object",
"properties": {
"content": {
"type": "string",
"format": "byte",
"description": "Content of the export."
}
},
"description": "InlineExport is the response for an export request where actual content is\ninlined in the response."
},
"v1alpha1LabelSet": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -1677,6 +1738,16 @@
},
"title": "TopNodeMeta is the metadata for a given node"
},
"v1alpha1URLExport": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to download the content from."
}
},
"description": "URLExport is the response for an export request where actual content is\nto be downloaded from the provided URL."
},
"v1alpha1ValuesResponse": {
"type": "object",
"properties": {
Expand Down
5 changes: 3 additions & 2 deletions gen/proto/swagger/parca/share/v1alpha1/share.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
"REPORT_TYPE_FLAMEGRAPH_ARROW",
"REPORT_TYPE_SOURCE",
"REPORT_TYPE_TABLE_ARROW",
"REPORT_TYPE_PROFILE_METADATA"
"REPORT_TYPE_PROFILE_METADATA",
"REPORT_TYPE_EXPORT"
],
"default": "REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED",
"description": "- REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED: REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED unspecified\n - REPORT_TYPE_PPROF: REPORT_TYPE_PPROF unspecified\n - REPORT_TYPE_TOP: REPORT_TYPE_TOP unspecified\n - REPORT_TYPE_CALLGRAPH: REPORT_TYPE_CALLGRAPH unspecified\n - REPORT_TYPE_FLAMEGRAPH_TABLE: REPORT_TYPE_FLAMEGRAPH_TABLE unspecified\n - REPORT_TYPE_FLAMEGRAPH_ARROW: REPORT_TYPE_FLAMEGRAPH_ARROW unspecified\n - REPORT_TYPE_SOURCE: REPORT_TYPE_SOURCE contains source code annotated with profiling information\n - REPORT_TYPE_TABLE_ARROW: REPORT_TYPE_TABLE_ARROW unspecified\n - REPORT_TYPE_PROFILE_METADATA: REPORT_TYPE_PROFILE_METADATA contains metadata about the profile i.e. binaries, labels",
"description": "- REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED: REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED unspecified\n - REPORT_TYPE_PPROF: REPORT_TYPE_PPROF unspecified\n - REPORT_TYPE_TOP: REPORT_TYPE_TOP unspecified\n - REPORT_TYPE_CALLGRAPH: REPORT_TYPE_CALLGRAPH unspecified\n - REPORT_TYPE_FLAMEGRAPH_TABLE: REPORT_TYPE_FLAMEGRAPH_TABLE unspecified\n - REPORT_TYPE_FLAMEGRAPH_ARROW: REPORT_TYPE_FLAMEGRAPH_ARROW unspecified\n - REPORT_TYPE_SOURCE: REPORT_TYPE_SOURCE contains source code annotated with profiling information\n - REPORT_TYPE_TABLE_ARROW: REPORT_TYPE_TABLE_ARROW unspecified\n - REPORT_TYPE_PROFILE_METADATA: REPORT_TYPE_PROFILE_METADATA contains metadata about the profile i.e. binaries, labels\n - REPORT_TYPE_EXPORT: REPORT_TYPE_EXPORT contains the profile as a downloadable file",
"title": "ReportType is the type of report to return"
},
"metastorev1alpha1Function": {
Expand Down
10 changes: 5 additions & 5 deletions parca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ object_storage:
# Scraping tends to produce a lot more data than Parca Agent CPU which
# results in memory usage by the server.
#
# scrape_configs:
# - job_name: "parca"
# scrape_interval: "10s"
# static_configs:
# - targets: [ '127.0.0.1:7070' ]
scrape_configs:
- job_name: "parca"
scrape_interval: "10s"
static_configs:
- targets: [ '127.0.0.1:7070' ]

# Nested under the job config:
#
Expand Down
5 changes: 5 additions & 0 deletions pkg/parca/parca.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ type Flags struct {

Storage FlagsStorage `embed:"" prefix:"storage-"`

ObjectStorageExport bool `default:"false" help:"Export profiles to object storage first before serving them via a signed URL."`

Symbolizer FlagsSymbolizer `embed:"" prefix:"symbolizer-"`

Debuginfo FlagsDebuginfo `embed:"" prefix:"debuginfo-"`
Expand Down Expand Up @@ -424,6 +426,9 @@ func Run(ctx context.Context, logger log.Logger, reg *prometheus.Registry, flags
debuginfoBucket,
debuginfodClients,
),
flags.ObjectStorageExport,
signedRequestsClient,
bucket,
)

t := telemetryservice.NewTelemetry(
Expand Down
92 changes: 92 additions & 0 deletions pkg/query/columnquery.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"context"
"errors"
"fmt"
"io"
"path"
"strconv"
"strings"
"sync"
"time"
Expand All @@ -27,6 +30,7 @@ import (
"github.com/apache/arrow/go/v16/arrow/bitutil"
"github.com/apache/arrow/go/v16/arrow/math"
"github.com/apache/arrow/go/v16/arrow/memory"
"github.com/cespare/xxhash/v2"
"github.com/go-kit/log"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -62,6 +66,14 @@ type SourceFinder interface {
SourceExists(ctx context.Context, ref *pb.SourceReference) (bool, error)
}

type SignedGetClient interface {
SignedGET(ctx context.Context, objectKey string, expiry time.Time) (string, error)
}

type ObjectStoreUploadClient interface {
Upload(ctx context.Context, objectKey string, r io.Reader) error
}

// ColumnQueryAPI is the read api interface for parca
// It implements the proto/query/query.proto APIServer interface.
type ColumnQueryAPI struct {
Expand All @@ -77,6 +89,13 @@ type ColumnQueryAPI struct {
converter *parcacol.ArrowToProfileConverter

sourceFinder SourceFinder

// Whether to first upload an export to object storage and returning a
// signed URL to the client instead of serving the export directly within
// the API response.
objectStorgeExport bool
signedGetClient SignedGetClient
objectStore ObjectStoreUploadClient
}

func NewColumnQueryAPI(
Expand All @@ -87,6 +106,9 @@ func NewColumnQueryAPI(
mem memory.Allocator,
converter *parcacol.ArrowToProfileConverter,
sourceFinder SourceFinder,
objectStorageExport bool,
signedGetClient SignedGetClient,
objectStore ObjectStoreUploadClient,
) *ColumnQueryAPI {
return &ColumnQueryAPI{
logger: logger,
Expand All @@ -97,6 +119,9 @@ func NewColumnQueryAPI(
mem: mem,
converter: converter,
sourceFinder: sourceFinder,
objectStorgeExport: objectStorageExport,
signedGetClient: signedGetClient,
objectStore: objectStore,
}
}

Expand Down Expand Up @@ -320,16 +345,24 @@ func (q *ColumnQueryAPI) Query(ctx context.Context, req *pb.QueryRequest) (*pb.Q
return nil, fmt.Errorf("filtering profile: %w", err)
}

data, err := req.MarshalVT()
if err != nil {
return nil, fmt.Errorf("marshaling request: %w", err)
}
requestKey := xxhash.Sum64(data)

return q.renderReport(
ctx,
p,
strconv.FormatUint(requestKey, 16),
req.GetReportType(),
req.GetNodeTrimThreshold(),
filtered,
groupBy,
req.GetSourceReference(),
source,
isDiff,
req.GetExportFormat(),
)
}

Expand Down Expand Up @@ -474,17 +507,20 @@ func filterRecord(
func (q *ColumnQueryAPI) renderReport(
ctx context.Context,
p profile.Profile,
requestKey string,
typ pb.QueryRequest_ReportType,
nodeTrimThreshold float32,
filtered int64,
groupBy []string,
sourceReference *pb.SourceReference,
source string,
isDiff bool,
exportFormat pb.Format,
) (*pb.QueryResponse, error) {
return RenderReport(
ctx,
q.tracer,
requestKey,
p,
typ,
nodeTrimThreshold,
Expand All @@ -496,12 +532,17 @@ func (q *ColumnQueryAPI) renderReport(
sourceReference,
source,
isDiff,
exportFormat,
q.objectStorgeExport,
q.signedGetClient,
q.objectStore,
)
}

func RenderReport(
ctx context.Context,
tracer trace.Tracer,
requestKey string,
p profile.Profile,
typ pb.QueryRequest_ReportType,
nodeTrimThreshold float32,
Expand All @@ -513,6 +554,10 @@ func RenderReport(
sourceReference *pb.SourceReference,
source string,
isDiff bool,
exportFormat pb.Format,
objectStorageExport bool,
signedGetClient SignedGetClient,
objectStore ObjectStoreUploadClient,
) (*pb.QueryResponse, error) {
ctx, span := tracer.Start(ctx, "renderReport")
span.SetAttributes(attribute.String("reportType", typ.String()))
Expand Down Expand Up @@ -609,6 +654,53 @@ func RenderReport(
Filtered: filtered,
Report: &pb.QueryResponse_Pprof{Pprof: buf},
}, nil
case pb.QueryRequest_REPORT_TYPE_EXPORT:
var buf []byte

switch exportFormat {
case pb.Format_FORMAT_PPROF:
pp, err := GenerateFlatPprof(ctx, isDiff, p)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate pprof: %v", err.Error())
}

buf, err = SerializePprof(pp)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate pprof: %v", err.Error())
}
default:
return nil, status.Errorf(codes.InvalidArgument, "requested export format does not exist")
}

if objectStorageExport {
key := path.Join(requestKey, "pprof")
if err := objectStore.Upload(ctx, key, bytes.NewReader(buf)); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upload export to object storage: %v", err.Error())
}

signedURL, err := signedGetClient.SignedGET(ctx, key, time.Now().Add(time.Hour))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate signed URL: %v", err.Error())
}

return &pb.QueryResponse{
Report: &pb.QueryResponse_Export{
Export: &pb.Export{
Format: exportFormat,
Content: &pb.Export_Url{Url: &pb.URLExport{Url: signedURL}},
},
},
}, nil
}

return &pb.QueryResponse{
Report: &pb.QueryResponse_Export{
Export: &pb.Export{
Format: exportFormat,
Content: &pb.Export_Inline{Inline: &pb.InlineExport{Content: buf}},
},
},
}, nil
case pb.QueryRequest_REPORT_TYPE_TOP:
op, err := converter.Convert(ctx, p)
if err != nil {
Expand Down
Loading
Loading